@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,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadSchedules, saveSchedules, deleteSchedule } from '@/lib/server/storage'
|
|
3
|
+
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
4
|
+
|
|
5
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const body = await req.json()
|
|
8
|
+
const schedules = loadSchedules()
|
|
9
|
+
if (!schedules[id]) return new NextResponse(null, { status: 404 })
|
|
10
|
+
|
|
11
|
+
const origId = id
|
|
12
|
+
Object.assign(schedules[id], body)
|
|
13
|
+
schedules[id].id = origId
|
|
14
|
+
schedules[id].name = resolveScheduleName({
|
|
15
|
+
name: schedules[id].name,
|
|
16
|
+
taskPrompt: schedules[id].taskPrompt,
|
|
17
|
+
})
|
|
18
|
+
saveSchedules(schedules)
|
|
19
|
+
return NextResponse.json(schedules[id])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
23
|
+
const { id } = await params
|
|
24
|
+
const schedules = loadSchedules()
|
|
25
|
+
if (!schedules[id]) return new NextResponse(null, { status: 404 })
|
|
26
|
+
deleteSchedule(id)
|
|
27
|
+
return NextResponse.json('ok')
|
|
28
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { loadSchedules, saveSchedules, loadAgents, loadTasks, saveTasks } from '@/lib/server/storage'
|
|
4
|
+
import { enqueueTask } from '@/lib/server/queue'
|
|
5
|
+
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
6
|
+
import { getScheduleSignatureKey } from '@/lib/schedule-dedupe'
|
|
7
|
+
|
|
8
|
+
type InFlightTask = {
|
|
9
|
+
status?: string
|
|
10
|
+
sourceScheduleKey?: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
14
|
+
const { id } = await params
|
|
15
|
+
const schedules = loadSchedules()
|
|
16
|
+
const schedule = schedules[id]
|
|
17
|
+
if (!schedule) return new NextResponse(null, { status: 404 })
|
|
18
|
+
|
|
19
|
+
const agents = loadAgents()
|
|
20
|
+
const agent = agents[schedule.agentId]
|
|
21
|
+
if (!agent) return NextResponse.json({ error: 'Agent not found' }, { status: 400 })
|
|
22
|
+
|
|
23
|
+
const tasks = loadTasks()
|
|
24
|
+
const scheduleSignature = getScheduleSignatureKey(schedule)
|
|
25
|
+
if (scheduleSignature) {
|
|
26
|
+
const inFlight = Object.values(tasks as Record<string, InFlightTask>).some((task) =>
|
|
27
|
+
task
|
|
28
|
+
&& (task.status === 'queued' || task.status === 'running')
|
|
29
|
+
&& task.sourceScheduleKey === scheduleSignature,
|
|
30
|
+
)
|
|
31
|
+
if (inFlight) {
|
|
32
|
+
return NextResponse.json({ ok: true, queued: false, reason: 'in_flight' })
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const now = Date.now()
|
|
37
|
+
schedule.runNumber = (schedule.runNumber || 0) + 1
|
|
38
|
+
|
|
39
|
+
// Reuse linked task if it exists and is not in-flight
|
|
40
|
+
let taskId = ''
|
|
41
|
+
const existingTaskId = typeof schedule.linkedTaskId === 'string' ? schedule.linkedTaskId : ''
|
|
42
|
+
const existingTask = existingTaskId ? tasks[existingTaskId] : null
|
|
43
|
+
|
|
44
|
+
if (existingTask && existingTask.status !== 'queued' && existingTask.status !== 'running') {
|
|
45
|
+
taskId = existingTaskId
|
|
46
|
+
const prev = existingTask as Record<string, unknown>
|
|
47
|
+
prev.totalRuns = ((prev.totalRuns as number) || 0) + 1
|
|
48
|
+
if (existingTask.status === 'completed') prev.totalCompleted = ((prev.totalCompleted as number) || 0) + 1
|
|
49
|
+
if (existingTask.status === 'failed') prev.totalFailed = ((prev.totalFailed as number) || 0) + 1
|
|
50
|
+
|
|
51
|
+
existingTask.status = 'backlog'
|
|
52
|
+
existingTask.title = `[Sched] ${schedule.name} (run #${schedule.runNumber})`
|
|
53
|
+
existingTask.result = null
|
|
54
|
+
existingTask.error = null
|
|
55
|
+
existingTask.sessionId = null
|
|
56
|
+
existingTask.updatedAt = now
|
|
57
|
+
existingTask.queuedAt = null
|
|
58
|
+
existingTask.startedAt = null
|
|
59
|
+
existingTask.completedAt = null
|
|
60
|
+
existingTask.archivedAt = null
|
|
61
|
+
existingTask.attempts = 0
|
|
62
|
+
existingTask.retryScheduledAt = null
|
|
63
|
+
existingTask.deadLetteredAt = null
|
|
64
|
+
existingTask.validation = null
|
|
65
|
+
prev.runNumber = schedule.runNumber
|
|
66
|
+
} else {
|
|
67
|
+
taskId = crypto.randomBytes(4).toString('hex')
|
|
68
|
+
tasks[taskId] = {
|
|
69
|
+
id: taskId,
|
|
70
|
+
title: `[Sched] ${schedule.name} (run #${schedule.runNumber})`,
|
|
71
|
+
description: schedule.taskPrompt || '',
|
|
72
|
+
status: 'backlog',
|
|
73
|
+
agentId: schedule.agentId,
|
|
74
|
+
sessionId: null,
|
|
75
|
+
result: null,
|
|
76
|
+
error: null,
|
|
77
|
+
createdAt: now,
|
|
78
|
+
updatedAt: now,
|
|
79
|
+
queuedAt: null,
|
|
80
|
+
startedAt: null,
|
|
81
|
+
completedAt: null,
|
|
82
|
+
sourceType: 'schedule',
|
|
83
|
+
sourceScheduleId: schedule.id,
|
|
84
|
+
sourceScheduleName: schedule.name,
|
|
85
|
+
sourceScheduleKey: scheduleSignature || null,
|
|
86
|
+
createdInSessionId: schedule.createdInSessionId || null,
|
|
87
|
+
createdByAgentId: schedule.createdByAgentId || null,
|
|
88
|
+
runNumber: schedule.runNumber,
|
|
89
|
+
}
|
|
90
|
+
schedule.linkedTaskId = taskId
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
saveTasks(tasks)
|
|
94
|
+
enqueueTask(taskId)
|
|
95
|
+
pushMainLoopEventToMainSessions({
|
|
96
|
+
type: 'schedule_fired',
|
|
97
|
+
text: `Schedule fired manually: "${schedule.name}" (${schedule.id}) run #${schedule.runNumber} — task ${taskId}`,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
schedule.lastRunAt = now
|
|
101
|
+
saveSchedules(schedules)
|
|
102
|
+
|
|
103
|
+
return NextResponse.json({ ok: true, queued: true, taskId, runNumber: schedule.runNumber })
|
|
104
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { loadSchedules, saveSchedules } from '@/lib/server/storage'
|
|
4
|
+
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
5
|
+
import { findDuplicateSchedule } from '@/lib/schedule-dedupe'
|
|
6
|
+
|
|
7
|
+
export async function GET() {
|
|
8
|
+
return NextResponse.json(loadSchedules())
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function POST(req: Request) {
|
|
12
|
+
const body = await req.json()
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
const schedules = loadSchedules()
|
|
15
|
+
const scheduleType = body.scheduleType || 'cron'
|
|
16
|
+
|
|
17
|
+
const duplicate = findDuplicateSchedule(schedules, {
|
|
18
|
+
agentId: body.agentId || null,
|
|
19
|
+
taskPrompt: body.taskPrompt || '',
|
|
20
|
+
scheduleType,
|
|
21
|
+
cron: body.cron,
|
|
22
|
+
intervalMs: body.intervalMs,
|
|
23
|
+
runAt: body.runAt,
|
|
24
|
+
})
|
|
25
|
+
if (duplicate) {
|
|
26
|
+
const duplicateId = duplicate.id || ''
|
|
27
|
+
let changed = false
|
|
28
|
+
const nextName = resolveScheduleName({
|
|
29
|
+
name: body.name ?? duplicate.name,
|
|
30
|
+
taskPrompt: body.taskPrompt ?? duplicate.taskPrompt,
|
|
31
|
+
})
|
|
32
|
+
if (nextName && nextName !== duplicate.name) {
|
|
33
|
+
duplicate.name = nextName
|
|
34
|
+
changed = true
|
|
35
|
+
}
|
|
36
|
+
const normalizedStatus = typeof body.status === 'string' ? body.status.trim().toLowerCase() : ''
|
|
37
|
+
if ((normalizedStatus === 'active' || normalizedStatus === 'paused') && duplicate.status !== normalizedStatus) {
|
|
38
|
+
duplicate.status = normalizedStatus as 'active' | 'paused'
|
|
39
|
+
changed = true
|
|
40
|
+
}
|
|
41
|
+
if (changed) {
|
|
42
|
+
const mutableDuplicate = duplicate as Record<string, unknown>
|
|
43
|
+
mutableDuplicate.updatedAt = now
|
|
44
|
+
if (duplicateId) schedules[duplicateId] = duplicate
|
|
45
|
+
saveSchedules(schedules)
|
|
46
|
+
}
|
|
47
|
+
return NextResponse.json(duplicate)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const id = crypto.randomBytes(4).toString('hex')
|
|
51
|
+
|
|
52
|
+
let nextRunAt: number | undefined
|
|
53
|
+
if (scheduleType === 'once' && body.runAt) {
|
|
54
|
+
nextRunAt = body.runAt
|
|
55
|
+
} else if (scheduleType === 'interval' && body.intervalMs) {
|
|
56
|
+
nextRunAt = now + body.intervalMs
|
|
57
|
+
} else if (scheduleType === 'cron') {
|
|
58
|
+
// nextRunAt will be computed by the scheduler engine
|
|
59
|
+
nextRunAt = undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
schedules[id] = {
|
|
63
|
+
id,
|
|
64
|
+
name: resolveScheduleName({ name: body.name, taskPrompt: body.taskPrompt }),
|
|
65
|
+
agentId: body.agentId,
|
|
66
|
+
taskPrompt: body.taskPrompt || '',
|
|
67
|
+
scheduleType,
|
|
68
|
+
cron: body.cron,
|
|
69
|
+
intervalMs: body.intervalMs,
|
|
70
|
+
runAt: body.runAt,
|
|
71
|
+
lastRunAt: undefined,
|
|
72
|
+
nextRunAt,
|
|
73
|
+
status: body.status || 'active',
|
|
74
|
+
createdAt: now,
|
|
75
|
+
}
|
|
76
|
+
saveSchedules(schedules)
|
|
77
|
+
return NextResponse.json(schedules[id])
|
|
78
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadSecrets, saveSecrets } from '@/lib/server/storage'
|
|
3
|
+
|
|
4
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id } = await params
|
|
6
|
+
const secrets = loadSecrets()
|
|
7
|
+
if (!secrets[id]) return new NextResponse(null, { status: 404 })
|
|
8
|
+
delete secrets[id]
|
|
9
|
+
saveSecrets(secrets)
|
|
10
|
+
return NextResponse.json('ok')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
14
|
+
const { id } = await params
|
|
15
|
+
const body = await req.json()
|
|
16
|
+
const secrets = loadSecrets()
|
|
17
|
+
if (!secrets[id]) return new NextResponse(null, { status: 404 })
|
|
18
|
+
|
|
19
|
+
// Update metadata only (not the encrypted value unless a new value is provided)
|
|
20
|
+
if (body.name !== undefined) secrets[id].name = body.name
|
|
21
|
+
if (body.service !== undefined) secrets[id].service = body.service
|
|
22
|
+
if (body.scope !== undefined) secrets[id].scope = body.scope
|
|
23
|
+
if (body.agentIds !== undefined) secrets[id].agentIds = body.agentIds
|
|
24
|
+
secrets[id].updatedAt = Date.now()
|
|
25
|
+
saveSecrets(secrets)
|
|
26
|
+
|
|
27
|
+
const { encryptedValue, ...safe } = secrets[id]
|
|
28
|
+
return NextResponse.json(safe)
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import { loadSecrets, saveSecrets, encryptKey } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
// Return secrets WITHOUT the encrypted values (just metadata)
|
|
7
|
+
const secrets = loadSecrets()
|
|
8
|
+
const safe = Object.fromEntries(
|
|
9
|
+
Object.entries(secrets).map(([id, s]: [string, any]) => [
|
|
10
|
+
id,
|
|
11
|
+
{ id: s.id, name: s.name, service: s.service, scope: s.scope, agentIds: s.agentIds, createdAt: s.createdAt, updatedAt: s.updatedAt },
|
|
12
|
+
])
|
|
13
|
+
)
|
|
14
|
+
return NextResponse.json(safe)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function POST(req: Request) {
|
|
18
|
+
const body = await req.json()
|
|
19
|
+
const id = crypto.randomBytes(4).toString('hex')
|
|
20
|
+
const now = Date.now()
|
|
21
|
+
const secrets = loadSecrets()
|
|
22
|
+
|
|
23
|
+
if (!body.value?.trim()) {
|
|
24
|
+
return NextResponse.json({ error: 'value is required' }, { status: 400 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
secrets[id] = {
|
|
28
|
+
id,
|
|
29
|
+
name: body.name || 'Unnamed Secret',
|
|
30
|
+
service: body.service || 'custom',
|
|
31
|
+
encryptedValue: encryptKey(body.value),
|
|
32
|
+
scope: body.scope || 'global',
|
|
33
|
+
agentIds: body.agentIds || [],
|
|
34
|
+
createdAt: now,
|
|
35
|
+
updatedAt: now,
|
|
36
|
+
}
|
|
37
|
+
saveSecrets(secrets)
|
|
38
|
+
|
|
39
|
+
// Return without encrypted value
|
|
40
|
+
const { encryptedValue, ...safe } = secrets[id]
|
|
41
|
+
return NextResponse.json(safe)
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { hasActiveBrowser, cleanupSessionBrowser } from '@/lib/server/session-tools'
|
|
3
|
+
|
|
4
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id } = await params
|
|
6
|
+
return NextResponse.json({ active: hasActiveBrowser(id) })
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
|
+
const { id } = await params
|
|
11
|
+
cleanupSessionBrowser(id)
|
|
12
|
+
return new NextResponse('OK')
|
|
13
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { enqueueSessionRun, type SessionQueueMode } from '@/lib/server/session-run-manager'
|
|
3
|
+
import { log } from '@/lib/server/logger'
|
|
4
|
+
|
|
5
|
+
function normalizeQueueMode(raw: unknown, internal: boolean): SessionQueueMode {
|
|
6
|
+
if (raw === 'steer' || raw === 'collect' || raw === 'followup') return raw
|
|
7
|
+
return internal ? 'collect' : 'followup'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
11
|
+
const { id } = await params
|
|
12
|
+
const body = await req.json().catch(() => ({}))
|
|
13
|
+
|
|
14
|
+
const message = typeof body.message === 'string' ? body.message : ''
|
|
15
|
+
const imagePath = typeof body.imagePath === 'string' ? body.imagePath : undefined
|
|
16
|
+
const imageUrl = typeof body.imageUrl === 'string' ? body.imageUrl : undefined
|
|
17
|
+
const internal = body.internal === true
|
|
18
|
+
const queueMode = normalizeQueueMode(body.queueMode, internal)
|
|
19
|
+
|
|
20
|
+
if (!message.trim()) {
|
|
21
|
+
return NextResponse.json({ error: 'message is required' }, { status: 400 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const encoder = new TextEncoder()
|
|
25
|
+
const stream = new ReadableStream({
|
|
26
|
+
start(controller) {
|
|
27
|
+
let closed = false
|
|
28
|
+
const writeEvent = (event: Record<string, unknown>) => {
|
|
29
|
+
if (closed) return
|
|
30
|
+
try {
|
|
31
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
|
|
32
|
+
} catch {
|
|
33
|
+
closed = true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const run = enqueueSessionRun({
|
|
38
|
+
sessionId: id,
|
|
39
|
+
message,
|
|
40
|
+
imagePath,
|
|
41
|
+
imageUrl,
|
|
42
|
+
internal,
|
|
43
|
+
source: internal ? 'heartbeat' : 'chat',
|
|
44
|
+
mode: queueMode,
|
|
45
|
+
onEvent: (ev) => writeEvent(ev as unknown as Record<string, unknown>),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
log.info('chat', `Enqueued session run ${run.runId}`, {
|
|
49
|
+
sessionId: id,
|
|
50
|
+
internal,
|
|
51
|
+
mode: queueMode,
|
|
52
|
+
position: run.position,
|
|
53
|
+
deduped: run.deduped || false,
|
|
54
|
+
coalesced: run.coalesced || false,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
writeEvent({
|
|
58
|
+
t: 'md',
|
|
59
|
+
text: JSON.stringify({
|
|
60
|
+
run: {
|
|
61
|
+
id: run.runId,
|
|
62
|
+
status: run.deduped ? 'deduped' : run.coalesced ? 'coalesced' : 'queued',
|
|
63
|
+
position: run.position,
|
|
64
|
+
internal,
|
|
65
|
+
mode: queueMode,
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
run.promise
|
|
71
|
+
.catch((err) => {
|
|
72
|
+
const msg = err?.message || String(err)
|
|
73
|
+
writeEvent({ t: 'err', text: msg })
|
|
74
|
+
})
|
|
75
|
+
.finally(() => {
|
|
76
|
+
writeEvent({ t: 'done' })
|
|
77
|
+
if (!closed) {
|
|
78
|
+
try { controller.close() } catch { /* stream already closed */ }
|
|
79
|
+
closed = true
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
},
|
|
83
|
+
cancel() {
|
|
84
|
+
// Client disconnected; subsequent writes should be ignored.
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return new NextResponse(stream, {
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'text/event-stream',
|
|
91
|
+
'Cache-Control': 'no-cache',
|
|
92
|
+
'Connection': 'keep-alive',
|
|
93
|
+
'X-Accel-Buffering': 'no',
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadSessions, saveSessions } from '@/lib/server/storage'
|
|
3
|
+
|
|
4
|
+
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
5
|
+
const { id } = await params
|
|
6
|
+
const sessions = loadSessions()
|
|
7
|
+
if (!sessions[id]) return new NextResponse(null, { status: 404 })
|
|
8
|
+
sessions[id].messages = []
|
|
9
|
+
sessions[id].claudeSessionId = null
|
|
10
|
+
sessions[id].codexThreadId = null
|
|
11
|
+
sessions[id].opencodeSessionId = null
|
|
12
|
+
sessions[id].delegateResumeIds = {
|
|
13
|
+
claudeCode: null,
|
|
14
|
+
codex: null,
|
|
15
|
+
opencode: null,
|
|
16
|
+
}
|
|
17
|
+
saveSessions(sessions)
|
|
18
|
+
return new NextResponse('OK')
|
|
19
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import { loadSessions } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const sessions = loadSessions()
|
|
8
|
+
const session = sessions[id]
|
|
9
|
+
if (!session) return new NextResponse(null, { status: 404 })
|
|
10
|
+
|
|
11
|
+
const body = await req.json()
|
|
12
|
+
const msg = body.message || 'Deploy from SwarmClaw'
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const opts = { cwd: session.cwd, encoding: 'utf8' as const, timeout: 30000 }
|
|
16
|
+
execSync('git add -A', opts)
|
|
17
|
+
let committed = false
|
|
18
|
+
try {
|
|
19
|
+
execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, opts)
|
|
20
|
+
committed = true
|
|
21
|
+
} catch (ce: any) {
|
|
22
|
+
if (!(ce.stdout || ce.stderr || '').includes('nothing to commit')) throw ce
|
|
23
|
+
}
|
|
24
|
+
execSync('git push 2>&1', opts)
|
|
25
|
+
console.log(`[${id}] deployed: ${msg}`)
|
|
26
|
+
return NextResponse.json({ ok: true, output: committed ? 'Committed and pushed!' : 'Already committed — pushed to remote!' })
|
|
27
|
+
} catch (e: any) {
|
|
28
|
+
console.error(`[${id}] deploy error:`, e.message)
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ ok: false, error: (e.stderr || e.stdout || e.message).toString().slice(0, 300) },
|
|
31
|
+
{ status: 500 },
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { spawn } from 'child_process'
|
|
3
|
+
import { loadSessions, devServers, localIP } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const sessions = loadSessions()
|
|
8
|
+
const session = sessions[id]
|
|
9
|
+
if (!session) return new NextResponse(null, { status: 404 })
|
|
10
|
+
|
|
11
|
+
const { action } = await req.json()
|
|
12
|
+
|
|
13
|
+
if (action === 'start') {
|
|
14
|
+
if (devServers.has(id)) {
|
|
15
|
+
const ds = devServers.get(id)!
|
|
16
|
+
return NextResponse.json({ running: true, url: ds.url })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const proc = spawn('npm', ['run', 'dev', '--', '--host', '0.0.0.0'], {
|
|
20
|
+
cwd: session.cwd,
|
|
21
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
22
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
let output = ''
|
|
26
|
+
let detectedUrl: string | null = null
|
|
27
|
+
const urlRe = /https?:\/\/(?:localhost|0\.0\.0\.0|[\d.]+):(\d+)/
|
|
28
|
+
|
|
29
|
+
function onData(chunk: Buffer) {
|
|
30
|
+
output += chunk.toString()
|
|
31
|
+
if (!detectedUrl) {
|
|
32
|
+
const match = output.match(urlRe)
|
|
33
|
+
if (match) {
|
|
34
|
+
const port = match[1]
|
|
35
|
+
detectedUrl = `http://${localIP()}:${port}`
|
|
36
|
+
const ds = devServers.get(id)
|
|
37
|
+
if (ds) ds.url = detectedUrl
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
proc.stdout!.on('data', onData)
|
|
43
|
+
proc.stderr!.on('data', onData)
|
|
44
|
+
proc.on('close', () => { devServers.delete(id); console.log(`[${id}] dev server stopped`) })
|
|
45
|
+
proc.on('error', () => devServers.delete(id))
|
|
46
|
+
|
|
47
|
+
devServers.set(id, { proc, url: `http://${localIP()}:4321` })
|
|
48
|
+
console.log(`[${id}] starting dev server in ${session.cwd}`)
|
|
49
|
+
|
|
50
|
+
// Wait for URL detection
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 4000))
|
|
52
|
+
const ds = devServers.get(id)
|
|
53
|
+
return NextResponse.json({ running: !!ds, url: ds?.url || `http://${localIP()}:4321` })
|
|
54
|
+
|
|
55
|
+
} else if (action === 'stop') {
|
|
56
|
+
if (devServers.has(id)) {
|
|
57
|
+
const ds = devServers.get(id)!
|
|
58
|
+
try { ds.proc.kill('SIGTERM') } catch {}
|
|
59
|
+
try { process.kill(-ds.proc.pid, 'SIGTERM') } catch {}
|
|
60
|
+
devServers.delete(id)
|
|
61
|
+
}
|
|
62
|
+
return NextResponse.json({ running: false })
|
|
63
|
+
|
|
64
|
+
} else if (action === 'status') {
|
|
65
|
+
return NextResponse.json({ running: devServers.has(id), url: devServers.get(id)?.url })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return NextResponse.json({ running: false })
|
|
69
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { ackMailboxEnvelope, clearMailbox, listMailbox, sendMailboxEnvelope } from '@/lib/server/session-mailbox'
|
|
3
|
+
import { loadSessions } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
function parseIntParam(value: string | null, fallback: number, min: number, max: number): number {
|
|
6
|
+
const parsed = value ? Number.parseInt(value, 10) : Number.NaN
|
|
7
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
8
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
12
|
+
const { id } = await params
|
|
13
|
+
const { searchParams } = new URL(req.url)
|
|
14
|
+
const limit = parseIntParam(searchParams.get('limit'), 50, 1, 500)
|
|
15
|
+
const includeAcked = searchParams.get('includeAcked') === 'true'
|
|
16
|
+
try {
|
|
17
|
+
const envelopes = listMailbox(id, { limit, includeAcked })
|
|
18
|
+
return NextResponse.json({ sessionId: id, count: envelopes.length, envelopes })
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
return NextResponse.json({ error: err?.message || 'Failed to load mailbox.' }, { status: 404 })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
25
|
+
const { id } = await params
|
|
26
|
+
const body = await req.json().catch(() => ({}))
|
|
27
|
+
const action = typeof body?.action === 'string' ? body.action : 'send'
|
|
28
|
+
const sessions = loadSessions()
|
|
29
|
+
const current = sessions[id]
|
|
30
|
+
if (!current) return NextResponse.json({ error: `Session not found: ${id}` }, { status: 404 })
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (action === 'send') {
|
|
34
|
+
const toSessionId = typeof body?.toSessionId === 'string' ? body.toSessionId : ''
|
|
35
|
+
const payload = typeof body?.payload === 'string' ? body.payload : ''
|
|
36
|
+
const type = typeof body?.type === 'string' ? body.type : 'message'
|
|
37
|
+
if (!toSessionId) return NextResponse.json({ error: 'toSessionId is required for send.' }, { status: 400 })
|
|
38
|
+
if (!payload.trim()) return NextResponse.json({ error: 'payload is required for send.' }, { status: 400 })
|
|
39
|
+
const envelope = sendMailboxEnvelope({
|
|
40
|
+
toSessionId,
|
|
41
|
+
type,
|
|
42
|
+
payload,
|
|
43
|
+
fromSessionId: id,
|
|
44
|
+
fromAgentId: current.agentId || null,
|
|
45
|
+
correlationId: typeof body?.correlationId === 'string' ? body.correlationId : null,
|
|
46
|
+
ttlSec: typeof body?.ttlSec === 'number' ? body.ttlSec : null,
|
|
47
|
+
})
|
|
48
|
+
return NextResponse.json({ ok: true, envelope })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (action === 'ack') {
|
|
52
|
+
const envelopeId = typeof body?.envelopeId === 'string' ? body.envelopeId : ''
|
|
53
|
+
if (!envelopeId) return NextResponse.json({ error: 'envelopeId is required for ack.' }, { status: 400 })
|
|
54
|
+
const envelope = ackMailboxEnvelope(id, envelopeId)
|
|
55
|
+
if (!envelope) return NextResponse.json({ error: `Envelope not found: ${envelopeId}` }, { status: 404 })
|
|
56
|
+
return NextResponse.json({ ok: true, envelope })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (action === 'clear') {
|
|
60
|
+
const includeAcked = body?.includeAcked !== false
|
|
61
|
+
const result = clearMailbox(id, includeAcked)
|
|
62
|
+
return NextResponse.json({ ok: true, ...result })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return NextResponse.json({ error: `Unsupported action: ${action}` }, { status: 400 })
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
return NextResponse.json({ error: err?.message || 'Mailbox operation failed.' }, { status: 500 })
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { enqueueSessionRun } from '@/lib/server/session-run-manager'
|
|
3
|
+
import { loadSessions } from '@/lib/server/storage'
|
|
4
|
+
import {
|
|
5
|
+
buildMainLoopHeartbeatPrompt,
|
|
6
|
+
getMainLoopStateForSession,
|
|
7
|
+
isMainSession,
|
|
8
|
+
pushMainLoopEventToMainSessions,
|
|
9
|
+
setMainLoopStateForSession,
|
|
10
|
+
} from '@/lib/server/main-agent-loop'
|
|
11
|
+
|
|
12
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
13
|
+
const { id } = await params
|
|
14
|
+
const sessions = loadSessions()
|
|
15
|
+
const session = sessions[id]
|
|
16
|
+
if (!session) return new NextResponse('Session not found', { status: 404 })
|
|
17
|
+
if (!isMainSession(session)) return new NextResponse('Main-loop controls only apply to __main__ sessions', { status: 400 })
|
|
18
|
+
const state = getMainLoopStateForSession(id)
|
|
19
|
+
return NextResponse.json({ sessionId: id, state })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
23
|
+
const { id } = await params
|
|
24
|
+
const body = await req.json().catch(() => ({}))
|
|
25
|
+
const action = typeof body.action === 'string' ? body.action.trim() : ''
|
|
26
|
+
|
|
27
|
+
const sessions = loadSessions()
|
|
28
|
+
const session = sessions[id]
|
|
29
|
+
if (!session) return new NextResponse('Session not found', { status: 404 })
|
|
30
|
+
if (!isMainSession(session)) return new NextResponse('Main-loop controls only apply to __main__ sessions', { status: 400 })
|
|
31
|
+
|
|
32
|
+
if (action === 'pause') {
|
|
33
|
+
const state = setMainLoopStateForSession(id, { paused: true })
|
|
34
|
+
return NextResponse.json({ ok: true, action, state })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (action === 'resume') {
|
|
38
|
+
const state = setMainLoopStateForSession(id, { paused: false })
|
|
39
|
+
return NextResponse.json({ ok: true, action, state })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (action === 'set_goal') {
|
|
43
|
+
const goal = typeof body.goal === 'string' ? body.goal.trim() : ''
|
|
44
|
+
if (!goal) return new NextResponse('goal is required for set_goal', { status: 400 })
|
|
45
|
+
const state = setMainLoopStateForSession(id, {
|
|
46
|
+
goal,
|
|
47
|
+
status: 'progress',
|
|
48
|
+
paused: false,
|
|
49
|
+
followupChainCount: 0,
|
|
50
|
+
})
|
|
51
|
+
pushMainLoopEventToMainSessions({
|
|
52
|
+
type: 'operator_goal',
|
|
53
|
+
text: `Operator set mission goal: ${goal}`,
|
|
54
|
+
user: session.user || null,
|
|
55
|
+
})
|
|
56
|
+
return NextResponse.json({ ok: true, action, state })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (action === 'set_mode') {
|
|
60
|
+
const mode = body.mode === 'assist' ? 'assist' : body.mode === 'autonomous' ? 'autonomous' : null
|
|
61
|
+
if (!mode) return new NextResponse('mode must be "assist" or "autonomous"', { status: 400 })
|
|
62
|
+
const state = setMainLoopStateForSession(id, { autonomyMode: mode })
|
|
63
|
+
return NextResponse.json({ ok: true, action, state })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (action === 'clear_events') {
|
|
67
|
+
const state = setMainLoopStateForSession(id, { pendingEvents: [] })
|
|
68
|
+
return NextResponse.json({ ok: true, action, state })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (action === 'nudge') {
|
|
72
|
+
const state = getMainLoopStateForSession(id)
|
|
73
|
+
if (state?.paused) {
|
|
74
|
+
return new NextResponse('Mission loop is paused; resume first', { status: 409 })
|
|
75
|
+
}
|
|
76
|
+
const note = typeof body.note === 'string' ? body.note.trim() : ''
|
|
77
|
+
const prompt = buildMainLoopHeartbeatPrompt(
|
|
78
|
+
session,
|
|
79
|
+
'Operator requested manual nudge: execute one concrete next step now and report concise progress.',
|
|
80
|
+
)
|
|
81
|
+
const message = note ? `${prompt}\nOperator note: ${note.slice(0, 500)}` : prompt
|
|
82
|
+
const run = enqueueSessionRun({
|
|
83
|
+
sessionId: id,
|
|
84
|
+
message,
|
|
85
|
+
internal: true,
|
|
86
|
+
source: 'mission-control',
|
|
87
|
+
mode: 'collect',
|
|
88
|
+
dedupeKey: `mission-control:nudge:${id}`,
|
|
89
|
+
})
|
|
90
|
+
return NextResponse.json({ ok: true, action, runId: run.runId, position: run.position, deduped: run.deduped || false, state })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return new NextResponse('Unknown action. Use pause, resume, set_goal, set_mode, clear_events, or nudge.', { status: 400 })
|
|
94
|
+
}
|