@swarmclawai/swarmclaw 0.7.7 → 0.8.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 +12 -14
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +23 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +46 -3
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +257 -38
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +48 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +45 -3
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +289 -34
- package/src/components/tasks/task-board.tsx +410 -25
- package/src/components/tasks/task-card.tsx +66 -8
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +33 -0
- package/src/lib/server/capability-router.ts +80 -19
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +378 -73
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +461 -137
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +84 -47
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +247 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +20 -11
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +3 -2
- package/src/lib/server/orchestrator.ts +2 -0
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +211 -6
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +409 -2
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +527 -68
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +83 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +366 -54
- package/src/lib/server/session-tools/context.ts +17 -3
- package/src/lib/server/session-tools/crud.ts +484 -84
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +102 -10
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +554 -75
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
- package/src/lib/server/session-tools/web.ts +621 -70
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +437 -2
- package/src/lib/server/stream-agent-chat.ts +957 -79
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +271 -0
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +249 -14
|
@@ -4,6 +4,7 @@ import http from 'http'
|
|
|
4
4
|
import fs from 'fs'
|
|
5
5
|
import path from 'path'
|
|
6
6
|
import { localIP } from '@/lib/server/storage'
|
|
7
|
+
import { resolveDevServerLaunchDir } from '@/lib/server/devserver-launch'
|
|
7
8
|
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
// MIME types for static server
|
|
@@ -77,6 +78,13 @@ interface ProjectInfo {
|
|
|
77
78
|
framework?: string // e.g. 'vite', 'next', 'cra'
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function buildFrameworkArgs(framework: string | undefined, port: number): string[] {
|
|
82
|
+
if (framework === 'next') {
|
|
83
|
+
return ['--', '--hostname', '0.0.0.0', '--port', String(port)]
|
|
84
|
+
}
|
|
85
|
+
return ['--', '--port', String(port), '--host', '0.0.0.0']
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
function detectProject(dir: string): ProjectInfo {
|
|
81
89
|
const pkgPath = path.join(dir, 'package.json')
|
|
82
90
|
if (!fs.existsSync(pkgPath)) {
|
|
@@ -168,7 +176,7 @@ function createStaticServer(dir: string): http.Server {
|
|
|
168
176
|
// npm dev server
|
|
169
177
|
// ---------------------------------------------------------------------------
|
|
170
178
|
|
|
171
|
-
async function startNpmServer(dir: string, command: string[], port: number): Promise<PreviewServer> {
|
|
179
|
+
async function startNpmServer(dir: string, command: string[], port: number, framework?: string): Promise<PreviewServer> {
|
|
172
180
|
// Install deps if node_modules missing
|
|
173
181
|
if (!fs.existsSync(path.join(dir, 'node_modules'))) {
|
|
174
182
|
console.log(`[preview] Installing dependencies in ${dir}`)
|
|
@@ -190,7 +198,7 @@ async function startNpmServer(dir: string, command: string[], port: number): Pro
|
|
|
190
198
|
const args = [...command.slice(1)]
|
|
191
199
|
const cmdName = command[0]
|
|
192
200
|
|
|
193
|
-
const proc = spawn(cmdName, [...args,
|
|
201
|
+
const proc = spawn(cmdName, [...args, ...buildFrameworkArgs(framework, port)], {
|
|
194
202
|
cwd: dir,
|
|
195
203
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
196
204
|
env,
|
|
@@ -234,6 +242,10 @@ async function startNpmServer(dir: string, command: string[], port: number): Pro
|
|
|
234
242
|
|
|
235
243
|
// Wait for the server to start and detect the actual port
|
|
236
244
|
await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
245
|
+
if (proc.exitCode !== null) {
|
|
246
|
+
servers.delete(dirKey(dir))
|
|
247
|
+
throw new Error(`npm dev server exited early with code ${proc.exitCode}\n${log.slice(-4000)}`)
|
|
248
|
+
}
|
|
237
249
|
entry.port = detectedPort
|
|
238
250
|
entry.log = log
|
|
239
251
|
|
|
@@ -263,7 +275,8 @@ export async function POST(req: Request) {
|
|
|
263
275
|
}
|
|
264
276
|
|
|
265
277
|
const dir = resolveServeDir(filePath)
|
|
266
|
-
const
|
|
278
|
+
const launch = resolveDevServerLaunchDir(dir)
|
|
279
|
+
const key = dirKey(launch.launchDir)
|
|
267
280
|
|
|
268
281
|
if (action === 'start') {
|
|
269
282
|
if (servers.has(key)) {
|
|
@@ -274,16 +287,18 @@ export async function POST(req: Request) {
|
|
|
274
287
|
return NextResponse.json({ error: 'Directory not found' }, { status: 404 })
|
|
275
288
|
}
|
|
276
289
|
|
|
277
|
-
const project = detectProject(
|
|
290
|
+
const project = detectProject(launch.launchDir)
|
|
278
291
|
const port = await findFreePort()
|
|
279
292
|
|
|
280
293
|
if (project.type === 'npm' && project.devCommand) {
|
|
281
|
-
console.log(`[preview] Detected ${project.framework} project in ${
|
|
294
|
+
console.log(`[preview] Detected ${project.framework} project in ${launch.launchDir}, running: ${project.devCommand.join(' ')}`)
|
|
282
295
|
try {
|
|
283
|
-
const entry = await startNpmServer(
|
|
296
|
+
const entry = await startNpmServer(launch.launchDir, project.devCommand, port, project.framework)
|
|
284
297
|
return NextResponse.json({
|
|
285
298
|
...buildResponse(entry),
|
|
286
299
|
framework: project.framework,
|
|
300
|
+
inputDir: dir,
|
|
301
|
+
launchDir: launch.launchDir,
|
|
287
302
|
})
|
|
288
303
|
} catch (err: unknown) {
|
|
289
304
|
console.error(`[preview] npm server failed, falling back to static:`, err)
|
|
@@ -313,15 +328,15 @@ export async function POST(req: Request) {
|
|
|
313
328
|
}
|
|
314
329
|
if (srv.server) srv.server.close()
|
|
315
330
|
servers.delete(key)
|
|
316
|
-
console.log(`[preview] Stopped server for ${
|
|
331
|
+
console.log(`[preview] Stopped server for ${launch.launchDir}`)
|
|
317
332
|
}
|
|
318
|
-
return NextResponse.json({ running: false, dir })
|
|
333
|
+
return NextResponse.json({ running: false, dir: launch.launchDir })
|
|
319
334
|
|
|
320
335
|
} else if (action === 'status') {
|
|
321
336
|
if (servers.has(key)) {
|
|
322
337
|
return NextResponse.json(buildResponse(servers.get(key)!))
|
|
323
338
|
}
|
|
324
|
-
return NextResponse.json({ running: false, dir })
|
|
339
|
+
return NextResponse.json({ running: false, dir: launch.launchDir })
|
|
325
340
|
|
|
326
341
|
} else if (action === 'list') {
|
|
327
342
|
const list = Array.from(servers.values()).map((s) => ({
|
|
@@ -331,8 +346,8 @@ export async function POST(req: Request) {
|
|
|
331
346
|
return NextResponse.json({ servers: list })
|
|
332
347
|
|
|
333
348
|
} else if (action === 'detect') {
|
|
334
|
-
const project = detectProject(
|
|
335
|
-
return NextResponse.json({ dir, ...project })
|
|
349
|
+
const project = detectProject(launch.launchDir)
|
|
350
|
+
return NextResponse.json({ dir, launchDir: launch.launchDir, frameworkHint: launch.framework, ...project })
|
|
336
351
|
}
|
|
337
352
|
|
|
338
353
|
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadProjects, saveProjects, deleteProject, loadAgents, saveAgents, loadTasks, saveTasks, loadSchedules, saveSchedules, loadSkills, saveSkills } from '@/lib/server/storage'
|
|
2
|
+
import { loadProjects, saveProjects, deleteProject, loadAgents, saveAgents, loadTasks, saveTasks, loadSchedules, saveSchedules, loadSkills, saveSkills, loadSecrets, saveSecrets } from '@/lib/server/storage'
|
|
3
3
|
import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { ensureProjectWorkspace, normalizeProjectPatchInput } from '@/lib/server/project-utils'
|
|
4
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
6
|
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -17,12 +18,14 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
17
18
|
const { id } = await params
|
|
18
19
|
const body = await req.json()
|
|
19
20
|
const result = mutateItem(ops, id, (project) => {
|
|
20
|
-
|
|
21
|
+
const patch = normalizeProjectPatchInput(body && typeof body === 'object' ? body as Record<string, unknown> : {})
|
|
22
|
+
Object.assign(project, patch, { updatedAt: Date.now() })
|
|
21
23
|
delete (project as Record<string, unknown>).id
|
|
22
24
|
project.id = id
|
|
23
25
|
return project
|
|
24
26
|
})
|
|
25
27
|
if (!result) return notFound()
|
|
28
|
+
ensureProjectWorkspace(id, result.name)
|
|
26
29
|
return NextResponse.json(result)
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -50,6 +53,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
50
53
|
clearProjectId(loadTasks, saveTasks, 'tasks')
|
|
51
54
|
clearProjectId(loadSchedules, saveSchedules, 'schedules')
|
|
52
55
|
clearProjectId(loadSkills, saveSkills, 'skills')
|
|
56
|
+
clearProjectId(loadSecrets, saveSecrets, 'secrets')
|
|
53
57
|
|
|
54
58
|
return NextResponse.json({ ok: true })
|
|
55
59
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
3
|
import { loadProjects, saveProjects } from '@/lib/server/storage'
|
|
4
|
+
import { ensureProjectWorkspace, normalizeProjectCreateInput } from '@/lib/server/project-utils'
|
|
4
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
6
|
export const dynamic = 'force-dynamic'
|
|
6
7
|
|
|
@@ -13,15 +14,15 @@ export async function POST(req: Request) {
|
|
|
13
14
|
const id = genId()
|
|
14
15
|
const now = Date.now()
|
|
15
16
|
const projects = loadProjects()
|
|
17
|
+
const normalized = normalizeProjectCreateInput(body && typeof body === 'object' ? body as Record<string, unknown> : {})
|
|
16
18
|
projects[id] = {
|
|
17
19
|
id,
|
|
18
|
-
|
|
19
|
-
description: body.description || '',
|
|
20
|
-
color: body.color || undefined,
|
|
20
|
+
...normalized,
|
|
21
21
|
createdAt: now,
|
|
22
22
|
updatedAt: now,
|
|
23
23
|
}
|
|
24
24
|
saveProjects(projects)
|
|
25
|
+
ensureProjectWorkspace(id, projects[id].name)
|
|
25
26
|
notify('projects')
|
|
26
27
|
return NextResponse.json(projects[id])
|
|
27
28
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
4
|
import { loadSchedules, saveSchedules, loadAgents, loadTasks, saveTasks } from '@/lib/server/storage'
|
|
5
|
+
import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agent-availability'
|
|
5
6
|
import { enqueueTask } from '@/lib/server/queue'
|
|
6
7
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
7
8
|
import { getScheduleSignatureKey } from '@/lib/schedule-dedupe'
|
|
@@ -20,6 +21,9 @@ export async function POST(_req: Request, { params }: { params: Promise<{ id: st
|
|
|
20
21
|
const agents = loadAgents()
|
|
21
22
|
const agent = agents[schedule.agentId]
|
|
22
23
|
if (!agent) return NextResponse.json({ error: 'Agent not found' }, { status: 400 })
|
|
24
|
+
if (isAgentDisabled(agent)) {
|
|
25
|
+
return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'run schedules') }, { status: 409 })
|
|
26
|
+
}
|
|
23
27
|
|
|
24
28
|
const tasks = loadTasks()
|
|
25
29
|
const scheduleSignature = getScheduleSignatureKey(schedule)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test, { afterEach } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { POST as createSchedule } from './route'
|
|
5
|
+
import { POST as runSchedule } from './[id]/run/route'
|
|
6
|
+
import { loadAgents, loadSchedules, saveAgents, saveSchedules } from '@/lib/server/storage'
|
|
7
|
+
|
|
8
|
+
const originalAgents = loadAgents()
|
|
9
|
+
const originalSchedules = loadSchedules()
|
|
10
|
+
|
|
11
|
+
function routeParams(id: string) {
|
|
12
|
+
return { params: Promise.resolve({ id }) }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function seedAgent(id: string, overrides: Record<string, unknown> = {}) {
|
|
16
|
+
const agents = loadAgents()
|
|
17
|
+
const now = Date.now()
|
|
18
|
+
agents[id] = {
|
|
19
|
+
id,
|
|
20
|
+
name: 'Schedule Test Agent',
|
|
21
|
+
description: 'Schedule smoke test agent',
|
|
22
|
+
systemPrompt: 'Handle schedules.',
|
|
23
|
+
provider: 'openai',
|
|
24
|
+
model: 'gpt-4o-mini',
|
|
25
|
+
credentialId: null,
|
|
26
|
+
fallbackCredentialIds: [],
|
|
27
|
+
apiEndpoint: null,
|
|
28
|
+
plugins: ['manage_schedules'],
|
|
29
|
+
createdAt: now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
...overrides,
|
|
32
|
+
}
|
|
33
|
+
saveAgents(agents)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
saveAgents(originalAgents)
|
|
38
|
+
saveSchedules(originalSchedules)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('POST /api/schedules rejects disabled agents', async () => {
|
|
42
|
+
seedAgent('schedule-disabled-agent', { disabled: true })
|
|
43
|
+
|
|
44
|
+
const response = await createSchedule(new Request('http://local/api/schedules', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'content-type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
agentId: 'schedule-disabled-agent',
|
|
49
|
+
name: 'Disabled smoke',
|
|
50
|
+
taskPrompt: 'Send a reminder',
|
|
51
|
+
scheduleType: 'once',
|
|
52
|
+
runAt: Date.now() + 60_000,
|
|
53
|
+
status: 'active',
|
|
54
|
+
}),
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
assert.equal(response.status, 409)
|
|
58
|
+
const payload = await response.json() as Record<string, unknown>
|
|
59
|
+
assert.match(String(payload.error || ''), /disabled/i)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('POST /api/schedules/[id]/run rejects disabled agents', async () => {
|
|
63
|
+
seedAgent('schedule-run-disabled-agent', { disabled: true })
|
|
64
|
+
const schedules = loadSchedules()
|
|
65
|
+
schedules['schedule-disabled-run'] = {
|
|
66
|
+
id: 'schedule-disabled-run',
|
|
67
|
+
name: 'Disabled Run',
|
|
68
|
+
agentId: 'schedule-run-disabled-agent',
|
|
69
|
+
taskPrompt: 'Send a reminder',
|
|
70
|
+
scheduleType: 'once',
|
|
71
|
+
runAt: Date.now() + 60_000,
|
|
72
|
+
status: 'active',
|
|
73
|
+
createdAt: Date.now(),
|
|
74
|
+
updatedAt: Date.now(),
|
|
75
|
+
}
|
|
76
|
+
saveSchedules(schedules)
|
|
77
|
+
|
|
78
|
+
const response = await runSchedule(
|
|
79
|
+
new Request('http://local/api/schedules/schedule-disabled-run/run', { method: 'POST' }),
|
|
80
|
+
routeParams('schedule-disabled-run'),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
assert.equal(response.status, 409)
|
|
84
|
+
const payload = await response.json() as Record<string, unknown>
|
|
85
|
+
assert.match(String(payload.error || ''), /disabled/i)
|
|
86
|
+
})
|
|
@@ -3,6 +3,7 @@ import { genId } from '@/lib/id'
|
|
|
3
3
|
import { loadAgents, loadSchedules, saveSchedules } from '@/lib/server/storage'
|
|
4
4
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
5
5
|
import { normalizeSchedulePayload } from '@/lib/server/schedule-normalization'
|
|
6
|
+
import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agent-availability'
|
|
6
7
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
7
8
|
import { findDuplicateSchedule } from '@/lib/schedule-dedupe'
|
|
8
9
|
import { notify } from '@/lib/server/ws-hub'
|
|
@@ -45,9 +46,13 @@ export async function POST(req: Request) {
|
|
|
45
46
|
|
|
46
47
|
const candidate = normalizedSchedule.value
|
|
47
48
|
const agents = loadAgents()
|
|
48
|
-
|
|
49
|
+
const agent = agents[String(candidate.agentId)]
|
|
50
|
+
if (!agent) {
|
|
49
51
|
return NextResponse.json({ error: `Agent not found: ${String(candidate.agentId)}` }, { status: 400 })
|
|
50
52
|
}
|
|
53
|
+
if (isAgentDisabled(agent)) {
|
|
54
|
+
return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'take scheduled work') }, { status: 409 })
|
|
55
|
+
}
|
|
51
56
|
const scheduleType = asScheduleType(candidate.scheduleType)
|
|
52
57
|
const candidateAgentId = asString(candidate.agentId) || null
|
|
53
58
|
const candidateTaskPrompt = asString(candidate.taskPrompt)
|
|
@@ -19,6 +19,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
19
19
|
if (body.service !== undefined) secret.service = body.service
|
|
20
20
|
if (body.scope !== undefined) secret.scope = body.scope
|
|
21
21
|
if (body.agentIds !== undefined) secret.agentIds = body.agentIds
|
|
22
|
+
if (body.projectId !== undefined) secret.projectId = body.projectId || undefined
|
|
22
23
|
secret.updatedAt = Date.now()
|
|
23
24
|
return secret
|
|
24
25
|
})
|
|
@@ -10,7 +10,7 @@ export async function GET(_req: Request) {
|
|
|
10
10
|
const safe = Object.fromEntries(
|
|
11
11
|
Object.entries(secrets).map(([id, s]: [string, any]) => [
|
|
12
12
|
id,
|
|
13
|
-
{ id: s.id, name: s.name, service: s.service, scope: s.scope, agentIds: s.agentIds, createdAt: s.createdAt, updatedAt: s.updatedAt },
|
|
13
|
+
{ id: s.id, name: s.name, service: s.service, scope: s.scope, agentIds: s.agentIds, projectId: s.projectId, createdAt: s.createdAt, updatedAt: s.updatedAt },
|
|
14
14
|
])
|
|
15
15
|
)
|
|
16
16
|
return NextResponse.json(safe)
|
|
@@ -33,6 +33,7 @@ export async function POST(req: Request) {
|
|
|
33
33
|
encryptedValue: encryptKey(body.value),
|
|
34
34
|
scope: body.scope || 'global',
|
|
35
35
|
agentIds: body.agentIds || [],
|
|
36
|
+
projectId: typeof body.projectId === 'string' && body.projectId.trim() ? body.projectId.trim() : undefined,
|
|
36
37
|
createdAt: now,
|
|
37
38
|
updatedAt: now,
|
|
38
39
|
}
|
|
@@ -126,6 +126,8 @@ export async function PUT(req: Request) {
|
|
|
126
126
|
settings.taskQualityGateRequireVerification = parseBoolSetting(settings.taskQualityGateRequireVerification, false)
|
|
127
127
|
settings.taskQualityGateRequireArtifact = parseBoolSetting(settings.taskQualityGateRequireArtifact, false)
|
|
128
128
|
settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
|
|
129
|
+
settings.taskManagementEnabled = parseBoolSetting(settings.taskManagementEnabled, true)
|
|
130
|
+
settings.projectManagementEnabled = parseBoolSetting(settings.projectManagementEnabled, true)
|
|
129
131
|
settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
|
|
130
132
|
settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
|
|
131
133
|
settings.sessionIdleTimeoutSec = parseIntSetting(
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { normalizeOllamaSetupEndpoint } from './route'
|
|
5
|
+
|
|
6
|
+
test('normalizeOllamaSetupEndpoint strips local /v1 suffixes but preserves cloud endpoints', () => {
|
|
7
|
+
assert.equal(
|
|
8
|
+
normalizeOllamaSetupEndpoint('http://localhost:11434/v1', false),
|
|
9
|
+
'http://localhost:11434',
|
|
10
|
+
)
|
|
11
|
+
assert.equal(
|
|
12
|
+
normalizeOllamaSetupEndpoint('http://localhost:11434/', false),
|
|
13
|
+
'http://localhost:11434',
|
|
14
|
+
)
|
|
15
|
+
assert.equal(
|
|
16
|
+
normalizeOllamaSetupEndpoint('https://ollama.com/v1', true),
|
|
17
|
+
'https://ollama.com/v1',
|
|
18
|
+
)
|
|
19
|
+
})
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadCredentials, decryptKey } from '@/lib/server/storage'
|
|
3
3
|
import { getDeviceId, wsConnect } from '@/lib/providers/openclaw'
|
|
4
4
|
import { OPENAI_COMPATIBLE_DEFAULTS } from '@/lib/server/provider-health'
|
|
5
|
+
import { resolveOllamaRuntimeConfig } from '@/lib/server/ollama-runtime'
|
|
5
6
|
|
|
6
7
|
type SetupProvider =
|
|
7
8
|
| 'openai'
|
|
@@ -28,6 +29,12 @@ function clean(value: unknown): string {
|
|
|
28
29
|
return typeof value === 'string' ? value.trim() : ''
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
export function normalizeOllamaSetupEndpoint(endpoint: string, useCloud: boolean): string {
|
|
33
|
+
const normalized = endpoint.replace(/\/+$/, '')
|
|
34
|
+
if (useCloud) return normalized
|
|
35
|
+
return normalized.replace(/\/v1$/i, '')
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
function parseBody(input: unknown): SetupCheckBody {
|
|
32
39
|
if (!input || typeof input !== 'object' || Array.isArray(input)) return {}
|
|
33
40
|
return input as SetupCheckBody
|
|
@@ -101,9 +108,28 @@ async function checkAnthropic(apiKey: string, modelRaw: string): Promise<{ ok: b
|
|
|
101
108
|
return { ok: true, message: text ? `Connected to Anthropic. Sample: ${text.slice(0, 120)}` : 'Connected to Anthropic.' }
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
async function checkOllama(
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
async function checkOllama(params: {
|
|
112
|
+
endpointRaw: string
|
|
113
|
+
modelRaw: string
|
|
114
|
+
apiKey?: string
|
|
115
|
+
}): Promise<{ ok: boolean; message: string; normalizedEndpoint: string; recommendedModel?: string }> {
|
|
116
|
+
const runtime = resolveOllamaRuntimeConfig({
|
|
117
|
+
model: params.modelRaw,
|
|
118
|
+
apiKey: params.apiKey,
|
|
119
|
+
apiEndpoint: params.endpointRaw,
|
|
120
|
+
})
|
|
121
|
+
const normalizedEndpoint = normalizeOllamaSetupEndpoint(runtime.endpoint, runtime.useCloud)
|
|
122
|
+
const tagsPath = runtime.useCloud ? '/v1/models' : '/api/tags'
|
|
123
|
+
const headers = runtime.apiKey ? { authorization: `Bearer ${runtime.apiKey}` } : undefined
|
|
124
|
+
if (runtime.useCloud && !runtime.apiKey) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
message: 'Ollama Cloud model requires an API key. Set OLLAMA_API_KEY or attach an Ollama credential.',
|
|
128
|
+
normalizedEndpoint,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const res = await fetch(`${normalizedEndpoint}${tagsPath}`, {
|
|
132
|
+
headers,
|
|
107
133
|
signal: AbortSignal.timeout(8_000),
|
|
108
134
|
cache: 'no-store',
|
|
109
135
|
})
|
|
@@ -112,20 +138,24 @@ async function checkOllama(endpointRaw: string): Promise<{ ok: boolean; message:
|
|
|
112
138
|
return { ok: false, message: detail, normalizedEndpoint }
|
|
113
139
|
}
|
|
114
140
|
const payload = await res.json().catch(() => ({} as any))
|
|
115
|
-
const models =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
141
|
+
const models = runtime.useCloud
|
|
142
|
+
? (Array.isArray(payload?.data) ? payload.data : [])
|
|
143
|
+
: (Array.isArray(payload?.models) ? payload.models : [])
|
|
144
|
+
const firstModel = runtime.useCloud
|
|
145
|
+
? (typeof models[0]?.id === 'string' ? String(models[0].id) : undefined)
|
|
146
|
+
: (typeof models[0]?.name === 'string' ? String(models[0].name).replace(/:latest$/, '') : undefined)
|
|
119
147
|
if (models.length === 0) {
|
|
120
148
|
return {
|
|
121
149
|
ok: true,
|
|
122
|
-
message:
|
|
150
|
+
message: runtime.useCloud
|
|
151
|
+
? 'Connected to Ollama Cloud, but no models were returned.'
|
|
152
|
+
: 'Connected to Ollama, but no models are installed yet. Run `ollama pull <model>` to add one.',
|
|
123
153
|
normalizedEndpoint,
|
|
124
154
|
}
|
|
125
155
|
}
|
|
126
156
|
return {
|
|
127
157
|
ok: true,
|
|
128
|
-
message: `Connected to Ollama. ${models.length} model(s) available.`,
|
|
158
|
+
message: `Connected to ${runtime.useCloud ? 'Ollama Cloud' : 'Ollama'}. ${models.length} model(s) available.`,
|
|
129
159
|
normalizedEndpoint,
|
|
130
160
|
recommendedModel: firstModel,
|
|
131
161
|
}
|
|
@@ -205,7 +235,7 @@ export async function POST(req: Request) {
|
|
|
205
235
|
return NextResponse.json(result)
|
|
206
236
|
}
|
|
207
237
|
case 'ollama': {
|
|
208
|
-
const result = await checkOllama(endpoint)
|
|
238
|
+
const result = await checkOllama({ endpointRaw: endpoint, modelRaw: model, apiKey })
|
|
209
239
|
return NextResponse.json(result)
|
|
210
240
|
}
|
|
211
241
|
case 'openclaw': {
|
|
@@ -31,6 +31,18 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
31
31
|
content: normalized.content,
|
|
32
32
|
sourceUrl: normalized.sourceUrl,
|
|
33
33
|
sourceFormat: normalized.sourceFormat,
|
|
34
|
+
author: normalized.author ?? skill.author,
|
|
35
|
+
tags: normalized.tags ?? skill.tags,
|
|
36
|
+
version: normalized.version ?? null,
|
|
37
|
+
homepage: normalized.homepage ?? null,
|
|
38
|
+
primaryEnv: normalized.primaryEnv ?? null,
|
|
39
|
+
skillKey: normalized.skillKey ?? null,
|
|
40
|
+
always: typeof normalized.always === 'boolean' ? normalized.always : false,
|
|
41
|
+
installOptions: normalized.installOptions,
|
|
42
|
+
skillRequirements: normalized.skillRequirements,
|
|
43
|
+
detectedEnvVars: normalized.detectedEnvVars,
|
|
44
|
+
security: normalized.security,
|
|
45
|
+
frontmatter: normalized.frontmatter,
|
|
34
46
|
scope: updatedScope,
|
|
35
47
|
agentIds: updatedAgentIds,
|
|
36
48
|
id,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { genId } from '@/lib/id'
|
|
2
1
|
import { NextResponse } from 'next/server'
|
|
3
|
-
import { loadSkills, saveSkills } from '@/lib/server/storage'
|
|
4
2
|
import { normalizeSkillPayload } from '@/lib/server/skills-normalize'
|
|
5
3
|
|
|
6
4
|
const MAX_SKILL_BYTES = 2 * 1024 * 1024
|
|
@@ -46,22 +44,26 @@ export async function POST(req: Request) {
|
|
|
46
44
|
sourceUrl: url,
|
|
47
45
|
})
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
const id = genId()
|
|
51
|
-
skills[id] = {
|
|
52
|
-
id,
|
|
47
|
+
return NextResponse.json({
|
|
53
48
|
name: normalized.name,
|
|
54
49
|
filename: normalized.filename,
|
|
55
50
|
description: normalized.description,
|
|
56
51
|
content: normalized.content,
|
|
57
52
|
sourceUrl: normalized.sourceUrl,
|
|
58
53
|
sourceFormat: normalized.sourceFormat,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
author: normalized.author,
|
|
55
|
+
tags: normalized.tags,
|
|
56
|
+
version: normalized.version,
|
|
57
|
+
homepage: normalized.homepage,
|
|
58
|
+
primaryEnv: normalized.primaryEnv,
|
|
59
|
+
skillKey: normalized.skillKey,
|
|
60
|
+
always: normalized.always,
|
|
61
|
+
installOptions: normalized.installOptions,
|
|
62
|
+
skillRequirements: normalized.skillRequirements,
|
|
63
|
+
detectedEnvVars: normalized.detectedEnvVars,
|
|
64
|
+
security: normalized.security,
|
|
65
|
+
frontmatter: normalized.frontmatter,
|
|
66
|
+
})
|
|
65
67
|
} catch (err: unknown) {
|
|
66
68
|
const message = err instanceof Error ? err.message : 'Failed to import skill'
|
|
67
69
|
return NextResponse.json({ error: message }, { status: 400 })
|
|
@@ -5,7 +5,7 @@ import { normalizeSkillPayload } from '@/lib/server/skills-normalize'
|
|
|
5
5
|
export const dynamic = 'force-dynamic'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
export async function GET(
|
|
8
|
+
export async function GET() {
|
|
9
9
|
return NextResponse.json(loadSkills())
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -26,6 +26,18 @@ export async function POST(req: Request) {
|
|
|
26
26
|
description: normalized.description || '',
|
|
27
27
|
sourceUrl: normalized.sourceUrl,
|
|
28
28
|
sourceFormat: normalized.sourceFormat,
|
|
29
|
+
author: normalized.author,
|
|
30
|
+
tags: normalized.tags,
|
|
31
|
+
version: normalized.version,
|
|
32
|
+
homepage: normalized.homepage,
|
|
33
|
+
primaryEnv: normalized.primaryEnv,
|
|
34
|
+
skillKey: normalized.skillKey,
|
|
35
|
+
always: normalized.always,
|
|
36
|
+
installOptions: normalized.installOptions,
|
|
37
|
+
skillRequirements: normalized.skillRequirements,
|
|
38
|
+
detectedEnvVars: normalized.detectedEnvVars,
|
|
39
|
+
security: normalized.security,
|
|
40
|
+
frontmatter: normalized.frontmatter,
|
|
29
41
|
scope,
|
|
30
42
|
agentIds,
|
|
31
43
|
createdAt: Date.now(),
|
|
@@ -126,7 +126,16 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
126
126
|
enqueueSystemEvent(tasks[id].sessionId, `Task ${tasks[id].status}: ${tasks[id].title}`)
|
|
127
127
|
}
|
|
128
128
|
if (tasks[id].agentId) {
|
|
129
|
-
requestHeartbeatNow({
|
|
129
|
+
requestHeartbeatNow({
|
|
130
|
+
agentId: tasks[id].agentId,
|
|
131
|
+
eventId: `task:${id}:${tasks[id].status}`,
|
|
132
|
+
reason: 'task-completed',
|
|
133
|
+
source: `task:${id}`,
|
|
134
|
+
resumeMessage: `Task ${tasks[id].status}: ${tasks[id].title}`,
|
|
135
|
+
detail: tasks[id].status === 'failed'
|
|
136
|
+
? String(tasks[id].error || '').slice(0, 400)
|
|
137
|
+
: JSON.stringify(tasks[id].result || '').slice(0, 400),
|
|
138
|
+
})
|
|
130
139
|
}
|
|
131
140
|
}
|
|
132
141
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import {
|
|
4
|
+
buildGitHubIssueTaskDescription,
|
|
5
|
+
buildGitHubIssueTaskTags,
|
|
6
|
+
buildGitHubIssueTaskTitle,
|
|
7
|
+
parseGitHubRepoInput,
|
|
8
|
+
} from './route'
|
|
9
|
+
|
|
10
|
+
test('parseGitHubRepoInput accepts repo slugs and GitHub URLs', () => {
|
|
11
|
+
assert.deepEqual(parseGitHubRepoInput('swarmclawai/swarmclaw'), {
|
|
12
|
+
owner: 'swarmclawai',
|
|
13
|
+
repo: 'swarmclaw',
|
|
14
|
+
fullName: 'swarmclawai/swarmclaw',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
assert.deepEqual(parseGitHubRepoInput('https://github.com/swarmclawai/swarmclaw/issues'), {
|
|
18
|
+
owner: 'swarmclawai',
|
|
19
|
+
repo: 'swarmclaw',
|
|
20
|
+
fullName: 'swarmclawai/swarmclaw',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
assert.equal(parseGitHubRepoInput('not-a-repo'), null)
|
|
24
|
+
assert.equal(parseGitHubRepoInput('https://example.com/swarmclawai/swarmclaw'), null)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('GitHub issue mapping builds a source-aware task payload shape', () => {
|
|
28
|
+
const issue = {
|
|
29
|
+
id: 12345,
|
|
30
|
+
number: 87,
|
|
31
|
+
title: 'Import GitHub issues into the board',
|
|
32
|
+
body: 'Bring open issues into SwarmClaw tasks.',
|
|
33
|
+
state: 'open',
|
|
34
|
+
html_url: 'https://github.com/swarmclawai/swarmclaw/issues/87',
|
|
35
|
+
labels: [{ name: 'feature' }, { name: 'task board' }, { name: 'feature' }],
|
|
36
|
+
assignee: { login: 'waydelyle' },
|
|
37
|
+
user: { login: 'octocat' },
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
assert.equal(
|
|
41
|
+
buildGitHubIssueTaskTitle(issue, 'swarmclawai/swarmclaw'),
|
|
42
|
+
'[swarmclawai/swarmclaw#87] Import GitHub issues into the board',
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
assert.equal(
|
|
46
|
+
buildGitHubIssueTaskDescription(issue, 'swarmclawai/swarmclaw'),
|
|
47
|
+
[
|
|
48
|
+
'Imported from GitHub issue swarmclawai/swarmclaw#87',
|
|
49
|
+
'URL: https://github.com/swarmclawai/swarmclaw/issues/87',
|
|
50
|
+
'State: open',
|
|
51
|
+
'Labels: feature, task board, feature',
|
|
52
|
+
'Assignee: waydelyle',
|
|
53
|
+
'Opened by: octocat',
|
|
54
|
+
'',
|
|
55
|
+
'Bring open issues into SwarmClaw tasks.',
|
|
56
|
+
].join('\n'),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
assert.deepEqual(buildGitHubIssueTaskTags(issue, 'swarmclawai/swarmclaw'), [
|
|
60
|
+
'github',
|
|
61
|
+
'swarmclawai/swarmclaw',
|
|
62
|
+
'feature',
|
|
63
|
+
'task board',
|
|
64
|
+
])
|
|
65
|
+
})
|