@swarmclawai/swarmclaw 0.2.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 (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. package/tsconfig.json +34 -0
@@ -0,0 +1,59 @@
1
+ import { NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import { UPLOAD_DIR } from '@/lib/server/storage'
5
+
6
+ const MIME_TYPES: Record<string, string> = {
7
+ '.png': 'image/png',
8
+ '.jpg': 'image/jpeg',
9
+ '.jpeg': 'image/jpeg',
10
+ '.gif': 'image/gif',
11
+ '.webp': 'image/webp',
12
+ '.svg': 'image/svg+xml',
13
+ '.bmp': 'image/bmp',
14
+ '.ico': 'image/x-icon',
15
+ '.mp4': 'video/mp4',
16
+ '.webm': 'video/webm',
17
+ '.mov': 'video/quicktime',
18
+ '.avi': 'video/x-msvideo',
19
+ '.mkv': 'video/x-matroska',
20
+ '.pdf': 'application/pdf',
21
+ '.json': 'application/json',
22
+ '.csv': 'text/csv',
23
+ '.txt': 'text/plain',
24
+ '.html': 'text/html',
25
+ '.xml': 'application/xml',
26
+ '.zip': 'application/zip',
27
+ '.tar': 'application/x-tar',
28
+ '.gz': 'application/gzip',
29
+ '.doc': 'application/msword',
30
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
31
+ '.xls': 'application/vnd.ms-excel',
32
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
33
+ '.ppt': 'application/vnd.ms-powerpoint',
34
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
35
+ '.mp3': 'audio/mpeg',
36
+ '.wav': 'audio/wav',
37
+ '.ogg': 'audio/ogg',
38
+ }
39
+
40
+ export async function GET(_req: Request, { params }: { params: Promise<{ filename: string }> }) {
41
+ const { filename } = await params
42
+ const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, '')
43
+ const filePath = path.join(UPLOAD_DIR, safeName)
44
+
45
+ if (!fs.existsSync(filePath)) {
46
+ return new NextResponse(null, { status: 404 })
47
+ }
48
+
49
+ const ext = path.extname(safeName).toLowerCase()
50
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream'
51
+ const data = fs.readFileSync(filePath)
52
+
53
+ return new NextResponse(data, {
54
+ headers: {
55
+ 'Content-Type': contentType,
56
+ 'Cache-Control': 'public, max-age=86400',
57
+ },
58
+ })
59
+ }
@@ -0,0 +1,35 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadUsage } from '@/lib/server/storage'
3
+
4
+ export async function GET() {
5
+ const usage = loadUsage()
6
+ // Compute summary
7
+ let totalTokens = 0
8
+ let totalCost = 0
9
+ const bySession: Record<string, { tokens: number; cost: number; count: number }> = {}
10
+ const byProvider: Record<string, { tokens: number; cost: number; count: number }> = {}
11
+
12
+ for (const [sessionId, records] of Object.entries(usage)) {
13
+ for (const r of records) {
14
+ totalTokens += r.totalTokens || 0
15
+ totalCost += r.estimatedCost || 0
16
+ if (!bySession[sessionId]) bySession[sessionId] = { tokens: 0, cost: 0, count: 0 }
17
+ bySession[sessionId].tokens += r.totalTokens || 0
18
+ bySession[sessionId].cost += r.estimatedCost || 0
19
+ bySession[sessionId].count++
20
+ const prov = r.provider || 'unknown'
21
+ if (!byProvider[prov]) byProvider[prov] = { tokens: 0, cost: 0, count: 0 }
22
+ byProvider[prov].tokens += r.totalTokens || 0
23
+ byProvider[prov].cost += r.estimatedCost || 0
24
+ byProvider[prov].count++
25
+ }
26
+ }
27
+
28
+ return NextResponse.json({
29
+ totalTokens,
30
+ totalCost: Math.round(totalCost * 10000) / 10000,
31
+ bySession,
32
+ byProvider,
33
+ raw: usage,
34
+ })
35
+ }
@@ -0,0 +1,81 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { execSync } from 'child_process'
3
+
4
+ let cachedRemote: {
5
+ sha: string
6
+ behindBy: number
7
+ channel: 'stable' | 'main'
8
+ remoteTag: string | null
9
+ checkedAt: number
10
+ } | null = null
11
+ const CACHE_TTL = 60_000 // 60s
12
+ const RELEASE_TAG_RE = /^v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/
13
+
14
+ function run(cmd: string): string {
15
+ return execSync(cmd, { encoding: 'utf-8', cwd: process.cwd(), timeout: 15_000 }).trim()
16
+ }
17
+
18
+ function getLatestStableTag(): string | null {
19
+ const tags = run(`git tag --list 'v*' --sort=-v:refname`)
20
+ .split('\n')
21
+ .map((line) => line.trim())
22
+ .filter(Boolean)
23
+ return tags.find((tag) => RELEASE_TAG_RE.test(tag)) || null
24
+ }
25
+
26
+ function getHeadStableTag(): string | null {
27
+ const tags = run(`git tag --points-at HEAD --list 'v*' --sort=-v:refname`)
28
+ .split('\n')
29
+ .map((line) => line.trim())
30
+ .filter(Boolean)
31
+ return tags.find((tag) => RELEASE_TAG_RE.test(tag)) || null
32
+ }
33
+
34
+ export async function GET() {
35
+ try {
36
+ const localSha = run('git rev-parse --short HEAD')
37
+ const localTag = getHeadStableTag()
38
+
39
+ let remoteSha = cachedRemote?.sha ?? localSha
40
+ let behindBy = cachedRemote?.behindBy ?? 0
41
+ let channel: 'stable' | 'main' = cachedRemote?.channel ?? 'main'
42
+ let remoteTag = cachedRemote?.remoteTag ?? null
43
+
44
+ if (!cachedRemote || Date.now() - cachedRemote.checkedAt > CACHE_TTL) {
45
+ try {
46
+ run('git fetch --tags origin --quiet')
47
+ const latestTag = getLatestStableTag()
48
+ if (latestTag) {
49
+ channel = 'stable'
50
+ remoteTag = latestTag
51
+ remoteSha = run(`git rev-parse --short ${latestTag}^{commit}`)
52
+ behindBy = parseInt(run(`git rev-list HEAD..${latestTag}^{commit} --count`), 10) || 0
53
+ } else {
54
+ // Fallback for repos without release tags yet.
55
+ channel = 'main'
56
+ remoteTag = null
57
+ run('git fetch origin main --quiet')
58
+ behindBy = parseInt(run('git rev-list HEAD..origin/main --count'), 10) || 0
59
+ remoteSha = behindBy > 0
60
+ ? run('git rev-parse --short origin/main')
61
+ : localSha
62
+ }
63
+ cachedRemote = { sha: remoteSha, behindBy, channel, remoteTag, checkedAt: Date.now() }
64
+ } catch {
65
+ // fetch failed (no network, no remote, etc.) — use stale cache or defaults
66
+ }
67
+ }
68
+
69
+ return NextResponse.json({
70
+ localSha,
71
+ localTag,
72
+ remoteSha,
73
+ remoteTag,
74
+ channel,
75
+ updateAvailable: behindBy > 0,
76
+ behindBy,
77
+ })
78
+ } catch {
79
+ return NextResponse.json({ error: 'Not a git repository' }, { status: 500 })
80
+ }
81
+ }
@@ -0,0 +1,95 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { execSync } from 'child_process'
3
+
4
+ const RELEASE_TAG_RE = /^v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/
5
+
6
+ function run(cmd: string): string {
7
+ return execSync(cmd, { encoding: 'utf-8', cwd: process.cwd(), timeout: 60_000 }).trim()
8
+ }
9
+
10
+ function getLatestStableTag(): string | null {
11
+ const tags = run(`git tag --list 'v*' --sort=-v:refname`)
12
+ .split('\n')
13
+ .map((line) => line.trim())
14
+ .filter(Boolean)
15
+ return tags.find((tag) => RELEASE_TAG_RE.test(tag)) || null
16
+ }
17
+
18
+ function ensureCleanWorkingTree() {
19
+ const dirty = run('git status --porcelain')
20
+ if (dirty) {
21
+ throw new Error('Local changes detected. Commit/stash them first, then retry update.')
22
+ }
23
+ }
24
+
25
+ export async function POST() {
26
+ try {
27
+ const beforeSha = run('git rev-parse --short HEAD')
28
+ const beforeRef = run('git rev-parse HEAD')
29
+ const currentBranch = run('git rev-parse --abbrev-ref HEAD')
30
+ let pullOutput = ''
31
+ let channel: 'stable' | 'main' = 'main'
32
+ let targetTag: string | null = null
33
+ let switchedToStable = false
34
+
35
+ // Prefer latest stable release tags when available.
36
+ try {
37
+ run('git fetch --tags origin --quiet')
38
+ const latestTag = getLatestStableTag()
39
+ if (latestTag) {
40
+ channel = 'stable'
41
+ targetTag = latestTag
42
+ const targetRef = `${latestTag}^{commit}`
43
+ const targetSha = run(`git rev-parse ${targetRef}`)
44
+ if (targetSha !== beforeRef) {
45
+ ensureCleanWorkingTree()
46
+ // Keep end-user installs on a predictable "stable" branch that tracks release tags.
47
+ run(`git checkout -B stable ${targetRef}`)
48
+ switchedToStable = currentBranch !== 'stable'
49
+ pullOutput = `Updated to stable release ${latestTag}.`
50
+ } else {
51
+ pullOutput = `Already on latest stable release ${latestTag}.`
52
+ }
53
+ }
54
+ } catch {
55
+ // If stable-tag flow fails, fallback to main.
56
+ channel = 'main'
57
+ targetTag = null
58
+ }
59
+
60
+ if (channel === 'main') {
61
+ // Fallback for repos without release tags.
62
+ pullOutput = run('git pull --ff-only origin main')
63
+ }
64
+
65
+ // Check if package-lock.json changed in the pull
66
+ let installedDeps = false
67
+ try {
68
+ const diff = run(`git diff --name-only ${beforeSha}..HEAD`)
69
+ if (diff.includes('package-lock.json') || diff.includes('package.json')) {
70
+ run('npm install --omit=dev')
71
+ installedDeps = true
72
+ }
73
+ } catch {
74
+ // If diff fails (e.g. first commit), skip install check
75
+ }
76
+
77
+ const newSha = run('git rev-parse --short HEAD')
78
+
79
+ return NextResponse.json({
80
+ success: true,
81
+ newSha,
82
+ pullOutput,
83
+ installedDeps,
84
+ channel,
85
+ targetTag,
86
+ switchedToStable,
87
+ needsRestart: true,
88
+ })
89
+ } catch (e) {
90
+ return NextResponse.json(
91
+ { success: false, error: e instanceof Error ? e.message : 'Update failed' },
92
+ { status: 500 }
93
+ )
94
+ }
95
+ }
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadWebhookLogs } from '@/lib/server/storage'
3
+
4
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id } = await params
6
+ const allLogs = loadWebhookLogs()
7
+ const entries = Object.values(allLogs)
8
+ .filter((entry: any) => entry.webhookId === id)
9
+ .sort((a: any, b: any) => (b.timestamp || 0) - (a.timestamp || 0))
10
+ .slice(0, 100)
11
+
12
+ return NextResponse.json(entries)
13
+ }
@@ -0,0 +1,204 @@
1
+ import crypto from 'crypto'
2
+ import { NextResponse } from 'next/server'
3
+ import { loadAgents, loadSessions, loadWebhooks, saveSessions, saveWebhooks, appendWebhookLog } from '@/lib/server/storage'
4
+ import { enqueueSessionRun } from '@/lib/server/session-run-manager'
5
+
6
+ function normalizeEvents(value: unknown): string[] {
7
+ if (!Array.isArray(value)) return []
8
+ return value
9
+ .filter((v): v is string => typeof v === 'string')
10
+ .map((v) => v.trim())
11
+ .filter(Boolean)
12
+ }
13
+
14
+ function eventMatches(registered: string[], incoming: string): boolean {
15
+ if (registered.length === 0) return true
16
+ if (registered.includes('*')) return true
17
+ return registered.includes(incoming)
18
+ }
19
+
20
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
21
+ const { id } = await params
22
+ const webhooks = loadWebhooks()
23
+ const webhook = webhooks[id]
24
+ if (!webhook) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
25
+ return NextResponse.json(webhook)
26
+ }
27
+
28
+ export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
29
+ const { id } = await params
30
+ const body = await req.json().catch(() => ({}))
31
+ const webhooks = loadWebhooks()
32
+ const webhook = webhooks[id]
33
+ if (!webhook) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
34
+
35
+ if (body.name !== undefined) webhook.name = body.name
36
+ if (body.source !== undefined) webhook.source = body.source
37
+ if (body.events !== undefined) webhook.events = normalizeEvents(body.events)
38
+ if (body.agentId !== undefined) webhook.agentId = body.agentId
39
+ if (body.secret !== undefined) webhook.secret = body.secret
40
+ if (body.isEnabled !== undefined) webhook.isEnabled = !!body.isEnabled
41
+ webhook.updatedAt = Date.now()
42
+
43
+ webhooks[id] = webhook
44
+ saveWebhooks(webhooks)
45
+ return NextResponse.json(webhook)
46
+ }
47
+
48
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
49
+ const { id } = await params
50
+ const webhooks = loadWebhooks()
51
+ if (!webhooks[id]) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
52
+ delete webhooks[id]
53
+ saveWebhooks(webhooks)
54
+ return NextResponse.json({ ok: true })
55
+ }
56
+
57
+ export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
58
+ const { id } = await params
59
+ const webhooks = loadWebhooks()
60
+ const webhook = webhooks[id]
61
+ if (!webhook) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
62
+ if (webhook.isEnabled === false) {
63
+ appendWebhookLog(crypto.randomBytes(8).toString('hex'), {
64
+ id: crypto.randomBytes(8).toString('hex'), webhookId: id, event: 'unknown',
65
+ payload: '', status: 'error', error: 'Webhook is disabled', timestamp: Date.now(),
66
+ })
67
+ return NextResponse.json({ error: 'Webhook is disabled' }, { status: 409 })
68
+ }
69
+
70
+ const secret = typeof webhook.secret === 'string' ? webhook.secret.trim() : ''
71
+ if (secret) {
72
+ const url = new URL(req.url)
73
+ const provided = req.headers.get('x-webhook-secret') || url.searchParams.get('secret') || ''
74
+ if (provided !== secret) {
75
+ appendWebhookLog(crypto.randomBytes(8).toString('hex'), {
76
+ id: crypto.randomBytes(8).toString('hex'), webhookId: id, event: 'unknown',
77
+ payload: '', status: 'error', error: 'Invalid webhook secret', timestamp: Date.now(),
78
+ })
79
+ return NextResponse.json({ error: 'Invalid webhook secret' }, { status: 401 })
80
+ }
81
+ }
82
+
83
+ let payload: unknown = null
84
+ let rawBody = ''
85
+ const contentType = req.headers.get('content-type') || ''
86
+ if (contentType.includes('application/json')) {
87
+ try {
88
+ payload = await req.json()
89
+ rawBody = JSON.stringify(payload)
90
+ } catch {
91
+ payload = {}
92
+ rawBody = '{}'
93
+ }
94
+ } else {
95
+ rawBody = await req.text()
96
+ try {
97
+ payload = JSON.parse(rawBody)
98
+ } catch {
99
+ payload = { raw: rawBody }
100
+ }
101
+ }
102
+
103
+ const url = new URL(req.url)
104
+ const incomingEvent = String(
105
+ (payload as Record<string, unknown> | null)?.type
106
+ || (payload as Record<string, unknown> | null)?.event
107
+ || req.headers.get('x-event-type')
108
+ || url.searchParams.get('event')
109
+ || 'unknown',
110
+ )
111
+ const registeredEvents = normalizeEvents(webhook.events)
112
+ if (!eventMatches(registeredEvents, incomingEvent)) {
113
+ return NextResponse.json({
114
+ ok: true,
115
+ ignored: true,
116
+ reason: 'Event does not match webhook filters',
117
+ event: incomingEvent,
118
+ })
119
+ }
120
+
121
+ const agents = loadAgents()
122
+ const agent = webhook.agentId ? agents[webhook.agentId] : null
123
+ if (!agent) {
124
+ appendWebhookLog(crypto.randomBytes(8).toString('hex'), {
125
+ id: crypto.randomBytes(8).toString('hex'), webhookId: id, event: incomingEvent,
126
+ payload: (rawBody || '').slice(0, 2000), status: 'error', error: 'Webhook agent is not configured or missing', timestamp: Date.now(),
127
+ })
128
+ return NextResponse.json({ error: 'Webhook agent is not configured or missing' }, { status: 400 })
129
+ }
130
+
131
+ const sessions = loadSessions()
132
+ const sessionName = `webhook:${id}`
133
+ let session = Object.values(sessions).find((s: any) => s.name === sessionName && s.agentId === agent.id) as any
134
+ if (!session) {
135
+ const sessionId = crypto.randomBytes(4).toString('hex')
136
+ const now = Date.now()
137
+ session = {
138
+ id: sessionId,
139
+ name: sessionName,
140
+ cwd: process.cwd(),
141
+ user: 'system',
142
+ provider: agent.provider || 'claude-cli',
143
+ model: agent.model || '',
144
+ credentialId: agent.credentialId || null,
145
+ apiEndpoint: agent.apiEndpoint || null,
146
+ claudeSessionId: null,
147
+ codexThreadId: null,
148
+ opencodeSessionId: null,
149
+ delegateResumeIds: {
150
+ claudeCode: null,
151
+ codex: null,
152
+ opencode: null,
153
+ },
154
+ messages: [],
155
+ createdAt: now,
156
+ lastActiveAt: now,
157
+ sessionType: 'orchestrated',
158
+ agentId: agent.id,
159
+ parentSessionId: null,
160
+ tools: agent.tools || [],
161
+ heartbeatEnabled: agent.heartbeatEnabled ?? true,
162
+ heartbeatIntervalSec: agent.heartbeatIntervalSec ?? null,
163
+ }
164
+ sessions[session.id] = session
165
+ saveSessions(sessions)
166
+ }
167
+
168
+ const payloadPreview = (rawBody || '').slice(0, 12_000)
169
+ const prompt = [
170
+ 'Webhook event received.',
171
+ `Webhook ID: ${id}`,
172
+ `Webhook Name: ${webhook.name || id}`,
173
+ `Source: ${webhook.source || 'custom'}`,
174
+ `Event: ${incomingEvent}`,
175
+ `Received At: ${new Date().toISOString()}`,
176
+ '',
177
+ 'Payload:',
178
+ payloadPreview || '(empty payload)',
179
+ '',
180
+ 'Handle this event now. If this requires notifying the user, use configured connector tools.',
181
+ ].join('\n')
182
+
183
+ const run = enqueueSessionRun({
184
+ sessionId: session.id,
185
+ message: prompt,
186
+ source: 'webhook',
187
+ internal: false,
188
+ mode: 'followup',
189
+ })
190
+
191
+ appendWebhookLog(crypto.randomBytes(8).toString('hex'), {
192
+ id: crypto.randomBytes(8).toString('hex'), webhookId: id, event: incomingEvent,
193
+ payload: (rawBody || '').slice(0, 2000), status: 'success',
194
+ sessionId: session.id, runId: run.runId, timestamp: Date.now(),
195
+ })
196
+
197
+ return NextResponse.json({
198
+ ok: true,
199
+ webhookId: id,
200
+ event: incomingEvent,
201
+ sessionId: session.id,
202
+ runId: run.runId,
203
+ })
204
+ }
@@ -0,0 +1,37 @@
1
+ import crypto from 'crypto'
2
+ import { NextResponse } from 'next/server'
3
+ import { loadWebhooks, saveWebhooks } from '@/lib/server/storage'
4
+
5
+ function normalizeEvents(value: unknown): string[] {
6
+ if (!Array.isArray(value)) return []
7
+ return value
8
+ .filter((v): v is string => typeof v === 'string')
9
+ .map((v) => v.trim())
10
+ .filter(Boolean)
11
+ }
12
+
13
+ export async function GET() {
14
+ return NextResponse.json(loadWebhooks())
15
+ }
16
+
17
+ export async function POST(req: Request) {
18
+ const body = await req.json().catch(() => ({}))
19
+ const webhooks = loadWebhooks()
20
+ const id = crypto.randomBytes(4).toString('hex')
21
+ const now = Date.now()
22
+
23
+ webhooks[id] = {
24
+ id,
25
+ name: typeof body.name === 'string' ? body.name : 'Unnamed Webhook',
26
+ source: typeof body.source === 'string' ? body.source : 'custom',
27
+ events: normalizeEvents(body.events),
28
+ agentId: typeof body.agentId === 'string' ? body.agentId : null,
29
+ secret: typeof body.secret === 'string' ? body.secret : '',
30
+ isEnabled: body.isEnabled !== false,
31
+ createdAt: now,
32
+ updatedAt: now,
33
+ }
34
+
35
+ saveWebhooks(webhooks)
36
+ return NextResponse.json(webhooks[id])
37
+ }
Binary file