@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.
- package/README.md +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- 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
|