@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.
Files changed (203) hide show
  1. package/README.md +82 -39
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +19 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  8. package/src/app/api/clawhub/install/route.ts +2 -2
  9. package/src/app/api/eval/run/route.ts +37 -0
  10. package/src/app/api/eval/scenarios/route.ts +24 -0
  11. package/src/app/api/eval/suite/route.ts +29 -0
  12. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  13. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  14. package/src/app/api/memory/graph/route.ts +46 -0
  15. package/src/app/api/memory/route.ts +36 -5
  16. package/src/app/api/notifications/route.ts +3 -0
  17. package/src/app/api/plugins/install/route.ts +57 -5
  18. package/src/app/api/plugins/marketplace/route.ts +73 -22
  19. package/src/app/api/plugins/route.ts +61 -1
  20. package/src/app/api/plugins/ui/route.ts +34 -0
  21. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  22. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  23. package/src/app/api/settings/route.ts +62 -0
  24. package/src/app/api/setup/doctor/route.ts +22 -5
  25. package/src/app/api/souls/[id]/route.ts +65 -0
  26. package/src/app/api/souls/route.ts +70 -0
  27. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  28. package/src/app/api/tasks/[id]/route.ts +16 -3
  29. package/src/app/api/tasks/route.ts +10 -2
  30. package/src/app/api/usage/route.ts +9 -2
  31. package/src/app/globals.css +27 -0
  32. package/src/app/page.tsx +10 -5
  33. package/src/cli/index.js +37 -0
  34. package/src/components/activity/activity-feed.tsx +9 -2
  35. package/src/components/agents/agent-avatar.tsx +5 -1
  36. package/src/components/agents/agent-card.tsx +55 -9
  37. package/src/components/agents/agent-sheet.tsx +112 -34
  38. package/src/components/agents/inspector-panel.tsx +1 -1
  39. package/src/components/agents/soul-library-picker.tsx +84 -13
  40. package/src/components/auth/access-key-gate.tsx +63 -54
  41. package/src/components/auth/user-picker.tsx +37 -32
  42. package/src/components/chat/activity-moment.tsx +2 -0
  43. package/src/components/chat/chat-area.tsx +11 -0
  44. package/src/components/chat/chat-header.tsx +69 -25
  45. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  46. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  47. package/src/components/chat/code-block.tsx +3 -1
  48. package/src/components/chat/exec-approval-card.tsx +8 -1
  49. package/src/components/chat/message-bubble.tsx +164 -4
  50. package/src/components/chat/message-list.tsx +46 -4
  51. package/src/components/chat/session-approval-card.tsx +80 -0
  52. package/src/components/chat/session-debug-panel.tsx +106 -84
  53. package/src/components/chat/streaming-bubble.tsx +6 -5
  54. package/src/components/chat/task-approval-card.tsx +78 -0
  55. package/src/components/chat/thinking-indicator.tsx +48 -12
  56. package/src/components/chat/tool-call-bubble.tsx +3 -0
  57. package/src/components/chat/tool-request-banner.tsx +39 -20
  58. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  59. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  60. package/src/components/connectors/connector-list.tsx +33 -11
  61. package/src/components/connectors/connector-sheet.tsx +37 -7
  62. package/src/components/home/home-view.tsx +54 -24
  63. package/src/components/input/chat-input.tsx +22 -1
  64. package/src/components/knowledge/knowledge-list.tsx +17 -18
  65. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  66. package/src/components/layout/app-layout.tsx +87 -19
  67. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  68. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  69. package/src/components/memory/memory-browser.tsx +73 -45
  70. package/src/components/memory/memory-graph-view.tsx +203 -0
  71. package/src/components/memory/memory-list.tsx +20 -13
  72. package/src/components/plugins/plugin-list.tsx +214 -60
  73. package/src/components/plugins/plugin-sheet.tsx +119 -24
  74. package/src/components/projects/project-list.tsx +17 -9
  75. package/src/components/providers/provider-list.tsx +21 -6
  76. package/src/components/providers/provider-sheet.tsx +42 -25
  77. package/src/components/runs/run-list.tsx +17 -13
  78. package/src/components/schedules/schedule-card.tsx +10 -3
  79. package/src/components/schedules/schedule-list.tsx +2 -2
  80. package/src/components/schedules/schedule-sheet.tsx +28 -9
  81. package/src/components/secrets/secret-sheet.tsx +7 -2
  82. package/src/components/secrets/secrets-list.tsx +18 -5
  83. package/src/components/sessions/new-session-sheet.tsx +183 -376
  84. package/src/components/sessions/session-card.tsx +10 -2
  85. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  86. package/src/components/shared/command-palette.tsx +13 -5
  87. package/src/components/shared/empty-state.tsx +20 -8
  88. package/src/components/shared/hint-tip.tsx +31 -0
  89. package/src/components/shared/notification-center.tsx +134 -86
  90. package/src/components/shared/profile-sheet.tsx +4 -0
  91. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  92. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  93. package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
  94. package/src/components/skills/clawhub-browser.tsx +1 -0
  95. package/src/components/skills/skill-list.tsx +31 -12
  96. package/src/components/skills/skill-sheet.tsx +20 -7
  97. package/src/components/tasks/approvals-panel.tsx +224 -0
  98. package/src/components/tasks/task-board.tsx +20 -12
  99. package/src/components/tasks/task-card.tsx +21 -7
  100. package/src/components/tasks/task-column.tsx +4 -3
  101. package/src/components/tasks/task-list.tsx +1 -1
  102. package/src/components/tasks/task-sheet.tsx +130 -1
  103. package/src/components/ui/dialog.tsx +1 -0
  104. package/src/components/ui/sheet.tsx +1 -0
  105. package/src/components/usage/metrics-dashboard.tsx +72 -48
  106. package/src/components/wallets/wallet-panel.tsx +65 -41
  107. package/src/components/wallets/wallet-section.tsx +9 -3
  108. package/src/components/webhooks/webhook-list.tsx +21 -12
  109. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  110. package/src/lib/approval-display.test.ts +45 -0
  111. package/src/lib/approval-display.ts +62 -0
  112. package/src/lib/clipboard.ts +38 -0
  113. package/src/lib/memory.ts +8 -0
  114. package/src/lib/providers/claude-cli.ts +5 -3
  115. package/src/lib/providers/index.ts +67 -21
  116. package/src/lib/runtime-loop.ts +3 -2
  117. package/src/lib/server/approvals.ts +150 -0
  118. package/src/lib/server/chat-execution.ts +319 -74
  119. package/src/lib/server/chatroom-helpers.ts +63 -5
  120. package/src/lib/server/chatroom-orchestration.ts +74 -0
  121. package/src/lib/server/clawhub-client.ts +82 -6
  122. package/src/lib/server/connectors/manager.ts +27 -1
  123. package/src/lib/server/context-manager.ts +132 -50
  124. package/src/lib/server/cost.test.ts +73 -0
  125. package/src/lib/server/cost.ts +165 -34
  126. package/src/lib/server/daemon-state.ts +112 -1
  127. package/src/lib/server/data-dir.ts +18 -1
  128. package/src/lib/server/eval/runner.ts +126 -0
  129. package/src/lib/server/eval/scenarios.ts +218 -0
  130. package/src/lib/server/eval/scorer.ts +96 -0
  131. package/src/lib/server/eval/store.ts +37 -0
  132. package/src/lib/server/eval/types.ts +48 -0
  133. package/src/lib/server/execution-log.ts +12 -8
  134. package/src/lib/server/guardian.ts +34 -0
  135. package/src/lib/server/heartbeat-service.ts +53 -1
  136. package/src/lib/server/integrity-monitor.ts +208 -0
  137. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  138. package/src/lib/server/link-understanding.ts +55 -0
  139. package/src/lib/server/llm-response-cache.test.ts +102 -0
  140. package/src/lib/server/llm-response-cache.ts +227 -0
  141. package/src/lib/server/main-agent-loop.ts +115 -16
  142. package/src/lib/server/main-session.ts +6 -3
  143. package/src/lib/server/mcp-conformance.test.ts +18 -0
  144. package/src/lib/server/mcp-conformance.ts +233 -0
  145. package/src/lib/server/memory-db.ts +193 -19
  146. package/src/lib/server/memory-retrieval.test.ts +56 -0
  147. package/src/lib/server/mmr.ts +73 -0
  148. package/src/lib/server/orchestrator-lg.ts +7 -1
  149. package/src/lib/server/orchestrator.ts +4 -3
  150. package/src/lib/server/plugins.ts +662 -132
  151. package/src/lib/server/process-manager.ts +18 -0
  152. package/src/lib/server/query-expansion.ts +57 -0
  153. package/src/lib/server/queue.ts +280 -11
  154. package/src/lib/server/runtime-settings.ts +9 -0
  155. package/src/lib/server/session-run-manager.test.ts +23 -0
  156. package/src/lib/server/session-run-manager.ts +32 -2
  157. package/src/lib/server/session-tools/canvas.ts +85 -50
  158. package/src/lib/server/session-tools/chatroom.ts +130 -127
  159. package/src/lib/server/session-tools/connector.ts +233 -454
  160. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  161. package/src/lib/server/session-tools/crud.ts +84 -7
  162. package/src/lib/server/session-tools/delegate.ts +351 -752
  163. package/src/lib/server/session-tools/discovery.ts +198 -0
  164. package/src/lib/server/session-tools/edit_file.ts +82 -0
  165. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  166. package/src/lib/server/session-tools/file.ts +257 -425
  167. package/src/lib/server/session-tools/git.ts +87 -47
  168. package/src/lib/server/session-tools/http.ts +95 -33
  169. package/src/lib/server/session-tools/index.ts +217 -138
  170. package/src/lib/server/session-tools/memory.ts +154 -239
  171. package/src/lib/server/session-tools/monitor.ts +126 -0
  172. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  173. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  174. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  175. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  176. package/src/lib/server/session-tools/platform.ts +86 -0
  177. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  178. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  179. package/src/lib/server/session-tools/sandbox.ts +175 -148
  180. package/src/lib/server/session-tools/schedule.ts +78 -0
  181. package/src/lib/server/session-tools/session-info.ts +104 -410
  182. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  183. package/src/lib/server/session-tools/shell.ts +171 -143
  184. package/src/lib/server/session-tools/subagent.ts +77 -77
  185. package/src/lib/server/session-tools/wallet.ts +182 -106
  186. package/src/lib/server/session-tools/web.ts +181 -327
  187. package/src/lib/server/storage.ts +36 -0
  188. package/src/lib/server/stream-agent-chat.ts +348 -242
  189. package/src/lib/server/task-quality-gate.test.ts +44 -0
  190. package/src/lib/server/task-quality-gate.ts +67 -0
  191. package/src/lib/server/task-validation.test.ts +78 -0
  192. package/src/lib/server/task-validation.ts +67 -2
  193. package/src/lib/server/tool-aliases.ts +68 -0
  194. package/src/lib/server/tool-capability-policy.ts +24 -5
  195. package/src/lib/server/tool-retry.ts +62 -0
  196. package/src/lib/server/transcript-repair.ts +72 -0
  197. package/src/lib/setup-defaults.ts +1 -0
  198. package/src/lib/tasks.ts +7 -1
  199. package/src/lib/tool-definitions.ts +24 -23
  200. package/src/lib/validation/schemas.ts +13 -0
  201. package/src/lib/view-routes.ts +2 -23
  202. package/src/stores/use-app-store.ts +23 -1
  203. 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 { getMemoryDb, getMemoryLookupLimits, storeMemoryImageAsset, storeMemoryImageFromDataUrl } from '@/lib/server/memory-db'
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(q, agentId || undefined, limits.maxDepth, limits.maxPerLookup, limits.maxLinkedExpansion)
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 entries = db.list(agentId || undefined, limits.maxPerLookup)
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: false,
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(url)
68
+ const res = await fetch(rawUrl, { signal: AbortSignal.timeout(15_000) })
38
69
  if (!res.ok) {
39
- throw new Error(`Download failed: ${res.status}`)
70
+ return NextResponse.json(
71
+ { error: `Download failed (HTTP ${res.status}) from ${rawUrl}` },
72
+ { status: 502 },
73
+ )
40
74
  }
41
- const code = await res.text()
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: 'Failed to install plugin', message: err instanceof Error ? err.message : String(err) },
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
- export const dynamic = 'force-dynamic'
2
+ import { searchClawHub } from '@/lib/server/clawhub-client'
3
3
 
4
+ export const dynamic = 'force-dynamic'
4
5
 
5
- const REGISTRY_URL = 'https://swarmclaw.ai/registry/plugins.json'
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: any; fetchedAt: number } | null = null
12
+ let cache: { data: unknown; fetchedAt: number } | null = null
9
13
 
10
- export async function GET(_req: Request) {
11
- const now = Date.now()
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
- if (cache && now - cache.fetchedAt < CACHE_TTL) {
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
- try {
18
- const res = await fetch(REGISTRY_URL, { cache: 'no-store' })
19
- if (!res.ok) {
20
- throw new Error(`Registry returned ${res.status}`)
21
- }
22
- const data = await res.json()
23
- cache = { data, fetchedAt: now }
24
- return NextResponse.json(data)
25
- } catch (err: any) {
26
- // Return stale cache if available
27
- if (cache) {
28
- return NextResponse.json(cache.data)
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: any) {
41
- return { ok: false, output: '', error: err?.message || String(err) }
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: any) {
79
- return { ok: false, error: err?.message || String(err) }
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) {