@swarmclawai/swarmclaw 0.7.1 → 0.7.3
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 +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +948 -112
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +14 -40
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +28 -1103
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { buildConnectorDoctorPreview, buildConnectorDoctorReport, type ConnectorDoctorPreviewInput } from '@/lib/server/connectors/doctor'
|
|
3
|
+
import { loadConnectors } from '@/lib/server/storage'
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
export async function POST(req: Request) {
|
|
8
|
+
const body = await req.json().catch(() => ({})) as ConnectorDoctorPreviewInput
|
|
9
|
+
const connectors = loadConnectors()
|
|
10
|
+
const baseConnector = typeof body.id === 'string' ? connectors[body.id] : null
|
|
11
|
+
const connector = buildConnectorDoctorPreview({ baseConnector, input: body })
|
|
12
|
+
return NextResponse.json(buildConnectorDoctorReport(connector, body.sampleMsg, { baseConnector }))
|
|
13
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
2
|
+
import { spawn } from 'child_process'
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
|
|
@@ -19,25 +19,27 @@ export async function POST(req: Request) {
|
|
|
19
19
|
const isDir = fs.statSync(resolved).isDirectory()
|
|
20
20
|
const platform = process.platform
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
let
|
|
22
|
+
let command: string
|
|
23
|
+
let args: string[]
|
|
24
24
|
if (platform === 'darwin') {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
command = 'open'
|
|
26
|
+
args = isDir ? [resolved] : ['-R', resolved]
|
|
27
27
|
} else if (platform === 'win32') {
|
|
28
|
-
|
|
28
|
+
command = 'explorer'
|
|
29
|
+
args = isDir ? [resolved] : [`/select,${resolved}`]
|
|
29
30
|
} else {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
command = 'xdg-open'
|
|
32
|
+
args = [isDir ? resolved : path.dirname(resolved)]
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
return new Promise<NextResponse>((resolve) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const child = spawn(command, args, { stdio: 'ignore' })
|
|
37
|
+
child.once('error', (err) => {
|
|
38
|
+
resolve(NextResponse.json({ error: err.message }, { status: 500 }))
|
|
39
|
+
})
|
|
40
|
+
child.once('spawn', () => {
|
|
41
|
+
child.unref()
|
|
42
|
+
resolve(NextResponse.json({ ok: true }))
|
|
41
43
|
})
|
|
42
44
|
})
|
|
43
45
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import path from 'path'
|
|
2
3
|
import { getMemoryDb } from '@/lib/server/memory-db'
|
|
3
4
|
import { loadSettings } from '@/lib/server/storage'
|
|
5
|
+
import { syncAllSessionArchiveMemories } from '@/lib/server/session-archive-memory'
|
|
6
|
+
import { DATA_DIR } from '@/lib/server/data-dir'
|
|
4
7
|
|
|
5
8
|
function parseBool(value: unknown, fallback: boolean): boolean {
|
|
6
9
|
if (typeof value === 'boolean') return value
|
|
@@ -33,10 +36,13 @@ export async function GET(req: Request) {
|
|
|
33
36
|
24 * 365,
|
|
34
37
|
)
|
|
35
38
|
const analyzed = db.analyzeMaintenance(ttlHours)
|
|
39
|
+
const archiveSync = syncAllSessionArchiveMemories()
|
|
36
40
|
return NextResponse.json({
|
|
37
41
|
ok: true,
|
|
38
42
|
ttlHours,
|
|
39
43
|
analyzed,
|
|
44
|
+
archiveSync,
|
|
45
|
+
archiveExportDir: path.join(DATA_DIR, 'session-archives'),
|
|
40
46
|
})
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -46,6 +52,9 @@ export async function POST(req: Request) {
|
|
|
46
52
|
const db = getMemoryDb()
|
|
47
53
|
const ttlHours = parseIntBounded(body?.ttlHours ?? settings.memoryWorkingTtlHours, 24, 1, 24 * 365)
|
|
48
54
|
const maxDeletes = parseIntBounded(body?.maxDeletes, 500, 1, 20_000)
|
|
55
|
+
const archiveSync = body?.syncArchives === false
|
|
56
|
+
? { synced: 0, skipped: 0, sessionIds: [] }
|
|
57
|
+
: syncAllSessionArchiveMemories()
|
|
49
58
|
const result = db.maintain({
|
|
50
59
|
ttlHours,
|
|
51
60
|
maxDeletes,
|
|
@@ -57,7 +66,8 @@ export async function POST(req: Request) {
|
|
|
57
66
|
ok: true,
|
|
58
67
|
ttlHours,
|
|
59
68
|
maxDeletes,
|
|
69
|
+
archiveSync,
|
|
70
|
+
archiveExportDir: path.join(DATA_DIR, 'session-archives'),
|
|
60
71
|
...result,
|
|
61
72
|
})
|
|
62
73
|
}
|
|
63
|
-
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
import { resolveOpenClawGatewayAgentId } from '@/lib/server/openclaw-agent-resolver'
|
|
3
4
|
|
|
4
5
|
const AGENT_FILES = ['SOUL.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md', 'MEMORY.md', 'AGENTS.md'] as const
|
|
5
6
|
|
|
@@ -16,12 +17,24 @@ export async function GET(req: Request) {
|
|
|
16
17
|
return NextResponse.json({ error: 'OpenClaw gateway not connected' }, { status: 503 })
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
let gatewayAgentId: string
|
|
21
|
+
try {
|
|
22
|
+
gatewayAgentId = await resolveOpenClawGatewayAgentId(agentId, gw)
|
|
23
|
+
} catch (err: unknown) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
25
|
+
const status = message.includes('not an OpenClaw agent') ? 400 : 404
|
|
26
|
+
return NextResponse.json({ error: message }, { status })
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
const files: Record<string, { content: string; error?: string }> = {}
|
|
20
30
|
await Promise.all(
|
|
21
31
|
AGENT_FILES.map(async (filename) => {
|
|
22
32
|
try {
|
|
23
|
-
const result = await gw.rpc('agents.files.get', {
|
|
24
|
-
|
|
33
|
+
const result = await gw.rpc('agents.files.get', {
|
|
34
|
+
agentId: gatewayAgentId,
|
|
35
|
+
name: filename,
|
|
36
|
+
}) as { file?: { content?: string } } | undefined
|
|
37
|
+
files[filename] = { content: result?.file?.content ?? '' }
|
|
25
38
|
} catch (err: unknown) {
|
|
26
39
|
files[filename] = { content: '', error: err instanceof Error ? err.message : String(err) }
|
|
27
40
|
}
|
|
@@ -48,10 +61,20 @@ export async function PUT(req: Request) {
|
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
try {
|
|
51
|
-
await
|
|
64
|
+
const gatewayAgentId = await resolveOpenClawGatewayAgentId(agentId, gw)
|
|
65
|
+
await gw.rpc('agents.files.set', {
|
|
66
|
+
agentId: gatewayAgentId,
|
|
67
|
+
name: filename,
|
|
68
|
+
content: content ?? '',
|
|
69
|
+
})
|
|
52
70
|
return NextResponse.json({ ok: true })
|
|
53
71
|
} catch (err: unknown) {
|
|
54
72
|
const message = err instanceof Error ? err.message : String(err)
|
|
55
|
-
|
|
73
|
+
const status = message.includes('not an OpenClaw agent')
|
|
74
|
+
? 400
|
|
75
|
+
: message.includes('not found')
|
|
76
|
+
? 404
|
|
77
|
+
: 502
|
|
78
|
+
return NextResponse.json({ error: message }, { status })
|
|
56
79
|
}
|
|
57
80
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
|
|
3
|
+
import { resolveOpenClawGatewayAgentId } from '@/lib/server/openclaw-agent-resolver'
|
|
4
|
+
import { normalizeOpenClawSkillsPayload } from '@/lib/server/openclaw-skills-normalize'
|
|
3
5
|
import { loadAgents, saveAgents } from '@/lib/server/storage'
|
|
4
6
|
import { notify } from '@/lib/server/ws-hub'
|
|
5
7
|
import type { OpenClawSkillEntry, SkillAllowlistMode } from '@/types'
|
|
@@ -18,11 +20,17 @@ export async function GET(req: Request) {
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
try {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
+
const gatewayAgentId = await resolveOpenClawGatewayAgentId(agentId, gw)
|
|
24
|
+
const result = await gw.rpc('skills.status', { agentId: gatewayAgentId }) as unknown
|
|
25
|
+
return NextResponse.json(normalizeOpenClawSkillsPayload(result))
|
|
23
26
|
} catch (err: unknown) {
|
|
24
27
|
const message = err instanceof Error ? err.message : String(err)
|
|
25
|
-
|
|
28
|
+
const status = message.includes('not an OpenClaw agent')
|
|
29
|
+
? 400
|
|
30
|
+
: message.includes('not found')
|
|
31
|
+
? 404
|
|
32
|
+
: 502
|
|
33
|
+
return NextResponse.json({ error: message }, { status })
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getPluginManager } from '@/lib/server/plugins'
|
|
3
|
+
|
|
4
|
+
export async function POST(req: Request) {
|
|
5
|
+
const body = await req.json()
|
|
6
|
+
const filename = typeof body?.filename === 'string' ? body.filename : ''
|
|
7
|
+
const packageManager = typeof body?.packageManager === 'string' ? body.packageManager : undefined
|
|
8
|
+
|
|
9
|
+
if (!filename) {
|
|
10
|
+
return NextResponse.json({ error: 'filename is required' }, { status: 400 })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const result = await getPluginManager().installPluginDependencies(filename, {
|
|
15
|
+
packageManager: packageManager as import('@/types').PluginPackageManager | undefined,
|
|
16
|
+
})
|
|
17
|
+
return NextResponse.json({ ok: true, dependencyInfo: result })
|
|
18
|
+
} catch (err: unknown) {
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
21
|
+
{ status: 400 },
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,110 +1,33 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { getPluginManager } from '@/lib/server/plugins'
|
|
5
|
-
|
|
6
|
-
const PLUGINS_DIR = path.join(process.cwd(), 'data', 'plugins')
|
|
7
|
-
|
|
8
|
-
function toRawUrl(url: string): string {
|
|
9
|
-
if (url.includes('github.com') && url.includes('/blob/')) {
|
|
10
|
-
return url.replace('github.com', 'raw.githubusercontent.com').replace('/blob/', '/')
|
|
11
|
-
}
|
|
12
|
-
if (url.includes('gist.github.com')) {
|
|
13
|
-
return url.endsWith('/raw') ? url : `${url}/raw`
|
|
14
|
-
}
|
|
15
|
-
return url
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function normalizeMarketplaceUrl(url: string): string {
|
|
19
|
-
const trimmed = typeof url === 'string' ? url.trim() : ''
|
|
20
|
-
if (!trimmed) return trimmed
|
|
21
|
-
|
|
22
|
-
let normalized = trimmed
|
|
23
|
-
.replace('github.com/swarmclawai/plugins/', 'github.com/swarmclawai/swarmforge/')
|
|
24
|
-
.replace('raw.githubusercontent.com/swarmclawai/plugins/', 'raw.githubusercontent.com/swarmclawai/swarmforge/')
|
|
25
|
-
|
|
26
|
-
normalized = toRawUrl(normalized)
|
|
27
|
-
|
|
28
|
-
// Legacy registry entries used master and old repo names.
|
|
29
|
-
normalized = normalized
|
|
30
|
-
.replace('/swarmclawai/swarmforge/master/', '/swarmclawai/swarmforge/main/')
|
|
31
|
-
.replace('/swarmclawai/plugins/master/', '/swarmclawai/swarmforge/main/')
|
|
32
|
-
.replace('/swarmclawai/plugins/main/', '/swarmclawai/swarmforge/main/')
|
|
33
|
-
|
|
34
|
-
return normalized
|
|
35
|
-
}
|
|
2
|
+
import { getPluginManager, sanitizePluginFilename } from '@/lib/server/plugins'
|
|
36
3
|
|
|
37
4
|
export async function POST(req: Request) {
|
|
38
5
|
const body = await req.json()
|
|
39
|
-
const
|
|
40
|
-
const
|
|
6
|
+
const url = typeof body?.url === 'string' ? body.url : ''
|
|
7
|
+
const filename = typeof body?.filename === 'string' ? body.filename : ''
|
|
41
8
|
|
|
42
|
-
|
|
43
|
-
if (!url || typeof url !== 'string' || !url.startsWith('https://')) {
|
|
9
|
+
if (!url || !url.startsWith('https://')) {
|
|
44
10
|
return NextResponse.json(
|
|
45
11
|
{ error: 'URL must be a valid HTTPS URL' },
|
|
46
12
|
{ status: 400 },
|
|
47
13
|
)
|
|
48
14
|
}
|
|
49
15
|
|
|
50
|
-
// Validate filename
|
|
51
|
-
if (!filename || typeof filename !== 'string' || !filename.endsWith('.js')) {
|
|
52
|
-
return NextResponse.json(
|
|
53
|
-
{ error: 'Filename must end in .js' },
|
|
54
|
-
{ status: 400 },
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Path traversal protection
|
|
59
|
-
const sanitized = path.basename(filename)
|
|
60
|
-
if (sanitized !== filename || filename.includes('..')) {
|
|
61
|
-
return NextResponse.json(
|
|
62
|
-
{ error: 'Invalid filename' },
|
|
63
|
-
{ status: 400 },
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
16
|
try {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{ error: `Download failed (HTTP ${res.status}) from ${rawUrl}` },
|
|
72
|
-
{ status: 502 },
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
const contentType = res.headers.get('content-type') || ''
|
|
76
|
-
let code = await res.text()
|
|
77
|
-
|
|
78
|
-
// Reject HTML responses (likely a GitHub page, not raw content)
|
|
79
|
-
if (contentType.includes('text/html') && code.includes('<!DOCTYPE')) {
|
|
80
|
-
return NextResponse.json(
|
|
81
|
-
{ error: 'URL returned an HTML page instead of JavaScript. Use a raw/direct link to the .js file.' },
|
|
82
|
-
{ status: 400 },
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Compatibility fix: Strip node-fetch requires if present, as modern Node has global fetch
|
|
87
|
-
code = code.replace(/const\s+fetch\s*=\s*require\(['"]node-fetch['"]\);?/g, '// node-fetch stripped for compatibility')
|
|
88
|
-
code = code.replace(/import\s+fetch\s+from\s+['"]node-fetch['"];?/g, '// node-fetch stripped for compatibility')
|
|
89
|
-
|
|
90
|
-
// Ensure plugins directory exists
|
|
91
|
-
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
92
|
-
fs.mkdirSync(PLUGINS_DIR, { recursive: true })
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const dest = path.join(PLUGINS_DIR, sanitized)
|
|
96
|
-
fs.writeFileSync(dest, code, 'utf8')
|
|
97
|
-
|
|
98
|
-
// Force plugin manager to re-scan so the new plugin appears in listings
|
|
99
|
-
getPluginManager().reload()
|
|
100
|
-
|
|
101
|
-
return NextResponse.json({ ok: true, filename: sanitized })
|
|
17
|
+
const sanitizedFilename = sanitizePluginFilename(filename)
|
|
18
|
+
const installed = await getPluginManager().installPluginFromUrl(url, sanitizedFilename)
|
|
19
|
+
return NextResponse.json({ ok: true, filename: installed.filename, hash: installed.sourceHash })
|
|
102
20
|
} catch (err: unknown) {
|
|
103
21
|
const msg = err instanceof Error ? err.message : String(err)
|
|
104
|
-
const isTimeout =
|
|
22
|
+
const isTimeout = /abort|timeout/i.test(msg)
|
|
23
|
+
const status = /valid HTTPS URL|Filename|Invalid filename|HTML page|too large/i.test(msg)
|
|
24
|
+
? 400
|
|
25
|
+
: isTimeout
|
|
26
|
+
? 504
|
|
27
|
+
: 500
|
|
105
28
|
return NextResponse.json(
|
|
106
|
-
{ error: isTimeout ? 'Download timed out — the plugin URL may be unreachable' :
|
|
107
|
-
{ status
|
|
29
|
+
{ error: isTimeout ? 'Download timed out — the plugin URL may be unreachable' : msg },
|
|
30
|
+
{ status },
|
|
108
31
|
)
|
|
109
32
|
}
|
|
110
33
|
}
|
|
@@ -1,31 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { getPluginManager } from '@/lib/server/plugins'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import '@/lib/server/session-tools/shell'
|
|
6
|
-
import '@/lib/server/session-tools/file'
|
|
7
|
-
import '@/lib/server/session-tools/edit_file'
|
|
8
|
-
import '@/lib/server/session-tools/web'
|
|
9
|
-
import '@/lib/server/session-tools/memory'
|
|
10
|
-
import '@/lib/server/session-tools/platform'
|
|
11
|
-
import '@/lib/server/session-tools/monitor'
|
|
12
|
-
import '@/lib/server/session-tools/discovery'
|
|
13
|
-
import '@/lib/server/session-tools/sample-ui'
|
|
14
|
-
import '@/lib/server/session-tools/git'
|
|
15
|
-
import '@/lib/server/session-tools/wallet'
|
|
16
|
-
import '@/lib/server/session-tools/connector'
|
|
17
|
-
import '@/lib/server/session-tools/http'
|
|
18
|
-
import '@/lib/server/session-tools/sandbox'
|
|
19
|
-
import '@/lib/server/session-tools/canvas'
|
|
20
|
-
import '@/lib/server/session-tools/chatroom'
|
|
21
|
-
import '@/lib/server/session-tools/delegate'
|
|
22
|
-
import '@/lib/server/session-tools/schedule'
|
|
23
|
-
import '@/lib/server/session-tools/session-info'
|
|
24
|
-
import '@/lib/server/session-tools/openclaw-nodes'
|
|
25
|
-
import '@/lib/server/session-tools/openclaw-workspace'
|
|
26
|
-
import '@/lib/server/session-tools/context-mgmt'
|
|
27
|
-
import '@/lib/server/session-tools/subagent'
|
|
28
|
-
import '@/lib/server/session-tools/plugin-creator'
|
|
3
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
4
|
+
import '@/lib/server/builtin-plugins'
|
|
29
5
|
|
|
30
6
|
export const dynamic = 'force-dynamic'
|
|
31
7
|
|
|
@@ -44,6 +20,7 @@ export async function POST(req: Request) {
|
|
|
44
20
|
|
|
45
21
|
const manager = getPluginManager()
|
|
46
22
|
manager.setEnabled(filename, enabled)
|
|
23
|
+
notify('plugins')
|
|
47
24
|
|
|
48
25
|
return NextResponse.json({ ok: true })
|
|
49
26
|
}
|
|
@@ -59,6 +36,7 @@ export async function DELETE(req: Request) {
|
|
|
59
36
|
if (!deleted) {
|
|
60
37
|
return NextResponse.json({ error: 'Cannot delete built-in or non-existent plugin' }, { status: 400 })
|
|
61
38
|
}
|
|
39
|
+
notify('plugins')
|
|
62
40
|
return NextResponse.json({ ok: true })
|
|
63
41
|
}
|
|
64
42
|
|
|
@@ -71,11 +49,13 @@ export async function PATCH(req: Request) {
|
|
|
71
49
|
|
|
72
50
|
if (all) {
|
|
73
51
|
await manager.updateAllPlugins()
|
|
52
|
+
notify('plugins')
|
|
74
53
|
return NextResponse.json({ ok: true, message: 'All plugins updated' })
|
|
75
54
|
}
|
|
76
55
|
|
|
77
56
|
if (id) {
|
|
78
57
|
await manager.updatePlugin(id)
|
|
58
|
+
notify('plugins')
|
|
79
59
|
return NextResponse.json({ ok: true, message: `Plugin ${id} updated` })
|
|
80
60
|
}
|
|
81
61
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getPluginManager } from '@/lib/server/plugins'
|
|
3
|
+
import '@/lib/server/builtin-plugins'
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
/** GET /api/plugins/settings?pluginId=X — read per-plugin settings */
|
|
8
|
+
export async function GET(req: Request) {
|
|
9
|
+
const { searchParams } = new URL(req.url)
|
|
10
|
+
const pluginId = searchParams.get('pluginId')
|
|
11
|
+
if (!pluginId) {
|
|
12
|
+
return NextResponse.json({ error: 'pluginId required' }, { status: 400 })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return NextResponse.json(getPluginManager().getPublicPluginSettings(pluginId))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** PUT /api/plugins/settings?pluginId=X — write per-plugin settings */
|
|
19
|
+
export async function PUT(req: Request) {
|
|
20
|
+
const { searchParams } = new URL(req.url)
|
|
21
|
+
const pluginId = searchParams.get('pluginId')
|
|
22
|
+
if (!pluginId) {
|
|
23
|
+
return NextResponse.json({ error: 'pluginId required' }, { status: 400 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const body = await req.json() as Record<string, unknown>
|
|
28
|
+
const saved = getPluginManager().setPluginSettings(pluginId, body)
|
|
29
|
+
return NextResponse.json({
|
|
30
|
+
ok: true,
|
|
31
|
+
values: saved,
|
|
32
|
+
configuredSecretFields: getPluginManager().getPublicPluginSettings(pluginId).configuredSecretFields,
|
|
33
|
+
})
|
|
34
|
+
} catch (err: unknown) {
|
|
35
|
+
return NextResponse.json(
|
|
36
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
37
|
+
{ status: 400 },
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadSettings, saveSettings } from '@/lib/server/storage'
|
|
2
|
+
import { loadPublicSettings, loadSettings, saveSettings } from '@/lib/server/storage'
|
|
3
3
|
import { DEFAULT_DELEGATION_MAX_DEPTH } from '@/lib/runtime-loop'
|
|
4
4
|
export const dynamic = 'force-dynamic'
|
|
5
5
|
|
|
@@ -20,6 +20,9 @@ const TASK_QG_MIN_RESULT_MIN = 10
|
|
|
20
20
|
const TASK_QG_MIN_RESULT_MAX = 2000
|
|
21
21
|
const TASK_QG_MIN_EVIDENCE_MIN = 0
|
|
22
22
|
const TASK_QG_MIN_EVIDENCE_MAX = 8
|
|
23
|
+
const SESSION_RESET_TIMEOUT_MIN = 0
|
|
24
|
+
const SESSION_RESET_TIMEOUT_MAX = 365 * 24 * 60 * 60
|
|
25
|
+
const SECRET_SETTING_KEYS = ['elevenLabsApiKey', 'tavilyApiKey', 'braveApiKey'] as const
|
|
23
26
|
|
|
24
27
|
function parseIntSetting(value: unknown, fallback: number, min: number, max: number): number {
|
|
25
28
|
const parsed = typeof value === 'number'
|
|
@@ -42,13 +45,25 @@ function parseBoolSetting(value: unknown, fallback: boolean): boolean {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export async function GET(_req: Request) {
|
|
45
|
-
return NextResponse.json(
|
|
48
|
+
return NextResponse.json(loadPublicSettings())
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export async function PUT(req: Request) {
|
|
49
|
-
const body = await req.json()
|
|
52
|
+
const body = await req.json() as Record<string, unknown>
|
|
53
|
+
const sanitizedBody: Record<string, unknown> = { ...body }
|
|
54
|
+
|
|
55
|
+
delete sanitizedBody.__encryptedAppSettings
|
|
56
|
+
|
|
57
|
+
for (const key of SECRET_SETTING_KEYS) {
|
|
58
|
+
const configuredKey = `${key}Configured`
|
|
59
|
+
if (sanitizedBody[key] === null && sanitizedBody[configuredKey] === true) {
|
|
60
|
+
delete sanitizedBody[key]
|
|
61
|
+
}
|
|
62
|
+
delete sanitizedBody[configuredKey]
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
const settings = loadSettings()
|
|
51
|
-
Object.assign(settings,
|
|
66
|
+
Object.assign(settings, sanitizedBody)
|
|
52
67
|
|
|
53
68
|
const nextDepth = parseIntSetting(
|
|
54
69
|
settings.memoryReferenceDepth ?? settings.memoryMaxDepth,
|
|
@@ -116,16 +131,43 @@ export async function PUT(req: Request) {
|
|
|
116
131
|
settings.taskQualityGateRequireArtifact = parseBoolSetting(settings.taskQualityGateRequireArtifact, false)
|
|
117
132
|
settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
|
|
118
133
|
settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
|
|
134
|
+
settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
|
|
135
|
+
settings.sessionIdleTimeoutSec = parseIntSetting(
|
|
136
|
+
settings.sessionIdleTimeoutSec,
|
|
137
|
+
12 * 60 * 60,
|
|
138
|
+
SESSION_RESET_TIMEOUT_MIN,
|
|
139
|
+
SESSION_RESET_TIMEOUT_MAX,
|
|
140
|
+
)
|
|
141
|
+
settings.sessionMaxAgeSec = parseIntSetting(
|
|
142
|
+
settings.sessionMaxAgeSec,
|
|
143
|
+
7 * 24 * 60 * 60,
|
|
144
|
+
SESSION_RESET_TIMEOUT_MIN,
|
|
145
|
+
SESSION_RESET_TIMEOUT_MAX,
|
|
146
|
+
)
|
|
147
|
+
if (typeof settings.sessionDailyResetAt === 'string') settings.sessionDailyResetAt = settings.sessionDailyResetAt.trim() || null
|
|
148
|
+
if (typeof settings.sessionResetTimezone === 'string') settings.sessionResetTimezone = settings.sessionResetTimezone.trim() || null
|
|
119
149
|
|
|
120
150
|
saveSettings(settings)
|
|
121
151
|
|
|
122
152
|
// Restart heartbeat service when heartbeat-related settings change
|
|
123
|
-
const heartbeatKeys = [
|
|
124
|
-
|
|
153
|
+
const heartbeatKeys = [
|
|
154
|
+
'heartbeatIntervalSec',
|
|
155
|
+
'heartbeatInterval',
|
|
156
|
+
'heartbeatPrompt',
|
|
157
|
+
'heartbeatEnabled',
|
|
158
|
+
'heartbeatActiveStart',
|
|
159
|
+
'heartbeatActiveEnd',
|
|
160
|
+
'sessionResetMode',
|
|
161
|
+
'sessionIdleTimeoutSec',
|
|
162
|
+
'sessionMaxAgeSec',
|
|
163
|
+
'sessionDailyResetAt',
|
|
164
|
+
'sessionResetTimezone',
|
|
165
|
+
]
|
|
166
|
+
if (heartbeatKeys.some((k) => k in sanitizedBody)) {
|
|
125
167
|
import('@/lib/server/heartbeat-service').then(({ restartHeartbeatService }) => {
|
|
126
168
|
restartHeartbeatService()
|
|
127
169
|
}).catch(() => { /* heartbeat service may not be initialized yet */ })
|
|
128
170
|
}
|
|
129
171
|
|
|
130
|
-
return NextResponse.json(
|
|
172
|
+
return NextResponse.json(loadPublicSettings())
|
|
131
173
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { genId } from '@/lib/id'
|
|
2
2
|
import { NextResponse } from 'next/server'
|
|
3
|
-
import {
|
|
3
|
+
import { loadAgents, loadSettings, loadTasks, logActivity, upsertStoredItems, upsertTask } from '@/lib/server/storage'
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
5
|
import { disableSessionHeartbeat, enqueueTask, recoverStalledRunningTasks, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
6
6
|
import { ensureTaskCompletionReport } from '@/lib/server/task-reports'
|
|
@@ -13,6 +13,7 @@ import { requestHeartbeatNow } from '@/lib/server/heartbeat-wake'
|
|
|
13
13
|
import { validateDag, cascadeUnblock } from '@/lib/server/dag-validation'
|
|
14
14
|
import { getPluginManager } from '@/lib/server/plugins'
|
|
15
15
|
import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
|
|
16
|
+
import '@/lib/server/builtin-plugins'
|
|
16
17
|
|
|
17
18
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
18
19
|
// Keep completed queue integrity even if daemon is not running.
|
|
@@ -90,7 +91,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
upsertTask(id, tasks[id])
|
|
94
95
|
logActivity({ entityType: 'task', entityId: id, action: 'updated', actor: 'user', summary: `Task updated: "${tasks[id].title}" (${prevStatus} → ${tasks[id].status})` })
|
|
95
96
|
if (prevStatus !== tasks[id].status) {
|
|
96
97
|
pushMainLoopEventToMainSessions({
|
|
@@ -111,7 +112,12 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
111
112
|
})
|
|
112
113
|
|
|
113
114
|
if (tasks[id].status === 'completed') {
|
|
114
|
-
|
|
115
|
+
const agentPlugins = tasks[id].agentId ? (loadAgents()[tasks[id].agentId]?.plugins || []) : []
|
|
116
|
+
getPluginManager().runHook(
|
|
117
|
+
'onTaskComplete',
|
|
118
|
+
{ taskId: id, result: tasks[id].result },
|
|
119
|
+
{ enabledIds: agentPlugins },
|
|
120
|
+
)
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
// Enqueue system event + heartbeat wake
|
|
@@ -131,7 +137,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
131
137
|
// Revert status change and reject
|
|
132
138
|
tasks[id].status = prevStatus
|
|
133
139
|
tasks[id].updatedAt = Date.now()
|
|
134
|
-
|
|
140
|
+
upsertTask(id, tasks[id])
|
|
135
141
|
return NextResponse.json(
|
|
136
142
|
{ error: 'Cannot queue: blocked by incomplete tasks', blockedBy: incompleteBlocker },
|
|
137
143
|
{ status: 409 },
|
|
@@ -143,7 +149,10 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
143
149
|
if (tasks[id].status === 'completed') {
|
|
144
150
|
const unblockedIds = cascadeUnblock(tasks, id)
|
|
145
151
|
if (unblockedIds.length > 0) {
|
|
146
|
-
|
|
152
|
+
upsertStoredItems('tasks', [
|
|
153
|
+
[id, tasks[id]],
|
|
154
|
+
...unblockedIds.map((uid) => [uid, tasks[uid]] as [string, any]),
|
|
155
|
+
])
|
|
147
156
|
for (const uid of unblockedIds) {
|
|
148
157
|
enqueueTask(uid)
|
|
149
158
|
}
|
|
@@ -168,7 +177,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
168
177
|
tasks[id].status = 'archived'
|
|
169
178
|
tasks[id].archivedAt = Date.now()
|
|
170
179
|
tasks[id].updatedAt = Date.now()
|
|
171
|
-
|
|
180
|
+
upsertTask(id, tasks[id])
|
|
172
181
|
logActivity({ entityType: 'task', entityId: id, action: 'deleted', actor: 'user', summary: `Task archived: "${tasks[id].title}"` })
|
|
173
182
|
pushMainLoopEventToMainSessions({
|
|
174
183
|
type: 'task_archived',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadTasks,
|
|
2
|
+
import { loadTasks, logActivity, upsertStoredItems } from '@/lib/server/storage'
|
|
3
3
|
import { enqueueTask, disableSessionHeartbeat } from '@/lib/server/queue'
|
|
4
4
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
5
5
|
import { notify } from '@/lib/server/ws-hub'
|
|
@@ -82,7 +82,7 @@ export async function POST(req: Request) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
upsertStoredItems('tasks', results.map((id) => [id, tasks[id]] as [string, any]))
|
|
86
86
|
|
|
87
87
|
if (updated > 0) {
|
|
88
88
|
const action = body.status
|