@swarmclawai/swarmclaw 0.4.5 → 0.5.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 +8 -2
- package/bin/server-cmd.js +28 -19
- package/next.config.ts +5 -0
- package/package.json +2 -1
- package/src/app/api/agents/[id]/route.ts +23 -5
- package/src/app/api/agents/trash/route.ts +44 -0
- package/src/app/api/connectors/[id]/route.ts +7 -4
- package/src/app/api/connectors/[id]/webhook/route.ts +6 -2
- package/src/app/api/openclaw/agent-files/route.ts +57 -0
- package/src/app/api/openclaw/approvals/route.ts +46 -0
- package/src/app/api/openclaw/config-sync/route.ts +33 -0
- package/src/app/api/openclaw/cron/route.ts +52 -0
- package/src/app/api/openclaw/directory/route.ts +4 -3
- package/src/app/api/openclaw/discover/route.ts +3 -2
- package/src/app/api/openclaw/dotenv-keys/route.ts +18 -0
- package/src/app/api/openclaw/exec-config/route.ts +41 -0
- package/src/app/api/openclaw/gateway/route.ts +72 -0
- package/src/app/api/openclaw/history/route.ts +109 -0
- package/src/app/api/openclaw/media/route.ts +53 -0
- package/src/app/api/openclaw/models/route.ts +12 -0
- package/src/app/api/openclaw/permissions/route.ts +39 -0
- package/src/app/api/openclaw/sandbox-env/route.ts +69 -0
- package/src/app/api/openclaw/skills/install/route.ts +32 -0
- package/src/app/api/openclaw/skills/remove/route.ts +24 -0
- package/src/app/api/openclaw/skills/route.ts +82 -0
- package/src/app/api/openclaw/sync/route.ts +3 -2
- package/src/app/api/projects/[id]/route.ts +1 -1
- package/src/app/api/projects/route.ts +1 -1
- package/src/app/api/secrets/[id]/route.ts +1 -1
- package/src/app/api/sessions/[id]/edit-resend/route.ts +22 -0
- package/src/app/api/sessions/[id]/fork/route.ts +44 -0
- package/src/app/api/sessions/[id]/messages/route.ts +18 -1
- package/src/app/api/sessions/[id]/route.ts +12 -3
- package/src/app/api/sessions/route.ts +6 -2
- package/src/app/globals.css +14 -0
- package/src/app/layout.tsx +5 -20
- package/src/cli/index.js +33 -1
- package/src/cli/spec.js +40 -0
- package/src/components/agents/agent-avatar.tsx +45 -0
- package/src/components/agents/agent-card.tsx +19 -5
- package/src/components/agents/agent-chat-list.tsx +31 -24
- package/src/components/agents/agent-files-editor.tsx +185 -0
- package/src/components/agents/agent-list.tsx +82 -3
- package/src/components/agents/agent-sheet.tsx +31 -0
- package/src/components/agents/cron-job-form.tsx +137 -0
- package/src/components/agents/exec-config-panel.tsx +147 -0
- package/src/components/agents/inspector-panel.tsx +310 -0
- package/src/components/agents/openclaw-skills-panel.tsx +230 -0
- package/src/components/agents/permission-preset-selector.tsx +79 -0
- package/src/components/agents/personality-builder.tsx +111 -0
- package/src/components/agents/sandbox-env-panel.tsx +72 -0
- package/src/components/agents/skill-install-dialog.tsx +102 -0
- package/src/components/agents/trash-list.tsx +109 -0
- package/src/components/chat/chat-area.tsx +14 -2
- package/src/components/chat/chat-header.tsx +168 -4
- package/src/components/chat/chat-preview-panel.tsx +113 -0
- package/src/components/chat/exec-approval-card.tsx +89 -0
- package/src/components/chat/message-bubble.tsx +218 -36
- package/src/components/chat/message-list.tsx +135 -31
- package/src/components/chat/streaming-bubble.tsx +59 -10
- package/src/components/chat/suggestions-bar.tsx +74 -0
- package/src/components/chat/thinking-indicator.tsx +20 -6
- package/src/components/chat/tool-call-bubble.tsx +89 -16
- package/src/components/chat/tool-request-banner.tsx +20 -2
- package/src/components/chat/trace-block.tsx +103 -0
- package/src/components/projects/project-list.tsx +1 -0
- package/src/components/settings/gateway-connection-panel.tsx +278 -0
- package/src/components/shared/avatar.tsx +13 -2
- package/src/components/shared/settings/settings-page.tsx +1 -0
- package/src/components/tasks/task-board.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +12 -12
- package/src/hooks/use-continuous-speech.ts +42 -5
- package/src/hooks/use-openclaw-gateway.ts +63 -0
- package/src/lib/notification-sounds.ts +58 -0
- package/src/lib/personality-parser.ts +97 -0
- package/src/lib/providers/openclaw.ts +17 -2
- package/src/lib/runtime-loop.ts +2 -2
- package/src/lib/server/chat-execution.ts +44 -2
- package/src/lib/server/connectors/bluebubbles.test.ts +17 -8
- package/src/lib/server/connectors/bluebubbles.ts +5 -2
- package/src/lib/server/connectors/googlechat.ts +14 -10
- package/src/lib/server/connectors/manager.ts +37 -15
- package/src/lib/server/connectors/openclaw.ts +1 -0
- package/src/lib/server/daemon-state.ts +11 -0
- package/src/lib/server/main-agent-loop.ts +2 -3
- package/src/lib/server/main-session.ts +21 -0
- package/src/lib/server/openclaw-config-sync.ts +107 -0
- package/src/lib/server/openclaw-exec-config.ts +52 -0
- package/src/lib/server/openclaw-gateway.ts +291 -0
- package/src/lib/server/openclaw-history-merge.ts +36 -0
- package/src/lib/server/openclaw-models.ts +56 -0
- package/src/lib/server/openclaw-permission-presets.ts +64 -0
- package/src/lib/server/openclaw-sync.ts +11 -10
- package/src/lib/server/queue.ts +2 -1
- package/src/lib/server/session-tools/connector.ts +4 -4
- package/src/lib/server/session-tools/delegate.ts +19 -3
- package/src/lib/server/session-tools/file.ts +20 -20
- package/src/lib/server/session-tools/index.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +6 -6
- package/src/lib/server/session-tools/sandbox.ts +2 -2
- package/src/lib/server/session-tools/search-providers.ts +13 -6
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +2 -2
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/web.ts +8 -8
- package/src/lib/server/storage.ts +62 -11
- package/src/lib/server/stream-agent-chat.ts +24 -4
- package/src/lib/server/suggestions.ts +20 -0
- package/src/lib/server/ws-hub.ts +14 -0
- package/src/stores/use-app-store.ts +53 -1
- package/src/stores/use-approval-store.ts +78 -0
- package/src/stores/use-chat-store.ts +153 -5
- package/src/types/index.ts +127 -1
- package/tsconfig.json +13 -4
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureGatewayConnected, getGateway, disconnectGateway, manualConnect } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
|
|
4
|
+
/** POST — proxy an RPC call or perform gateway actions */
|
|
5
|
+
export async function POST(req: Request) {
|
|
6
|
+
const body = await req.json()
|
|
7
|
+
const { method, params } = body as { method?: string; params?: Record<string, unknown> }
|
|
8
|
+
if (!method || typeof method !== 'string') {
|
|
9
|
+
return NextResponse.json({ error: 'Missing RPC method' }, { status: 400 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Gateway control actions
|
|
13
|
+
if (method === 'gateway.connect') {
|
|
14
|
+
try {
|
|
15
|
+
const url = (params?.url as string) || undefined
|
|
16
|
+
const token = (params?.token as string) || undefined
|
|
17
|
+
const ok = await manualConnect(url, token)
|
|
18
|
+
return NextResponse.json({ ok })
|
|
19
|
+
} catch (err: unknown) {
|
|
20
|
+
return NextResponse.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, { status: 502 })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (method === 'gateway.disconnect') {
|
|
25
|
+
disconnectGateway()
|
|
26
|
+
return NextResponse.json({ ok: true })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Reload mode get/set
|
|
30
|
+
if (method === 'gateway.reload-mode.get') {
|
|
31
|
+
const gw = await ensureGatewayConnected()
|
|
32
|
+
if (!gw) return NextResponse.json({ error: 'Not connected' }, { status: 503 })
|
|
33
|
+
try {
|
|
34
|
+
const config = await gw.rpc('config.get') as Record<string, unknown> | undefined
|
|
35
|
+
const mode = (config as Record<string, unknown>)?.reloadMode ?? 'hot'
|
|
36
|
+
return NextResponse.json({ ok: true, result: mode })
|
|
37
|
+
} catch (err: unknown) {
|
|
38
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : String(err) }, { status: 502 })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (method === 'gateway.reload-mode.set') {
|
|
43
|
+
const gw = await ensureGatewayConnected()
|
|
44
|
+
if (!gw) return NextResponse.json({ error: 'Not connected' }, { status: 503 })
|
|
45
|
+
try {
|
|
46
|
+
await gw.rpc('config.set', { reloadMode: params?.mode })
|
|
47
|
+
return NextResponse.json({ ok: true })
|
|
48
|
+
} catch (err: unknown) {
|
|
49
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : String(err) }, { status: 502 })
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// General RPC proxy
|
|
54
|
+
const gw = await ensureGatewayConnected()
|
|
55
|
+
if (!gw) {
|
|
56
|
+
return NextResponse.json({ error: 'OpenClaw gateway not connected' }, { status: 503 })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const result = await gw.rpc(method, params)
|
|
61
|
+
return NextResponse.json({ ok: true, result })
|
|
62
|
+
} catch (err: unknown) {
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
64
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** GET — check gateway connection status */
|
|
69
|
+
export async function GET() {
|
|
70
|
+
const gw = getGateway()
|
|
71
|
+
return NextResponse.json({ connected: !!gw?.connected })
|
|
72
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
import { mergeHistoryMessages, isValidSessionKey } from '@/lib/server/openclaw-history-merge'
|
|
4
|
+
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
5
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
6
|
+
import type { GatewaySessionPreview } from '@/types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract a single session preview from the gateway response.
|
|
10
|
+
* The gateway may return:
|
|
11
|
+
* - A map: { [sessionKey]: preview }
|
|
12
|
+
* - An array: [preview, ...]
|
|
13
|
+
* - A single object with sessionKey field
|
|
14
|
+
*/
|
|
15
|
+
function extractPreview(
|
|
16
|
+
raw: unknown,
|
|
17
|
+
sessionKey: string,
|
|
18
|
+
): GatewaySessionPreview | undefined {
|
|
19
|
+
if (!raw || typeof raw !== 'object') return undefined
|
|
20
|
+
|
|
21
|
+
// Direct object with messages array
|
|
22
|
+
if ('messages' in (raw as Record<string, unknown>)) {
|
|
23
|
+
return raw as GatewaySessionPreview
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Map keyed by session key
|
|
27
|
+
const asMap = raw as Record<string, unknown>
|
|
28
|
+
if (asMap[sessionKey] && typeof asMap[sessionKey] === 'object') {
|
|
29
|
+
return asMap[sessionKey] as GatewaySessionPreview
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Array — find matching session
|
|
33
|
+
if (Array.isArray(raw)) {
|
|
34
|
+
return raw.find(
|
|
35
|
+
(p: unknown) =>
|
|
36
|
+
p && typeof p === 'object' && (p as GatewaySessionPreview).sessionKey === sessionKey,
|
|
37
|
+
) as GatewaySessionPreview | undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** GET ?sessionKey=X — preview gateway session history */
|
|
44
|
+
export async function GET(req: Request) {
|
|
45
|
+
const { searchParams } = new URL(req.url)
|
|
46
|
+
const sessionKey = searchParams.get('sessionKey')
|
|
47
|
+
if (!sessionKey || !isValidSessionKey(sessionKey)) {
|
|
48
|
+
return NextResponse.json({ error: 'Missing or invalid sessionKey' }, { status: 400 })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const gw = await ensureGatewayConnected()
|
|
52
|
+
if (!gw) {
|
|
53
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const raw = await gw.rpc('sessions.preview', { keys: [sessionKey], limit: 100 })
|
|
58
|
+
const preview = extractPreview(raw, sessionKey)
|
|
59
|
+
return NextResponse.json(preview ?? { sessionKey, epoch: 0, messages: [] })
|
|
60
|
+
} catch (err: unknown) {
|
|
61
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
62
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** POST { sessionKey, epoch, localSessionId } — merge gateway history into local session */
|
|
67
|
+
export async function POST(req: Request) {
|
|
68
|
+
const body = await req.json()
|
|
69
|
+
const { sessionKey, localSessionId } = body as {
|
|
70
|
+
sessionKey?: string
|
|
71
|
+
epoch?: number
|
|
72
|
+
localSessionId?: string
|
|
73
|
+
}
|
|
74
|
+
if (!sessionKey || !localSessionId) {
|
|
75
|
+
return NextResponse.json({ error: 'Missing sessionKey or localSessionId' }, { status: 400 })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const gw = await ensureGatewayConnected()
|
|
79
|
+
if (!gw) {
|
|
80
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const raw = await gw.rpc('sessions.preview', { keys: [sessionKey], limit: 100 })
|
|
85
|
+
const preview = extractPreview(raw, sessionKey)
|
|
86
|
+
if (!preview?.messages?.length) {
|
|
87
|
+
return NextResponse.json({ ok: true, merged: 0 })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sessions = loadSessions()
|
|
91
|
+
const session = sessions[localSessionId]
|
|
92
|
+
if (!session) {
|
|
93
|
+
return NextResponse.json({ error: 'Local session not found' }, { status: 404 })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const merged = mergeHistoryMessages(session.messages, preview)
|
|
97
|
+
const newCount = merged.length - session.messages.length
|
|
98
|
+
session.messages = merged
|
|
99
|
+
session.lastActiveAt = Date.now()
|
|
100
|
+
sessions[localSessionId] = session
|
|
101
|
+
saveSessions(sessions)
|
|
102
|
+
notify('sessions')
|
|
103
|
+
|
|
104
|
+
return NextResponse.json({ ok: true, merged: newCount })
|
|
105
|
+
} catch (err: unknown) {
|
|
106
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
107
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
import { lookup } from 'mime-types'
|
|
4
|
+
|
|
5
|
+
const MAX_SIZE = 25 * 1024 * 1024 // 25MB
|
|
6
|
+
|
|
7
|
+
/** GET ?path=... — proxy agent files (images etc.) from gateway */
|
|
8
|
+
export async function GET(req: Request) {
|
|
9
|
+
const { searchParams } = new URL(req.url)
|
|
10
|
+
const filePath = searchParams.get('path')
|
|
11
|
+
if (!filePath) {
|
|
12
|
+
return NextResponse.json({ error: 'Missing path' }, { status: 400 })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Security: path must be under ~/.openclaw/
|
|
16
|
+
if (!filePath.includes('.openclaw') && !filePath.includes('.clawdbot')) {
|
|
17
|
+
return NextResponse.json({ error: 'Path not allowed' }, { status: 403 })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const gw = await ensureGatewayConnected()
|
|
21
|
+
if (!gw) {
|
|
22
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const result = await gw.rpc('files.read', { path: filePath }) as { content?: string; encoding?: string } | undefined
|
|
27
|
+
if (!result?.content) {
|
|
28
|
+
return NextResponse.json({ error: 'File not found' }, { status: 404 })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const isBase64 = result.encoding === 'base64'
|
|
32
|
+
const buf = isBase64
|
|
33
|
+
? Buffer.from(result.content, 'base64')
|
|
34
|
+
: Buffer.from(result.content, 'utf-8')
|
|
35
|
+
|
|
36
|
+
if (buf.length > MAX_SIZE) {
|
|
37
|
+
return NextResponse.json({ error: 'File too large' }, { status: 413 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const mimeType = lookup(filePath) || 'application/octet-stream'
|
|
41
|
+
|
|
42
|
+
return new NextResponse(buf, {
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': mimeType,
|
|
45
|
+
'Content-Length': String(buf.length),
|
|
46
|
+
'Cache-Control': 'public, max-age=300',
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
} catch (err: unknown) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
51
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { fetchGatewayModelPolicy, buildAllowedModelKeys } from '@/lib/server/openclaw-models'
|
|
3
|
+
|
|
4
|
+
/** GET — fetch allowed models for OpenClaw agents from gateway policy */
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const policy = await fetchGatewayModelPolicy()
|
|
7
|
+
const models = buildAllowedModelKeys(policy)
|
|
8
|
+
return NextResponse.json({
|
|
9
|
+
models: models ?? ['default'],
|
|
10
|
+
defaultModel: policy?.defaultModel ?? 'default',
|
|
11
|
+
})
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import type { PermissionPreset } from '@/types'
|
|
3
|
+
import { getExecConfig } from '@/lib/server/openclaw-exec-config'
|
|
4
|
+
import { resolvePresetFromConfig, applyPreset } from '@/lib/server/openclaw-permission-presets'
|
|
5
|
+
|
|
6
|
+
/** GET ?agentId=X — resolve current permission preset */
|
|
7
|
+
export async function GET(req: Request) {
|
|
8
|
+
const { searchParams } = new URL(req.url)
|
|
9
|
+
const agentId = searchParams.get('agentId')
|
|
10
|
+
if (!agentId) {
|
|
11
|
+
return NextResponse.json({ error: 'Missing agentId' }, { status: 400 })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const snap = await getExecConfig(agentId)
|
|
16
|
+
const preset = resolvePresetFromConfig(snap.file)
|
|
17
|
+
return NextResponse.json({ preset, config: snap.file })
|
|
18
|
+
} catch (err: unknown) {
|
|
19
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
20
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** PUT { agentId, preset } — apply a permission preset */
|
|
25
|
+
export async function PUT(req: Request) {
|
|
26
|
+
const body = await req.json()
|
|
27
|
+
const { agentId, preset } = body as { agentId?: string; preset?: PermissionPreset }
|
|
28
|
+
if (!agentId || !preset) {
|
|
29
|
+
return NextResponse.json({ error: 'Missing agentId or preset' }, { status: 400 })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await applyPreset(agentId, preset)
|
|
34
|
+
return NextResponse.json({ ok: true })
|
|
35
|
+
} catch (err: unknown) {
|
|
36
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
37
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
|
|
4
|
+
/** GET — list available and allowed env keys for sandbox */
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const gw = await ensureGatewayConnected()
|
|
7
|
+
if (!gw) {
|
|
8
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// Get available keys from dotenv
|
|
13
|
+
const available = await gw.rpc('env.keys') as string[] | undefined
|
|
14
|
+
|
|
15
|
+
// Get current config to find allowed keys
|
|
16
|
+
const config = await gw.rpc('config.get') as Record<string, unknown> | undefined
|
|
17
|
+
const agents = (config as Record<string, unknown>)?.agents as Record<string, unknown> | undefined
|
|
18
|
+
const defaults = agents?.defaults as Record<string, unknown> | undefined
|
|
19
|
+
const sandbox = defaults?.sandbox as Record<string, unknown> | undefined
|
|
20
|
+
const docker = sandbox?.docker as Record<string, unknown> | undefined
|
|
21
|
+
const envList = docker?.env as string[] | undefined
|
|
22
|
+
|
|
23
|
+
// Parse allowed keys from ${KEY} format
|
|
24
|
+
const allowed = (envList ?? [])
|
|
25
|
+
.map((entry) => {
|
|
26
|
+
const m = entry.match(/^\$\{(.+)\}$/)
|
|
27
|
+
return m ? m[1] : entry
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
return NextResponse.json({ available: available ?? [], allowed })
|
|
31
|
+
} catch (err: unknown) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
33
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** PUT { allowed: string[] } — update sandbox env allowlist */
|
|
38
|
+
export async function PUT(req: Request) {
|
|
39
|
+
const body = await req.json()
|
|
40
|
+
const { allowed } = body as { allowed?: string[] }
|
|
41
|
+
if (!allowed || !Array.isArray(allowed)) {
|
|
42
|
+
return NextResponse.json({ error: 'Missing allowed array' }, { status: 400 })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const gw = await ensureGatewayConnected()
|
|
46
|
+
if (!gw) {
|
|
47
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Format as ${KEY} for gateway config
|
|
52
|
+
const envEntries = allowed.map((key) => `\${${key}}`)
|
|
53
|
+
|
|
54
|
+
// Fetch current config hash for conflict detection
|
|
55
|
+
const config = await gw.rpc('config.get') as Record<string, unknown> | undefined
|
|
56
|
+
const configHash = (config as Record<string, unknown>)?._hash as string | undefined
|
|
57
|
+
|
|
58
|
+
await gw.rpc('config.set', {
|
|
59
|
+
key: 'agents.defaults.sandbox.docker.env',
|
|
60
|
+
value: envEntries,
|
|
61
|
+
baseHash: configHash,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return NextResponse.json({ ok: true })
|
|
65
|
+
} catch (err: unknown) {
|
|
66
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
67
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
|
|
4
|
+
/** POST { name, installId, timeoutMs? } — install a skill via gateway */
|
|
5
|
+
export async function POST(req: Request) {
|
|
6
|
+
const body = await req.json()
|
|
7
|
+
const { name, installId, timeoutMs } = body as {
|
|
8
|
+
name?: string
|
|
9
|
+
installId?: string
|
|
10
|
+
timeoutMs?: number
|
|
11
|
+
}
|
|
12
|
+
if (!name) {
|
|
13
|
+
return NextResponse.json({ error: 'Missing skill name' }, { status: 400 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const gw = await ensureGatewayConnected()
|
|
17
|
+
if (!gw) {
|
|
18
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const result = await gw.rpc('skills.install', {
|
|
23
|
+
name,
|
|
24
|
+
installId,
|
|
25
|
+
timeoutMs: timeoutMs ?? 120_000,
|
|
26
|
+
}, (timeoutMs ?? 120_000) + 5_000)
|
|
27
|
+
return NextResponse.json({ ok: true, result })
|
|
28
|
+
} catch (err: unknown) {
|
|
29
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
30
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
|
|
4
|
+
/** POST { skillKey, source } — remove a skill via gateway */
|
|
5
|
+
export async function POST(req: Request) {
|
|
6
|
+
const body = await req.json()
|
|
7
|
+
const { skillKey, source } = body as { skillKey?: string; source?: string }
|
|
8
|
+
if (!skillKey) {
|
|
9
|
+
return NextResponse.json({ error: 'Missing skillKey' }, { status: 400 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const gw = await ensureGatewayConnected()
|
|
13
|
+
if (!gw) {
|
|
14
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
await gw.rpc('skills.remove', { skillKey, source })
|
|
19
|
+
return NextResponse.json({ ok: true })
|
|
20
|
+
} catch (err: unknown) {
|
|
21
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
22
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
import { loadAgents, saveAgents } from '@/lib/server/storage'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
+
import type { OpenClawSkillEntry, SkillAllowlistMode } from '@/types'
|
|
6
|
+
|
|
7
|
+
/** GET ?agentId=X — fetch skills from gateway with eligibility */
|
|
8
|
+
export async function GET(req: Request) {
|
|
9
|
+
const { searchParams } = new URL(req.url)
|
|
10
|
+
const agentId = searchParams.get('agentId')
|
|
11
|
+
if (!agentId) {
|
|
12
|
+
return NextResponse.json({ error: 'Missing agentId' }, { status: 400 })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const gw = await ensureGatewayConnected()
|
|
16
|
+
if (!gw) {
|
|
17
|
+
return NextResponse.json({ error: 'OpenClaw gateway not connected' }, { status: 503 })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const result = await gw.rpc('skills.status', { agentId }) as OpenClawSkillEntry[] | undefined
|
|
22
|
+
return NextResponse.json(result ?? [])
|
|
23
|
+
} catch (err: unknown) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
25
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** PATCH { skillKey, enabled?, apiKey? } — update a skill's config on gateway */
|
|
30
|
+
export async function PATCH(req: Request) {
|
|
31
|
+
const body = await req.json()
|
|
32
|
+
const { skillKey, enabled, apiKey } = body as {
|
|
33
|
+
skillKey?: string
|
|
34
|
+
enabled?: boolean
|
|
35
|
+
apiKey?: string
|
|
36
|
+
}
|
|
37
|
+
if (!skillKey) {
|
|
38
|
+
return NextResponse.json({ error: 'Missing skillKey' }, { status: 400 })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const gw = await ensureGatewayConnected()
|
|
42
|
+
if (!gw) {
|
|
43
|
+
return NextResponse.json({ error: 'Gateway not connected' }, { status: 503 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await gw.rpc('skills.update', { skillKey, enabled, apiKey })
|
|
48
|
+
return NextResponse.json({ ok: true })
|
|
49
|
+
} catch (err: unknown) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
51
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** PUT { agentId, mode, allowedSkills } — save skill allowlist config to agent */
|
|
56
|
+
export async function PUT(req: Request) {
|
|
57
|
+
const body = await req.json()
|
|
58
|
+
const { agentId, mode, allowedSkills } = body as {
|
|
59
|
+
agentId?: string
|
|
60
|
+
mode?: SkillAllowlistMode
|
|
61
|
+
allowedSkills?: string[]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!agentId || !mode) {
|
|
65
|
+
return NextResponse.json({ error: 'Missing agentId or mode' }, { status: 400 })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const agents = loadAgents({ includeTrashed: true })
|
|
69
|
+
const agent = agents[agentId]
|
|
70
|
+
if (!agent) {
|
|
71
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
agent.openclawSkillMode = mode
|
|
75
|
+
agent.openclawAllowedSkills = mode === 'selected' ? (allowedSkills ?? []) : undefined
|
|
76
|
+
agent.updatedAt = Date.now()
|
|
77
|
+
agents[agentId] = agent
|
|
78
|
+
saveAgents(agents)
|
|
79
|
+
notify('agents')
|
|
80
|
+
|
|
81
|
+
return NextResponse.json({ ok: true })
|
|
82
|
+
}
|
|
@@ -24,7 +24,8 @@ export async function POST(req: Request) {
|
|
|
24
24
|
|
|
25
25
|
const results = await runSync({ action, types: validTypes })
|
|
26
26
|
return NextResponse.json({ ok: true, results })
|
|
27
|
-
} catch (err:
|
|
28
|
-
|
|
27
|
+
} catch (err: unknown) {
|
|
28
|
+
const message = err instanceof Error ? err.message : 'Sync failed'
|
|
29
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -31,7 +31,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
31
31
|
if (!deleteItem(ops, id)) return notFound()
|
|
32
32
|
|
|
33
33
|
// Clear projectId from referencing entities
|
|
34
|
-
const clearProjectId = (load: () => Record<string,
|
|
34
|
+
const clearProjectId = (load: () => Record<string, Record<string, unknown>>, save: (d: Record<string, Record<string, unknown>>) => void, topic: string) => {
|
|
35
35
|
const items = load()
|
|
36
36
|
let changed = false
|
|
37
37
|
for (const item of Object.values(items)) {
|
|
@@ -4,7 +4,7 @@ import { loadProjects, saveProjects } from '@/lib/server/storage'
|
|
|
4
4
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
5
|
export const dynamic = 'force-dynamic'
|
|
6
6
|
|
|
7
|
-
export async function GET(
|
|
7
|
+
export async function GET() {
|
|
8
8
|
return NextResponse.json(loadProjects())
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -23,6 +23,6 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
23
23
|
return secret
|
|
24
24
|
})
|
|
25
25
|
if (!result) return notFound()
|
|
26
|
-
const { encryptedValue, ...safe } = result as Record<string,
|
|
26
|
+
const { encryptedValue, ...safe } = result as Record<string, unknown>
|
|
27
27
|
return NextResponse.json(safe)
|
|
28
28
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
|
|
5
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const body = await req.json() as { messageIndex: number; newText: string }
|
|
8
|
+
const sessions = loadSessions()
|
|
9
|
+
const session = sessions[id]
|
|
10
|
+
if (!session) return notFound()
|
|
11
|
+
|
|
12
|
+
const { messageIndex, newText } = body
|
|
13
|
+
if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= session.messages.length) {
|
|
14
|
+
return NextResponse.json({ error: 'Invalid message index' }, { status: 400 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Truncate messages to messageIndex (discard that msg + everything after)
|
|
18
|
+
session.messages = session.messages.slice(0, messageIndex)
|
|
19
|
+
saveSessions(sessions)
|
|
20
|
+
|
|
21
|
+
return NextResponse.json({ message: newText })
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
4
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
|
+
|
|
6
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
|
+
const { id } = await params
|
|
8
|
+
const body = await req.json() as { messageIndex: number }
|
|
9
|
+
const sessions = loadSessions()
|
|
10
|
+
const source = sessions[id]
|
|
11
|
+
if (!source) return notFound()
|
|
12
|
+
|
|
13
|
+
const { messageIndex } = body
|
|
14
|
+
if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= source.messages.length) {
|
|
15
|
+
return NextResponse.json({ error: 'Invalid message index' }, { status: 400 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const now = Date.now()
|
|
19
|
+
const newId = randomUUID()
|
|
20
|
+
const forked = {
|
|
21
|
+
id: newId,
|
|
22
|
+
name: `Fork of ${source.name}`,
|
|
23
|
+
cwd: source.cwd,
|
|
24
|
+
user: source.user,
|
|
25
|
+
provider: source.provider,
|
|
26
|
+
model: source.model,
|
|
27
|
+
credentialId: source.credentialId ?? null,
|
|
28
|
+
fallbackCredentialIds: source.fallbackCredentialIds,
|
|
29
|
+
apiEndpoint: source.apiEndpoint ?? null,
|
|
30
|
+
claudeSessionId: null,
|
|
31
|
+
messages: source.messages.slice(0, messageIndex + 1),
|
|
32
|
+
createdAt: now,
|
|
33
|
+
lastActiveAt: now,
|
|
34
|
+
agentId: source.agentId ?? null,
|
|
35
|
+
parentSessionId: id,
|
|
36
|
+
tools: source.tools,
|
|
37
|
+
conversationTone: source.conversationTone,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
sessions[newId] = forked
|
|
41
|
+
saveSessions(sessions)
|
|
42
|
+
|
|
43
|
+
return NextResponse.json(forked)
|
|
44
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadSessions } from '@/lib/server/storage'
|
|
2
|
+
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
4
|
|
|
5
5
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -8,3 +8,20 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
8
8
|
if (!sessions[id]) return notFound()
|
|
9
9
|
return NextResponse.json(sessions[id].messages)
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
13
|
+
const { id } = await params
|
|
14
|
+
const body = await req.json() as { messageIndex: number; bookmarked: boolean }
|
|
15
|
+
const sessions = loadSessions()
|
|
16
|
+
const session = sessions[id]
|
|
17
|
+
if (!session) return notFound()
|
|
18
|
+
|
|
19
|
+
const { messageIndex, bookmarked } = body
|
|
20
|
+
if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= session.messages.length) {
|
|
21
|
+
return NextResponse.json({ error: 'Invalid message index' }, { status: 400 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
session.messages[messageIndex].bookmarked = bookmarked
|
|
25
|
+
saveSessions(sessions)
|
|
26
|
+
return NextResponse.json(session.messages[messageIndex])
|
|
27
|
+
}
|