@swarmclawai/swarmclaw 0.6.7 → 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 +82 -39
- 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 +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- 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/graph/route.ts +46 -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/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -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 +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- 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/checkpoint-timeline.tsx +112 -0
- 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 +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- 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 +37 -7
- package/src/components/home/home-view.tsx +54 -24
- 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 +87 -19
- 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-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- 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 +28 -9
- 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/hint-tip.tsx +31 -0
- 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 +149 -4
- 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 +224 -0
- 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 +72 -48
- 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 +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -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 +115 -16
- 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 +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -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 +32 -2
- 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 +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- 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 +78 -0
- 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 +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- 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 +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import { NextResponse } from 'next/server'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
filterMemoriesByScope,
|
|
6
|
+
getMemoryDb,
|
|
7
|
+
getMemoryLookupLimits,
|
|
8
|
+
normalizeMemoryScopeMode,
|
|
9
|
+
storeMemoryImageAsset,
|
|
10
|
+
storeMemoryImageFromDataUrl,
|
|
11
|
+
type MemoryRerankMode,
|
|
12
|
+
type MemoryScopeFilter,
|
|
13
|
+
} from '@/lib/server/memory-db'
|
|
5
14
|
import { resolveLookupRequest } from '@/lib/server/memory-graph'
|
|
6
15
|
import type { MemoryReference, FileReference, MemoryImage } from '@/types'
|
|
7
16
|
|
|
@@ -21,10 +30,16 @@ export async function GET(req: Request) {
|
|
|
21
30
|
const { searchParams } = new URL(req.url)
|
|
22
31
|
const q = searchParams.get('q')
|
|
23
32
|
const agentId = searchParams.get('agentId')
|
|
33
|
+
const rawScope = searchParams.get('scope')
|
|
24
34
|
const envelope = searchParams.get('envelope') === 'true'
|
|
25
35
|
const requestedDepth = parseOptionalInt(searchParams.get('depth'))
|
|
26
36
|
const requestedLimit = parseOptionalInt(searchParams.get('limit'))
|
|
27
37
|
const requestedLinkedLimit = parseOptionalInt(searchParams.get('linkedLimit'))
|
|
38
|
+
const scopeMode = normalizeMemoryScopeMode(rawScope ?? (agentId ? 'agent' : 'all'))
|
|
39
|
+
const scopeSessionId = searchParams.get('scopeSessionId')
|
|
40
|
+
const scopeProjectRoot = searchParams.get('projectRoot')
|
|
41
|
+
const rerankRaw = searchParams.get('rerank')
|
|
42
|
+
const rerankMode: MemoryRerankMode = rerankRaw === 'semantic' || rerankRaw === 'lexical' ? rerankRaw : 'balanced'
|
|
28
43
|
|
|
29
44
|
const counts = searchParams.get('counts') === 'true'
|
|
30
45
|
const db = getMemoryDb()
|
|
@@ -39,14 +54,27 @@ export async function GET(req: Request) {
|
|
|
39
54
|
limit: requestedLimit,
|
|
40
55
|
linkedLimit: requestedLinkedLimit,
|
|
41
56
|
})
|
|
57
|
+
const scopeFilter: MemoryScopeFilter = {
|
|
58
|
+
mode: scopeMode,
|
|
59
|
+
agentId: agentId || null,
|
|
60
|
+
sessionId: scopeSessionId || null,
|
|
61
|
+
projectRoot: scopeProjectRoot || null,
|
|
62
|
+
}
|
|
42
63
|
|
|
43
64
|
if (q) {
|
|
44
65
|
if (limits.maxDepth > 0) {
|
|
45
|
-
const result = db.searchWithLinked(
|
|
66
|
+
const result = db.searchWithLinked(
|
|
67
|
+
q,
|
|
68
|
+
agentId || undefined,
|
|
69
|
+
limits.maxDepth,
|
|
70
|
+
limits.maxPerLookup,
|
|
71
|
+
limits.maxLinkedExpansion,
|
|
72
|
+
{ scope: scopeFilter, rerankMode },
|
|
73
|
+
)
|
|
46
74
|
if (envelope) return NextResponse.json(result)
|
|
47
75
|
return NextResponse.json(result.entries)
|
|
48
76
|
}
|
|
49
|
-
const base = db.search(q, agentId || undefined)
|
|
77
|
+
const base = db.search(q, agentId || undefined, { scope: scopeFilter, rerankMode })
|
|
50
78
|
const entries = base.slice(0, limits.maxPerLookup)
|
|
51
79
|
if (envelope) {
|
|
52
80
|
return NextResponse.json({
|
|
@@ -59,11 +87,14 @@ export async function GET(req: Request) {
|
|
|
59
87
|
return NextResponse.json(entries)
|
|
60
88
|
}
|
|
61
89
|
|
|
62
|
-
const
|
|
90
|
+
const scanLimit = Math.max(limits.maxPerLookup, 200)
|
|
91
|
+
const listed = db.list(undefined, scanLimit)
|
|
92
|
+
const filtered = filterMemoriesByScope(listed, scopeFilter)
|
|
93
|
+
const entries = filtered.slice(0, limits.maxPerLookup)
|
|
63
94
|
if (envelope) {
|
|
64
95
|
return NextResponse.json({
|
|
65
96
|
entries,
|
|
66
|
-
truncated:
|
|
97
|
+
truncated: filtered.length > entries.length,
|
|
67
98
|
expandedLinkedCount: 0,
|
|
68
99
|
limits,
|
|
69
100
|
})
|
|
@@ -13,6 +13,9 @@ export async function GET(req: Request) {
|
|
|
13
13
|
const all = loadNotifications()
|
|
14
14
|
let entries = Object.values(all) as AppNotification[]
|
|
15
15
|
|
|
16
|
+
// Approval requests now have a dedicated Approvals view/badge; keep notifications focused on ops/events.
|
|
17
|
+
entries = entries.filter((e) => e.entityType !== 'approval')
|
|
18
|
+
|
|
16
19
|
if (unreadOnly) {
|
|
17
20
|
entries = entries.filter((e) => !e.read)
|
|
18
21
|
}
|
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import path from 'path'
|
|
4
|
+
import { getPluginManager } from '@/lib/server/plugins'
|
|
4
5
|
|
|
5
6
|
const PLUGINS_DIR = path.join(process.cwd(), 'data', 'plugins')
|
|
6
7
|
|
|
8
|
+
function toRawUrl(url: string): string {
|
|
9
|
+
if (url.includes('github.com') && url.includes('/blob/')) {
|
|
10
|
+
return url.replace('github.com', 'raw.githubusercontent.com').replace('/blob/', '/')
|
|
11
|
+
}
|
|
12
|
+
if (url.includes('gist.github.com')) {
|
|
13
|
+
return url.endsWith('/raw') ? url : `${url}/raw`
|
|
14
|
+
}
|
|
15
|
+
return url
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeMarketplaceUrl(url: string): string {
|
|
19
|
+
const trimmed = typeof url === 'string' ? url.trim() : ''
|
|
20
|
+
if (!trimmed) return trimmed
|
|
21
|
+
|
|
22
|
+
let normalized = trimmed
|
|
23
|
+
.replace('github.com/swarmclawai/plugins/', 'github.com/swarmclawai/swarmforge/')
|
|
24
|
+
.replace('raw.githubusercontent.com/swarmclawai/plugins/', 'raw.githubusercontent.com/swarmclawai/swarmforge/')
|
|
25
|
+
|
|
26
|
+
normalized = toRawUrl(normalized)
|
|
27
|
+
|
|
28
|
+
// Legacy registry entries used master and old repo names.
|
|
29
|
+
normalized = normalized
|
|
30
|
+
.replace('/swarmclawai/swarmforge/master/', '/swarmclawai/swarmforge/main/')
|
|
31
|
+
.replace('/swarmclawai/plugins/master/', '/swarmclawai/swarmforge/main/')
|
|
32
|
+
.replace('/swarmclawai/plugins/main/', '/swarmclawai/swarmforge/main/')
|
|
33
|
+
|
|
34
|
+
return normalized
|
|
35
|
+
}
|
|
36
|
+
|
|
7
37
|
export async function POST(req: Request) {
|
|
8
38
|
const body = await req.json()
|
|
9
39
|
const { url, filename } = body
|
|
40
|
+
const rawUrl = normalizeMarketplaceUrl(url)
|
|
10
41
|
|
|
11
42
|
// Validate URL
|
|
12
43
|
if (!url || typeof url !== 'string' || !url.startsWith('https://')) {
|
|
@@ -34,11 +65,27 @@ export async function POST(req: Request) {
|
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
try {
|
|
37
|
-
const res = await fetch(
|
|
68
|
+
const res = await fetch(rawUrl, { signal: AbortSignal.timeout(15_000) })
|
|
38
69
|
if (!res.ok) {
|
|
39
|
-
|
|
70
|
+
return NextResponse.json(
|
|
71
|
+
{ error: `Download failed (HTTP ${res.status}) from ${rawUrl}` },
|
|
72
|
+
{ status: 502 },
|
|
73
|
+
)
|
|
40
74
|
}
|
|
41
|
-
const
|
|
75
|
+
const contentType = res.headers.get('content-type') || ''
|
|
76
|
+
let code = await res.text()
|
|
77
|
+
|
|
78
|
+
// Reject HTML responses (likely a GitHub page, not raw content)
|
|
79
|
+
if (contentType.includes('text/html') && code.includes('<!DOCTYPE')) {
|
|
80
|
+
return NextResponse.json(
|
|
81
|
+
{ error: 'URL returned an HTML page instead of JavaScript. Use a raw/direct link to the .js file.' },
|
|
82
|
+
{ status: 400 },
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Compatibility fix: Strip node-fetch requires if present, as modern Node has global fetch
|
|
87
|
+
code = code.replace(/const\s+fetch\s*=\s*require\(['"]node-fetch['"]\);?/g, '// node-fetch stripped for compatibility')
|
|
88
|
+
code = code.replace(/import\s+fetch\s+from\s+['"]node-fetch['"];?/g, '// node-fetch stripped for compatibility')
|
|
42
89
|
|
|
43
90
|
// Ensure plugins directory exists
|
|
44
91
|
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
@@ -48,11 +95,16 @@ export async function POST(req: Request) {
|
|
|
48
95
|
const dest = path.join(PLUGINS_DIR, sanitized)
|
|
49
96
|
fs.writeFileSync(dest, code, 'utf8')
|
|
50
97
|
|
|
98
|
+
// Force plugin manager to re-scan so the new plugin appears in listings
|
|
99
|
+
getPluginManager().reload()
|
|
100
|
+
|
|
51
101
|
return NextResponse.json({ ok: true, filename: sanitized })
|
|
52
102
|
} catch (err: unknown) {
|
|
103
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
104
|
+
const isTimeout = msg.includes('abort') || msg.includes('timeout')
|
|
53
105
|
return NextResponse.json(
|
|
54
|
-
{ error: '
|
|
55
|
-
{ status: 500 },
|
|
106
|
+
{ error: isTimeout ? 'Download timed out — the plugin URL may be unreachable' : `Install failed: ${msg}` },
|
|
107
|
+
{ status: isTimeout ? 504 : 500 },
|
|
56
108
|
)
|
|
57
109
|
}
|
|
58
110
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getCheckpointSaver } from '@/lib/server/langgraph-checkpoint'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
/** GET /api/sessions/[id]/checkpoints — returns checkpoint history for a thread */
|
|
7
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id: threadId } = await params
|
|
9
|
+
if (!threadId) return NextResponse.json({ error: 'Thread ID is required' }, { status: 400 })
|
|
10
|
+
|
|
11
|
+
const saver = getCheckpointSaver()
|
|
12
|
+
const checkpoints = []
|
|
13
|
+
|
|
14
|
+
// LangGraph's list() is an async generator
|
|
15
|
+
const iterator = saver.list({ configurable: { thread_id: threadId } })
|
|
16
|
+
|
|
17
|
+
for await (const tuple of iterator) {
|
|
18
|
+
checkpoints.push({
|
|
19
|
+
checkpointId: tuple.config.configurable?.checkpoint_id,
|
|
20
|
+
parentCheckpointId: tuple.parentConfig?.configurable?.checkpoint_id,
|
|
21
|
+
metadata: tuple.metadata,
|
|
22
|
+
createdAt: new Date(tuple.checkpoint.ts).getTime(),
|
|
23
|
+
values: tuple.checkpoint.channel_values,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Sort by created_at descending (saver.list usually does this but we want to be sure)
|
|
28
|
+
checkpoints.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0))
|
|
29
|
+
|
|
30
|
+
return NextResponse.json(checkpoints)
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getCheckpointSaver } from '@/lib/server/langgraph-checkpoint'
|
|
3
|
+
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
/** POST /api/sessions/[id]/restore — restores thread to a specific checkpoint */
|
|
9
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
|
+
const { id: sessionId } = await params
|
|
11
|
+
const { checkpointId, timestamp } = await req.json()
|
|
12
|
+
|
|
13
|
+
if (!checkpointId || !timestamp) {
|
|
14
|
+
return NextResponse.json({ error: 'checkpointId and timestamp are required' }, { status: 400 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const saver = getCheckpointSaver()
|
|
18
|
+
|
|
19
|
+
// 1. Delete all checkpoints after the target one
|
|
20
|
+
await saver.deleteCheckpointsAfter(sessionId, timestamp)
|
|
21
|
+
|
|
22
|
+
// 2. Truncate messages in the session to match the timestamp
|
|
23
|
+
// Both timestamp (from checkpoint.ts → getTime()) and Message.time use epoch milliseconds
|
|
24
|
+
const sessions = loadSessions()
|
|
25
|
+
const session = sessions[sessionId]
|
|
26
|
+
if (session) {
|
|
27
|
+
session.messages = session.messages.filter((m: { time: number }) => m.time <= timestamp)
|
|
28
|
+
session.lastActiveAt = Date.now()
|
|
29
|
+
saveSessions(sessions)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
notify(`messages:${sessionId}`)
|
|
33
|
+
notify('sessions')
|
|
34
|
+
|
|
35
|
+
return NextResponse.json({ ok: true, restoredTo: checkpointId })
|
|
36
|
+
}
|
|
@@ -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) {
|