@swarmclawai/swarmclaw 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -40
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +17 -0
- package/src/app/api/agents/[id]/thread/route.ts +3 -1
- package/src/app/api/agents/route.ts +23 -1
- package/src/app/api/auth/route.ts +1 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +16 -5
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/route.ts +12 -0
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +7 -1
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +1 -1
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +6 -10
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +2 -1
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/page.tsx +126 -15
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +34 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +20 -4
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-sheet.tsx +249 -7
- package/src/components/agents/inspector-panel.tsx +3 -2
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +41 -14
- package/src/components/chat/chat-card.tsx +2 -1
- package/src/components/chat/chat-header.tsx +8 -13
- package/src/components/chat/chat-list.tsx +58 -20
- package/src/components/chat/message-list.tsx +142 -18
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +157 -86
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +2 -0
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/projects/project-detail.tsx +7 -2
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/section-heartbeat.tsx +11 -6
- package/src/components/shared/settings/section-orchestrator.tsx +3 -0
- package/src/components/shared/settings/settings-page.tsx +5 -3
- package/src/components/tasks/approvals-panel.tsx +7 -1
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approvals-auto-approve.test.ts +59 -0
- package/src/lib/server/build-llm.test.ts +13 -5
- package/src/lib/server/chat-execution-tool-events.test.ts +87 -2
- package/src/lib/server/chat-execution.ts +159 -71
- package/src/lib/server/chatroom-helpers.test.ts +7 -0
- package/src/lib/server/chatroom-helpers.ts +99 -6
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/manager.ts +89 -61
- package/src/lib/server/connectors/slack.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -2
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +10 -4
- package/src/lib/server/main-agent-loop.ts +13 -6
- package/src/lib/server/openclaw-exec-config.ts +4 -2
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/orchestrator-lg.ts +1 -2
- package/src/lib/server/orchestrator.ts +3 -2
- package/src/lib/server/plugins.test.ts +9 -1
- package/src/lib/server/plugins.ts +12 -2
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +1 -1
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/session-tools/autonomy-tools.test.ts +23 -0
- package/src/lib/server/session-tools/crud.ts +27 -3
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +18 -8
- package/src/lib/server/session-tools/file-normalize.test.ts +5 -0
- package/src/lib/server/session-tools/file.ts +8 -2
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/index.ts +31 -1
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/monitor.ts +14 -7
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +9 -2
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/session-info.ts +22 -1
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +23 -0
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +3 -1
- package/src/lib/server/session-tools/web.ts +73 -30
- package/src/lib/server/storage.ts +29 -3
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +139 -4
- package/src/lib/server/structured-extract.ts +1 -1
- package/src/lib/server/task-mention.ts +0 -1
- package/src/lib/server/tool-aliases.ts +37 -6
- package/src/lib/server/tool-capability-policy.ts +1 -1
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.ts +55 -1
- package/src/stores/use-app-store.ts +43 -1
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +189 -6
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -13
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { genId } from '@/lib/id'
|
|
3
|
+
import { formatZodError, ExternalAgentRegisterSchema } from '@/lib/validation/schemas'
|
|
4
|
+
import { loadExternalAgents, saveExternalAgents } from '@/lib/server/storage'
|
|
5
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
6
|
+
import type { ExternalAgentRuntime } from '@/types'
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
export const dynamic = 'force-dynamic'
|
|
9
|
+
|
|
10
|
+
function withDerivedStatus(record: ExternalAgentRuntime): ExternalAgentRuntime {
|
|
11
|
+
const now = Date.now()
|
|
12
|
+
const lastSeenAt = typeof record.lastSeenAt === 'number' ? record.lastSeenAt : null
|
|
13
|
+
const staleMs = 3 * 60_000
|
|
14
|
+
if (!lastSeenAt) return { ...record, status: record.status || 'offline' }
|
|
15
|
+
if (record.status === 'offline') return record
|
|
16
|
+
return {
|
|
17
|
+
...record,
|
|
18
|
+
status: now - lastSeenAt > staleMs ? 'stale' : (record.status || 'online'),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function GET() {
|
|
23
|
+
const runtimes = loadExternalAgents()
|
|
24
|
+
const items: ExternalAgentRuntime[] = Object.values(runtimes)
|
|
25
|
+
.map((item) => withDerivedStatus(item))
|
|
26
|
+
.sort((a, b) => (b.lastSeenAt || b.updatedAt || 0) - (a.lastSeenAt || a.updatedAt || 0))
|
|
27
|
+
return NextResponse.json(items)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function POST(req: Request) {
|
|
31
|
+
const raw = await req.json().catch(() => ({}))
|
|
32
|
+
const parsed = ExternalAgentRegisterSchema.safeParse(raw)
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
|
|
35
|
+
}
|
|
36
|
+
const body = parsed.data
|
|
37
|
+
const now = Date.now()
|
|
38
|
+
const items = loadExternalAgents()
|
|
39
|
+
const id = body.id || `external-${genId()}`
|
|
40
|
+
const existing = items[id]
|
|
41
|
+
items[id] = {
|
|
42
|
+
...existing,
|
|
43
|
+
id,
|
|
44
|
+
name: body.name.trim(),
|
|
45
|
+
sourceType: body.sourceType,
|
|
46
|
+
status: body.status || existing?.status || 'online',
|
|
47
|
+
provider: (body.provider as ExternalAgentRuntime['provider']) || null,
|
|
48
|
+
model: body.model || null,
|
|
49
|
+
workspace: body.workspace || null,
|
|
50
|
+
transport: body.transport || null,
|
|
51
|
+
endpoint: body.endpoint || null,
|
|
52
|
+
agentId: body.agentId || null,
|
|
53
|
+
gatewayProfileId: body.gatewayProfileId || null,
|
|
54
|
+
capabilities: body.capabilities,
|
|
55
|
+
labels: body.labels,
|
|
56
|
+
metadata: body.metadata,
|
|
57
|
+
tokenStats: body.tokenStats,
|
|
58
|
+
lastHeartbeatAt: existing?.lastHeartbeatAt || now,
|
|
59
|
+
lastSeenAt: now,
|
|
60
|
+
createdAt: existing?.createdAt || now,
|
|
61
|
+
updatedAt: now,
|
|
62
|
+
}
|
|
63
|
+
saveExternalAgents(items)
|
|
64
|
+
notify('external_agents')
|
|
65
|
+
return NextResponse.json(items[id])
|
|
66
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { probeOpenClawHealth } from '@/lib/server/openclaw-health'
|
|
3
|
+
import { loadGatewayProfiles, saveGatewayProfiles } from '@/lib/server/storage'
|
|
4
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
|
+
const { id } = await params
|
|
10
|
+
const gateways = loadGatewayProfiles()
|
|
11
|
+
const gateway = gateways[id]
|
|
12
|
+
if (!gateway) return notFound()
|
|
13
|
+
|
|
14
|
+
const result = await probeOpenClawHealth({
|
|
15
|
+
endpoint: gateway.endpoint,
|
|
16
|
+
credentialId: gateway.credentialId || null,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
gateway.status = result.ok ? 'healthy' : (result.authProvided ? 'degraded' : 'offline')
|
|
20
|
+
gateway.lastCheckedAt = Date.now()
|
|
21
|
+
gateway.lastError = result.ok ? null : (result.error || result.hint || 'Gateway health check failed.')
|
|
22
|
+
gateway.lastModelCount = Array.isArray(result.models) ? result.models.length : 0
|
|
23
|
+
gateway.updatedAt = Date.now()
|
|
24
|
+
saveGatewayProfiles(gateways)
|
|
25
|
+
notify('gateways')
|
|
26
|
+
|
|
27
|
+
return NextResponse.json(result)
|
|
28
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { normalizeOpenClawEndpoint } from '@/lib/openclaw-endpoint'
|
|
3
|
+
import { loadAgents, loadGatewayProfiles, saveAgents, saveGatewayProfiles } from '@/lib/server/storage'
|
|
4
|
+
import { mutateItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
5
|
+
import type { Agent, AgentRoutingTarget, GatewayProfile } from '@/types'
|
|
6
|
+
|
|
7
|
+
const ops: CollectionOps<GatewayProfile> = {
|
|
8
|
+
load: loadGatewayProfiles,
|
|
9
|
+
save: saveGatewayProfiles,
|
|
10
|
+
topic: 'gateways',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeTags(value: unknown): string[] {
|
|
14
|
+
if (!Array.isArray(value)) return []
|
|
15
|
+
return value
|
|
16
|
+
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
21
|
+
const { id } = await params
|
|
22
|
+
const body = await req.json().catch(() => ({}))
|
|
23
|
+
const result = mutateItem(ops, id, (gateway, all) => {
|
|
24
|
+
if (body.isDefault === true) {
|
|
25
|
+
for (const [candidateId, candidate] of Object.entries(all)) {
|
|
26
|
+
if (candidateId === id || !candidate || typeof candidate !== 'object') continue
|
|
27
|
+
candidate.isDefault = false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (body.name !== undefined) gateway.name = String(body.name || '').trim() || gateway.name
|
|
31
|
+
if (body.endpoint !== undefined) gateway.endpoint = normalizeOpenClawEndpoint(body.endpoint || undefined)
|
|
32
|
+
if (body.wsUrl !== undefined) gateway.wsUrl = body.wsUrl || null
|
|
33
|
+
if (body.credentialId !== undefined) gateway.credentialId = body.credentialId || null
|
|
34
|
+
if (body.status !== undefined) gateway.status = body.status || 'unknown'
|
|
35
|
+
if (body.notes !== undefined) gateway.notes = body.notes || null
|
|
36
|
+
if (body.tags !== undefined) gateway.tags = normalizeTags(body.tags)
|
|
37
|
+
if (body.lastError !== undefined) gateway.lastError = body.lastError || null
|
|
38
|
+
if (body.lastCheckedAt !== undefined) gateway.lastCheckedAt = body.lastCheckedAt || null
|
|
39
|
+
if (body.lastModelCount !== undefined) gateway.lastModelCount = body.lastModelCount || null
|
|
40
|
+
if (body.discoveredHost !== undefined) gateway.discoveredHost = body.discoveredHost || null
|
|
41
|
+
if (body.discoveredPort !== undefined) gateway.discoveredPort = body.discoveredPort || null
|
|
42
|
+
if (body.isDefault !== undefined) gateway.isDefault = body.isDefault === true
|
|
43
|
+
gateway.updatedAt = Date.now()
|
|
44
|
+
return gateway
|
|
45
|
+
})
|
|
46
|
+
if (!result) return notFound()
|
|
47
|
+
return NextResponse.json(result)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
51
|
+
const { id } = await params
|
|
52
|
+
const gateways = loadGatewayProfiles()
|
|
53
|
+
if (!gateways[id]) return notFound()
|
|
54
|
+
delete gateways[id]
|
|
55
|
+
saveGatewayProfiles(gateways)
|
|
56
|
+
|
|
57
|
+
const agents = loadAgents({ includeTrashed: true })
|
|
58
|
+
let agentChanged = false
|
|
59
|
+
for (const agent of Object.values(agents) as Agent[]) {
|
|
60
|
+
if (agent.gatewayProfileId === id) {
|
|
61
|
+
agent.gatewayProfileId = null
|
|
62
|
+
agentChanged = true
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(agent.routingTargets)) {
|
|
65
|
+
const nextTargets = agent.routingTargets.map((target: AgentRoutingTarget) => (
|
|
66
|
+
target.gatewayProfileId === id
|
|
67
|
+
? { ...target, gatewayProfileId: null }
|
|
68
|
+
: target
|
|
69
|
+
))
|
|
70
|
+
if (JSON.stringify(nextTargets) !== JSON.stringify(agent.routingTargets)) {
|
|
71
|
+
agent.routingTargets = nextTargets
|
|
72
|
+
agentChanged = true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (agentChanged) saveAgents(agents)
|
|
77
|
+
|
|
78
|
+
return NextResponse.json({ ok: true })
|
|
79
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { genId } from '@/lib/id'
|
|
3
|
+
import { normalizeOpenClawEndpoint } from '@/lib/openclaw-endpoint'
|
|
4
|
+
import { getGatewayProfiles } from '@/lib/server/agent-runtime-config'
|
|
5
|
+
import { loadGatewayProfiles, saveGatewayProfiles } from '@/lib/server/storage'
|
|
6
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
function normalizeTags(value: unknown): string[] {
|
|
10
|
+
if (!Array.isArray(value)) return []
|
|
11
|
+
return value
|
|
12
|
+
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function GET() {
|
|
17
|
+
return NextResponse.json(getGatewayProfiles('openclaw'))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function POST(req: Request) {
|
|
21
|
+
const body = await req.json().catch(() => ({}))
|
|
22
|
+
const endpoint = normalizeOpenClawEndpoint(body.endpoint || undefined)
|
|
23
|
+
const now = Date.now()
|
|
24
|
+
const gateways = loadGatewayProfiles()
|
|
25
|
+
const id = body.id || `gateway-${genId()}`
|
|
26
|
+
const isDefault = body.isDefault === true
|
|
27
|
+
|
|
28
|
+
if (isDefault) {
|
|
29
|
+
for (const gateway of Object.values(gateways) as Array<Record<string, unknown>>) {
|
|
30
|
+
gateway.isDefault = false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
gateways[id] = {
|
|
35
|
+
id,
|
|
36
|
+
name: typeof body.name === 'string' && body.name.trim() ? body.name.trim() : 'OpenClaw Gateway',
|
|
37
|
+
provider: 'openclaw',
|
|
38
|
+
endpoint,
|
|
39
|
+
wsUrl: body.wsUrl || null,
|
|
40
|
+
credentialId: body.credentialId || null,
|
|
41
|
+
status: body.status || 'unknown',
|
|
42
|
+
notes: typeof body.notes === 'string' ? body.notes : null,
|
|
43
|
+
tags: normalizeTags(body.tags),
|
|
44
|
+
lastError: null,
|
|
45
|
+
lastCheckedAt: null,
|
|
46
|
+
lastModelCount: null,
|
|
47
|
+
discoveredHost: typeof body.discoveredHost === 'string' ? body.discoveredHost : null,
|
|
48
|
+
discoveredPort: typeof body.discoveredPort === 'number' ? body.discoveredPort : null,
|
|
49
|
+
isDefault,
|
|
50
|
+
createdAt: now,
|
|
51
|
+
updatedAt: now,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
saveGatewayProfiles(gateways)
|
|
55
|
+
notify('gateways')
|
|
56
|
+
return NextResponse.json(gateways[id])
|
|
57
|
+
}
|
|
@@ -5,6 +5,7 @@ import { ensureGatewayConnected, getGateway, disconnectGateway, manualConnect }
|
|
|
5
5
|
export async function POST(req: Request) {
|
|
6
6
|
const body = await req.json()
|
|
7
7
|
const { method, params } = body as { method?: string; params?: Record<string, unknown> }
|
|
8
|
+
const profileId = typeof params?.profileId === 'string' ? params.profileId : undefined
|
|
8
9
|
if (!method || typeof method !== 'string') {
|
|
9
10
|
return NextResponse.json({ error: 'Missing RPC method' }, { status: 400 })
|
|
10
11
|
}
|
|
@@ -14,7 +15,7 @@ export async function POST(req: Request) {
|
|
|
14
15
|
try {
|
|
15
16
|
const url = (params?.url as string) || undefined
|
|
16
17
|
const token = (params?.token as string) || undefined
|
|
17
|
-
const ok = await manualConnect(url, token)
|
|
18
|
+
const ok = await manualConnect(url, token, profileId)
|
|
18
19
|
return NextResponse.json({ ok })
|
|
19
20
|
} catch (err: unknown) {
|
|
20
21
|
return NextResponse.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, { status: 502 })
|
|
@@ -22,13 +23,13 @@ export async function POST(req: Request) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
if (method === 'gateway.disconnect') {
|
|
25
|
-
disconnectGateway()
|
|
26
|
+
disconnectGateway(profileId)
|
|
26
27
|
return NextResponse.json({ ok: true })
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
// Reload mode get/set
|
|
30
31
|
if (method === 'gateway.reload-mode.get') {
|
|
31
|
-
const gw = await ensureGatewayConnected()
|
|
32
|
+
const gw = await ensureGatewayConnected({ profileId })
|
|
32
33
|
if (!gw) return NextResponse.json({ error: 'Not connected' }, { status: 503 })
|
|
33
34
|
try {
|
|
34
35
|
const config = await gw.rpc('config.get') as Record<string, unknown> | undefined
|
|
@@ -40,7 +41,7 @@ export async function POST(req: Request) {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
if (method === 'gateway.reload-mode.set') {
|
|
43
|
-
const gw = await ensureGatewayConnected()
|
|
44
|
+
const gw = await ensureGatewayConnected({ profileId })
|
|
44
45
|
if (!gw) return NextResponse.json({ error: 'Not connected' }, { status: 503 })
|
|
45
46
|
try {
|
|
46
47
|
await gw.rpc('config.set', { reloadMode: params?.mode })
|
|
@@ -51,7 +52,7 @@ export async function POST(req: Request) {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// General RPC proxy
|
|
54
|
-
const gw = await ensureGatewayConnected()
|
|
55
|
+
const gw = await ensureGatewayConnected({ profileId })
|
|
55
56
|
if (!gw) {
|
|
56
57
|
return NextResponse.json({ error: 'OpenClaw gateway not connected' }, { status: 503 })
|
|
57
58
|
}
|
|
@@ -66,7 +67,9 @@ export async function POST(req: Request) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/** GET — check gateway connection status */
|
|
69
|
-
export async function GET() {
|
|
70
|
-
const
|
|
70
|
+
export async function GET(req: Request) {
|
|
71
|
+
const { searchParams } = new URL(req.url)
|
|
72
|
+
const profileId = searchParams.get('profileId') || undefined
|
|
73
|
+
const gw = getGateway(profileId || undefined)
|
|
71
74
|
return NextResponse.json({ connected: !!gw?.connected })
|
|
72
75
|
}
|
|
@@ -4,7 +4,7 @@ import { resolveOpenClawGatewayAgentId } from '@/lib/server/openclaw-agent-resol
|
|
|
4
4
|
import { normalizeOpenClawSkillsPayload } from '@/lib/server/openclaw-skills-normalize'
|
|
5
5
|
import { loadAgents, saveAgents } from '@/lib/server/storage'
|
|
6
6
|
import { notify } from '@/lib/server/ws-hub'
|
|
7
|
-
import type {
|
|
7
|
+
import type { SkillAllowlistMode } from '@/types'
|
|
8
8
|
|
|
9
9
|
/** GET ?agentId=X — fetch skills from gateway with eligibility */
|
|
10
10
|
export async function GET(req: Request) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { discoverProviderModels } from '@/lib/server/provider-model-discovery'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET(
|
|
7
|
+
req: Request,
|
|
8
|
+
{ params }: { params: Promise<{ id: string }> },
|
|
9
|
+
) {
|
|
10
|
+
const { id } = await params
|
|
11
|
+
const { searchParams } = new URL(req.url)
|
|
12
|
+
const result = await discoverProviderModels({
|
|
13
|
+
providerId: id,
|
|
14
|
+
credentialId: searchParams.get('credentialId'),
|
|
15
|
+
endpoint: searchParams.get('endpoint'),
|
|
16
|
+
force: searchParams.get('force') === '1',
|
|
17
|
+
requiresApiKey: searchParams.has('requiresApiKey')
|
|
18
|
+
? searchParams.get('requiresApiKey') !== '0'
|
|
19
|
+
: undefined,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return NextResponse.json(result, {
|
|
23
|
+
headers: {
|
|
24
|
+
'Cache-Control': 'private, no-store',
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadSchedules, saveSchedules, deleteSchedule } from '@/lib/server/storage'
|
|
2
|
+
import { loadAgents, loadSchedules, loadSessions, saveSchedules, deleteSchedule } from '@/lib/server/storage'
|
|
3
|
+
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
3
4
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
4
5
|
import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
6
|
+
import { normalizeSchedulePayload } from '@/lib/server/schedule-normalization'
|
|
5
7
|
|
|
6
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
9
|
const ops: CollectionOps<any> = { load: loadSchedules, save: saveSchedules, deleteFn: deleteSchedule, topic: 'schedules' }
|
|
@@ -9,15 +11,42 @@ const ops: CollectionOps<any> = { load: loadSchedules, save: saveSchedules, dele
|
|
|
9
11
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
12
|
const { id } = await params
|
|
11
13
|
const body = await req.json()
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const sessions = loadSessions()
|
|
15
|
+
const agents = loadAgents()
|
|
16
|
+
let result = null
|
|
17
|
+
try {
|
|
18
|
+
result = mutateItem(ops, id, (schedule) => {
|
|
19
|
+
const sessionCwd = typeof schedule.createdInSessionId === 'string'
|
|
20
|
+
? sessions[schedule.createdInSessionId]?.cwd
|
|
21
|
+
: null
|
|
22
|
+
const normalized = normalizeSchedulePayload({
|
|
23
|
+
...schedule,
|
|
24
|
+
...(body as Record<string, unknown>),
|
|
25
|
+
id,
|
|
26
|
+
}, {
|
|
27
|
+
cwd: sessionCwd || WORKSPACE_DIR,
|
|
28
|
+
now: Date.now(),
|
|
29
|
+
})
|
|
30
|
+
if (!normalized.ok) throw new Error(normalized.error)
|
|
31
|
+
const nextSchedule = {
|
|
32
|
+
...schedule,
|
|
33
|
+
...normalized.value,
|
|
34
|
+
id,
|
|
35
|
+
updatedAt: Date.now(),
|
|
36
|
+
}
|
|
37
|
+
if (!agents[String(nextSchedule.agentId)]) {
|
|
38
|
+
throw new Error(`Agent not found: ${String(nextSchedule.agentId)}`)
|
|
39
|
+
}
|
|
40
|
+
nextSchedule.name = resolveScheduleName({
|
|
41
|
+
name: nextSchedule.name,
|
|
42
|
+
taskPrompt: nextSchedule.taskPrompt,
|
|
43
|
+
})
|
|
44
|
+
return nextSchedule
|
|
18
45
|
})
|
|
19
|
-
|
|
20
|
-
|
|
46
|
+
} catch (error: unknown) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
48
|
+
return NextResponse.json({ error: message }, { status: 400 })
|
|
49
|
+
}
|
|
21
50
|
if (!result) return notFound()
|
|
22
51
|
return NextResponse.json(result)
|
|
23
52
|
}
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
|
-
import { loadSchedules, saveSchedules } from '@/lib/server/storage'
|
|
3
|
+
import { loadAgents, loadSchedules, saveSchedules } from '@/lib/server/storage'
|
|
4
|
+
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
5
|
+
import { normalizeSchedulePayload } from '@/lib/server/schedule-normalization'
|
|
4
6
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
5
7
|
import { findDuplicateSchedule } from '@/lib/schedule-dedupe'
|
|
6
8
|
import { notify } from '@/lib/server/ws-hub'
|
|
7
9
|
export const dynamic = 'force-dynamic'
|
|
8
10
|
|
|
11
|
+
function asString(value: unknown): string {
|
|
12
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function asPositiveInt(value: unknown): number | null {
|
|
16
|
+
const parsed = typeof value === 'number'
|
|
17
|
+
? value
|
|
18
|
+
: typeof value === 'string'
|
|
19
|
+
? Number.parseInt(value, 10)
|
|
20
|
+
: Number.NaN
|
|
21
|
+
if (!Number.isFinite(parsed)) return null
|
|
22
|
+
const intValue = Math.trunc(parsed)
|
|
23
|
+
return intValue > 0 ? intValue : null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function asScheduleType(value: unknown): 'cron' | 'interval' | 'once' {
|
|
27
|
+
return value === 'cron' || value === 'interval' || value === 'once' ? value : 'cron'
|
|
28
|
+
}
|
|
9
29
|
|
|
10
30
|
export async function GET(_req: Request) {
|
|
11
31
|
return NextResponse.json(loadSchedules())
|
|
@@ -15,28 +35,46 @@ export async function POST(req: Request) {
|
|
|
15
35
|
const body = await req.json()
|
|
16
36
|
const now = Date.now()
|
|
17
37
|
const schedules = loadSchedules()
|
|
18
|
-
const
|
|
38
|
+
const normalizedSchedule = normalizeSchedulePayload(body as Record<string, unknown>, {
|
|
39
|
+
cwd: WORKSPACE_DIR,
|
|
40
|
+
now,
|
|
41
|
+
})
|
|
42
|
+
if (!normalizedSchedule.ok) {
|
|
43
|
+
return NextResponse.json({ error: normalizedSchedule.error }, { status: 400 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const candidate = normalizedSchedule.value
|
|
47
|
+
const agents = loadAgents()
|
|
48
|
+
if (!agents[String(candidate.agentId)]) {
|
|
49
|
+
return NextResponse.json({ error: `Agent not found: ${String(candidate.agentId)}` }, { status: 400 })
|
|
50
|
+
}
|
|
51
|
+
const scheduleType = asScheduleType(candidate.scheduleType)
|
|
52
|
+
const candidateAgentId = asString(candidate.agentId) || null
|
|
53
|
+
const candidateTaskPrompt = asString(candidate.taskPrompt)
|
|
54
|
+
const candidateCron = asString(candidate.cron) || null
|
|
55
|
+
const candidateIntervalMs = asPositiveInt(candidate.intervalMs)
|
|
56
|
+
const candidateRunAt = asPositiveInt(candidate.runAt)
|
|
19
57
|
|
|
20
58
|
const duplicate = findDuplicateSchedule(schedules, {
|
|
21
|
-
agentId:
|
|
22
|
-
taskPrompt:
|
|
59
|
+
agentId: candidateAgentId,
|
|
60
|
+
taskPrompt: candidateTaskPrompt,
|
|
23
61
|
scheduleType,
|
|
24
|
-
cron:
|
|
25
|
-
intervalMs:
|
|
26
|
-
runAt:
|
|
62
|
+
cron: candidateCron,
|
|
63
|
+
intervalMs: candidateIntervalMs,
|
|
64
|
+
runAt: candidateRunAt,
|
|
27
65
|
})
|
|
28
66
|
if (duplicate) {
|
|
29
67
|
const duplicateId = duplicate.id || ''
|
|
30
68
|
let changed = false
|
|
31
69
|
const nextName = resolveScheduleName({
|
|
32
|
-
name:
|
|
33
|
-
taskPrompt:
|
|
70
|
+
name: candidate.name ?? duplicate.name,
|
|
71
|
+
taskPrompt: candidate.taskPrompt ?? duplicate.taskPrompt,
|
|
34
72
|
})
|
|
35
73
|
if (nextName && nextName !== duplicate.name) {
|
|
36
74
|
duplicate.name = nextName
|
|
37
75
|
changed = true
|
|
38
76
|
}
|
|
39
|
-
const normalizedStatus = typeof
|
|
77
|
+
const normalizedStatus = typeof candidate.status === 'string' ? candidate.status.trim().toLowerCase() : ''
|
|
40
78
|
if ((normalizedStatus === 'active' || normalizedStatus === 'paused') && duplicate.status !== normalizedStatus) {
|
|
41
79
|
duplicate.status = normalizedStatus as 'active' | 'paused'
|
|
42
80
|
changed = true
|
|
@@ -53,29 +91,14 @@ export async function POST(req: Request) {
|
|
|
53
91
|
|
|
54
92
|
const id = genId()
|
|
55
93
|
|
|
56
|
-
let nextRunAt: number | undefined
|
|
57
|
-
if (scheduleType === 'once' && body.runAt) {
|
|
58
|
-
nextRunAt = body.runAt
|
|
59
|
-
} else if (scheduleType === 'interval' && body.intervalMs) {
|
|
60
|
-
nextRunAt = now + body.intervalMs
|
|
61
|
-
} else if (scheduleType === 'cron') {
|
|
62
|
-
// nextRunAt will be computed by the scheduler engine
|
|
63
|
-
nextRunAt = undefined
|
|
64
|
-
}
|
|
65
|
-
|
|
66
94
|
schedules[id] = {
|
|
67
95
|
id,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
taskPrompt: body.taskPrompt || '',
|
|
96
|
+
...candidate,
|
|
97
|
+
name: resolveScheduleName({ name: candidate.name, taskPrompt: candidate.taskPrompt }),
|
|
71
98
|
scheduleType,
|
|
72
|
-
cron: body.cron,
|
|
73
|
-
intervalMs: body.intervalMs,
|
|
74
|
-
runAt: body.runAt,
|
|
75
99
|
lastRunAt: undefined,
|
|
76
|
-
nextRunAt,
|
|
77
|
-
status: body.status || 'active',
|
|
78
100
|
createdAt: now,
|
|
101
|
+
updatedAt: now,
|
|
79
102
|
}
|
|
80
103
|
saveSchedules(schedules)
|
|
81
104
|
notify('schedules')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import { normalizeHeartbeatSettingFields } from '@/lib/heartbeat-defaults'
|
|
2
3
|
import { loadPublicSettings, loadSettings, saveSettings } from '@/lib/server/storage'
|
|
3
|
-
import {
|
|
4
|
+
import { normalizeRuntimeSettingFields } from '@/lib/runtime-loop'
|
|
4
5
|
export const dynamic = 'force-dynamic'
|
|
5
6
|
|
|
6
7
|
|
|
@@ -10,8 +11,6 @@ const MEMORY_PER_LOOKUP_MIN = 1
|
|
|
10
11
|
const MEMORY_PER_LOOKUP_MAX = 200
|
|
11
12
|
const MEMORY_LINKED_MIN = 0
|
|
12
13
|
const MEMORY_LINKED_MAX = 1000
|
|
13
|
-
const DELEGATION_DEPTH_MIN = 1
|
|
14
|
-
const DELEGATION_DEPTH_MAX = 12
|
|
15
14
|
const RESPONSE_CACHE_TTL_MIN_SEC = 5
|
|
16
15
|
const RESPONSE_CACHE_TTL_MAX_SEC = 7 * 24 * 3600
|
|
17
16
|
const RESPONSE_CACHE_MAX_ENTRIES_MIN = 1
|
|
@@ -83,12 +82,8 @@ export async function PUT(req: Request) {
|
|
|
83
82
|
MEMORY_LINKED_MIN,
|
|
84
83
|
MEMORY_LINKED_MAX,
|
|
85
84
|
)
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
DEFAULT_DELEGATION_MAX_DEPTH,
|
|
89
|
-
DELEGATION_DEPTH_MIN,
|
|
90
|
-
DELEGATION_DEPTH_MAX,
|
|
91
|
-
)
|
|
85
|
+
const normalizedRuntime = normalizeRuntimeSettingFields(settings)
|
|
86
|
+
const normalizedHeartbeat = normalizeHeartbeatSettingFields(settings)
|
|
92
87
|
const nextResponseCacheTtlSec = parseIntSetting(
|
|
93
88
|
settings.responseCacheTtlSec,
|
|
94
89
|
15 * 60,
|
|
@@ -120,7 +115,8 @@ export async function PUT(req: Request) {
|
|
|
120
115
|
settings.maxMemoriesPerLookup = nextPerLookup
|
|
121
116
|
settings.memoryMaxPerLookup = nextPerLookup
|
|
122
117
|
settings.maxLinkedMemoriesExpanded = nextLinked
|
|
123
|
-
settings
|
|
118
|
+
Object.assign(settings, normalizedRuntime)
|
|
119
|
+
Object.assign(settings, normalizedHeartbeat)
|
|
124
120
|
settings.responseCacheTtlSec = nextResponseCacheTtlSec
|
|
125
121
|
settings.responseCacheMaxEntries = nextResponseCacheMaxEntries
|
|
126
122
|
settings.responseCacheEnabled = parseBoolSetting(settings.responseCacheEnabled, true)
|
|
@@ -90,12 +90,14 @@ export async function GET(req: Request) {
|
|
|
90
90
|
const checkedAt = Date.now()
|
|
91
91
|
|
|
92
92
|
const nodeVersion = process.versions.node
|
|
93
|
-
const
|
|
94
|
-
|
|
93
|
+
const [nodeMajorRaw, nodeMinorRaw] = String(nodeVersion).split('.')
|
|
94
|
+
const nodeMajor = Number.parseInt(nodeMajorRaw || '0', 10)
|
|
95
|
+
const nodeMinor = Number.parseInt(nodeMinorRaw || '0', 10)
|
|
96
|
+
if (nodeMajor > 22 || (nodeMajor === 22 && nodeMinor >= 6)) {
|
|
95
97
|
pushCheck(checks, 'node-version', 'Node.js version', 'pass', `Detected Node ${nodeVersion}.`, true)
|
|
96
98
|
} else {
|
|
97
|
-
pushCheck(checks, 'node-version', 'Node.js version', 'fail', `Detected Node ${nodeVersion}. Node
|
|
98
|
-
actions.push('Install Node.js
|
|
99
|
+
pushCheck(checks, 'node-version', 'Node.js version', 'fail', `Detected Node ${nodeVersion}. Node 22.6+ is required.`, true)
|
|
100
|
+
actions.push('Install Node.js 22.6 or newer from https://nodejs.org and rerun setup.')
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
const npmCheck = run('npm', ['--version'], 5_000)
|
|
@@ -13,6 +13,7 @@ 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
15
|
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
16
|
+
import type { BoardTask } from '@/types'
|
|
16
17
|
import '@/lib/server/builtin-plugins'
|
|
17
18
|
|
|
18
19
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -151,7 +152,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
151
152
|
if (unblockedIds.length > 0) {
|
|
152
153
|
upsertStoredItems('tasks', [
|
|
153
154
|
[id, tasks[id]],
|
|
154
|
-
...unblockedIds.map((uid) => [uid, tasks[uid]] as [string,
|
|
155
|
+
...unblockedIds.map((uid) => [uid, tasks[uid]] as [string, BoardTask]),
|
|
155
156
|
])
|
|
156
157
|
for (const uid of unblockedIds) {
|
|
157
158
|
enqueueTask(uid)
|
|
@@ -4,7 +4,7 @@ import { enqueueTask, disableSessionHeartbeat } from '@/lib/server/queue'
|
|
|
4
4
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
5
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
6
6
|
import { createNotification } from '@/lib/server/create-notification'
|
|
7
|
-
import type { BoardTaskStatus } from '@/types'
|
|
7
|
+
import type { BoardTask, BoardTaskStatus } from '@/types'
|
|
8
8
|
|
|
9
9
|
const VALID_STATUSES: BoardTaskStatus[] = ['backlog', 'queued', 'running', 'completed', 'failed', 'archived']
|
|
10
10
|
|
|
@@ -82,7 +82,7 @@ export async function POST(req: Request) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
upsertStoredItems('tasks', results.map((id) => [id, tasks[id]] as [string,
|
|
85
|
+
upsertStoredItems('tasks', results.map((id) => [id, tasks[id]] as [string, BoardTask]))
|
|
86
86
|
|
|
87
87
|
if (updated > 0) {
|
|
88
88
|
const action = body.status
|