@swarmclawai/swarmclaw 1.0.9 → 1.1.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 +8 -1
- package/package.json +1 -1
- package/src/app/api/activity/route.ts +1 -1
- package/src/app/api/autonomy/estop/route.ts +92 -0
- package/src/app/api/autonomy/guardian/restore/route.ts +18 -0
- package/src/app/api/chats/[id]/queue/route.ts +84 -0
- package/src/app/api/chats/[id]/route.ts +6 -4
- package/src/app/api/chats/route.ts +5 -3
- package/src/app/api/clawhub/install/route.ts +22 -7
- package/src/app/api/missions/[id]/actions/route.ts +31 -0
- package/src/app/api/missions/[id]/events/route.ts +14 -0
- package/src/app/api/missions/[id]/route.ts +10 -0
- package/src/app/api/missions/route.test.ts +244 -0
- package/src/app/api/missions/route.ts +57 -0
- package/src/app/api/openclaw/skills/install/route.ts +12 -1
- package/src/app/api/projects/[id]/route.ts +4 -4
- package/src/app/api/runs/[id]/events/route.ts +22 -0
- package/src/app/api/runs/route.ts +4 -1
- package/src/app/api/schedules/[id]/route.ts +1 -1
- package/src/app/api/search/route.ts +4 -4
- package/src/app/api/settings/route.ts +10 -1
- package/src/app/api/skills/import/route.ts +14 -0
- package/src/app/api/tasks/[id]/route.ts +14 -3
- package/src/app/api/tasks/route.ts +13 -4
- package/src/app/api/webhooks/[id]/helpers.ts +9 -2
- package/src/app/api/webhooks/route.test.ts +39 -0
- package/src/app/autonomy/page.tsx +822 -0
- package/src/app/globals.css +22 -1
- package/src/app/memory/page.tsx +24 -2
- package/src/app/missions/[id]/page.tsx +3 -0
- package/src/app/missions/page.tsx +631 -0
- package/src/cli/index.js +17 -0
- package/src/cli/spec.js +23 -0
- package/src/components/agents/agent-chat-list.tsx +2 -0
- package/src/components/chat/chat-area.tsx +29 -4
- package/src/components/chat/chat-card.tsx +55 -2
- package/src/components/chat/message-bubble.test.ts +94 -0
- package/src/components/chat/message-bubble.tsx +383 -190
- package/src/components/chat/message-list.tsx +51 -42
- package/src/components/chatrooms/chatroom-input.tsx +86 -19
- package/src/components/chatrooms/chatroom-message.tsx +3 -3
- package/src/components/input/chat-input.tsx +173 -38
- package/src/components/layout/dashboard-shell.tsx +1 -10
- package/src/components/layout/sidebar-rail.tsx +59 -65
- package/src/components/projects/project-detail.tsx +103 -2
- package/src/components/runs/run-list.tsx +59 -3
- package/src/components/shared/command-palette.tsx +1 -0
- package/src/components/tasks/task-card.tsx +38 -1
- package/src/components/tasks/task-sheet.tsx +50 -0
- package/src/lib/app/navigation.ts +7 -0
- package/src/lib/app/view-constants.ts +18 -2
- package/src/lib/chat/chat-streaming-state.test.ts +74 -0
- package/src/lib/chat/chat-streaming-state.ts +57 -0
- package/src/lib/chat/chats.ts +21 -1
- package/src/lib/chat/queued-message-queue.test.ts +86 -11
- package/src/lib/chat/queued-message-queue.ts +49 -22
- package/src/lib/runtime/heartbeat-defaults.ts +5 -1
- package/src/lib/runtime/runtime-loop.ts +4 -2
- package/src/lib/server/agents/agent-cascade.ts +13 -12
- package/src/lib/server/agents/delegation-jobs.test.ts +70 -0
- package/src/lib/server/agents/delegation-jobs.ts +28 -3
- package/src/lib/server/agents/guardian.ts +202 -24
- package/src/lib/server/agents/main-agent-loop.test.ts +86 -0
- package/src/lib/server/agents/main-agent-loop.ts +33 -1
- package/src/lib/server/agents/subagent-runtime.ts +2 -1
- package/src/lib/server/approvals.ts +6 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +113 -0
- package/src/lib/server/autonomy/supervisor-reflection.ts +152 -16
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +68 -6
- package/src/lib/server/chat-execution/chat-execution-utils.ts +11 -0
- package/src/lib/server/chat-execution/chat-execution.ts +166 -17
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +11 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +340 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +237 -19
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +85 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +230 -0
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +81 -0
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +155 -10
- package/src/lib/server/chat-execution/stream-agent-chat.ts +79 -14
- package/src/lib/server/chat-execution/stream-continuation.ts +65 -2
- package/src/lib/server/chatrooms/chatroom-helpers.ts +1 -1
- package/src/lib/server/chatrooms/session-mailbox.ts +10 -0
- package/src/lib/server/connectors/manager.test.ts +195 -0
- package/src/lib/server/connectors/manager.ts +280 -6
- package/src/lib/server/connectors/session-consolidation.ts +4 -4
- package/src/lib/server/context-manager.test.ts +19 -0
- package/src/lib/server/context-manager.ts +10 -3
- package/src/lib/server/elevenlabs.ts +1 -1
- package/src/lib/server/eval/agent-regression.ts +9 -9
- package/src/lib/server/integrity-monitor.ts +4 -1
- package/src/lib/server/memory/memory-db.ts +39 -4
- package/src/lib/server/memory/memory-graph.ts +5 -1
- package/src/lib/server/memory/temporal-decay.ts +7 -1
- package/src/lib/server/missions/mission-intent.test.ts +63 -0
- package/src/lib/server/missions/mission-intent.ts +569 -0
- package/src/lib/server/missions/mission-service.test.ts +881 -0
- package/src/lib/server/missions/mission-service.ts +2202 -0
- package/src/lib/server/provider-health.test.ts +38 -0
- package/src/lib/server/provider-health.ts +33 -4
- package/src/lib/server/query-expansion.ts +2 -1
- package/src/lib/server/runtime/daemon-state.ts +29 -0
- package/src/lib/server/runtime/estop.test.ts +98 -0
- package/src/lib/server/runtime/estop.ts +199 -0
- package/src/lib/server/runtime/queue.ts +63 -13
- package/src/lib/server/runtime/run-ledger.ts +108 -0
- package/src/lib/server/runtime/scheduler.test.ts +86 -0
- package/src/lib/server/runtime/scheduler.ts +14 -0
- package/src/lib/server/runtime/session-run-manager.test.ts +178 -0
- package/src/lib/server/runtime/session-run-manager.ts +406 -52
- package/src/lib/server/schedules/schedule-service.ts +1 -1
- package/src/lib/server/session-tools/context-mgmt.ts +3 -1
- package/src/lib/server/session-tools/crud.ts +32 -4
- package/src/lib/server/session-tools/delegate.ts +3 -43
- package/src/lib/server/session-tools/file-normalize.test.ts +18 -0
- package/src/lib/server/session-tools/file.ts +6 -0
- package/src/lib/server/skills/skill-audit.test.ts +33 -0
- package/src/lib/server/skills/skill-audit.ts +145 -0
- package/src/lib/server/storage.ts +267 -22
- package/src/lib/server/tasks/task-lifecycle.ts +2 -1
- package/src/lib/server/tasks/task-service.ts +10 -3
- package/src/lib/server/tasks/task-validation.ts +2 -2
- package/src/lib/server/tool-capability-policy-advanced.test.ts +6 -0
- package/src/lib/server/tool-capability-policy.ts +18 -10
- package/src/lib/server/untrusted-content.ts +113 -0
- package/src/stores/use-chat-store.test.ts +346 -22
- package/src/stores/use-chat-store.ts +213 -55
- package/src/stores/use-chatroom-store.ts +4 -0
- package/src/types/index.ts +311 -1
- package/src/views/settings/section-runtime-loop.tsx +28 -0
- package/src/components/chat/streaming-bubble.tsx +0 -97
- package/src/components/layout/mobile-drawer.tsx +0 -227
package/README.md
CHANGED
|
@@ -17,6 +17,13 @@ Extension tutorial: https://swarmclaw.ai/docs/extension-tutorial
|
|
|
17
17
|
|
|
18
18
|
## Release Notes
|
|
19
19
|
|
|
20
|
+
### v1.1.0 Highlights
|
|
21
|
+
|
|
22
|
+
- **Mission controller and Missions UI**: SwarmClaw now tracks durable multi-step objectives as missions with status, phase, linked tasks, queued turns, recent runs, event history, and operator actions from the new **Missions** surface.
|
|
23
|
+
- **Autonomy safety desk and run replay**: the new **Autonomy Control** page adds estop visibility, resume policy controls, incident review, and run replay backed by durable run history rather than transient in-memory state.
|
|
24
|
+
- **Durable queued follow-ups**: direct chat and connector follow-up turns now use a backend queue so queued work survives reloads, drains in order, and stays attached to the right mission/session context.
|
|
25
|
+
- **Chat execution and UX hardening**: streamed handoff, memory writes, inline media, queue state, and tool-policy fallback behavior were cleaned up so agents are less noisy, less brittle, and easier to follow in real chats.
|
|
26
|
+
|
|
20
27
|
### v1.0.9 Highlights
|
|
21
28
|
|
|
22
29
|
- **Quieter chat and inbox replies**: chat-origin and connector turns now suppress more hidden control text, stop replaying connector-tool output as normal assistant prose, and avoid extra empty follow-up chatter after successful tool work.
|
|
@@ -108,7 +115,7 @@ Then open `http://localhost:3456`.
|
|
|
108
115
|
|
|
109
116
|
- **Providers**: OpenClaw, OpenAI, Anthropic, Ollama, Google, DeepSeek, Groq, Together, Mistral, xAI, Fireworks, plus compatible custom endpoints.
|
|
110
117
|
- **Delegation**: built-in delegation to Claude Code, Codex CLI, OpenCode CLI, Gemini CLI, and native SwarmClaw subagents.
|
|
111
|
-
- **Autonomy**: heartbeat loops, schedules, background jobs, task execution, supervisor recovery, and agent wakeups.
|
|
118
|
+
- **Autonomy**: heartbeat loops, schedules, background jobs, task execution, supervisor recovery, mission control, and agent wakeups.
|
|
112
119
|
- **Memory**: hybrid recall, graph traversal, journaling, durable documents, project-scoped context, automatic reflection memory, communication preferences, profile and boundary memory, significant events, and open follow-up loops.
|
|
113
120
|
- **Wallets**: balances, transfers, signatures, EVM call/quote/swap flows, and approval-gated execution.
|
|
114
121
|
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, and more.
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ export async function GET(req: Request) {
|
|
|
11
11
|
const limit = Math.min(200, Math.max(1, Number(searchParams.get('limit')) || 50))
|
|
12
12
|
|
|
13
13
|
const all = loadActivity()
|
|
14
|
-
let entries = Object.values(all) as Array<Record<string, unknown>>
|
|
14
|
+
let entries = Object.values(all) as unknown as Array<Record<string, unknown>>
|
|
15
15
|
|
|
16
16
|
if (entityType) entries = entries.filter((e) => e.entityType === entityType)
|
|
17
17
|
if (entityId) entries = entries.filter((e) => e.entityId === entityId)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { cancelAllRuns } from '@/lib/server/runtime/session-run-manager'
|
|
3
|
+
import { startDaemon, stopDaemon } from '@/lib/server/runtime/daemon-state'
|
|
4
|
+
import {
|
|
5
|
+
areEstopResumeApprovalsEnabled,
|
|
6
|
+
engageEstop,
|
|
7
|
+
findEstopResumeApproval,
|
|
8
|
+
loadEstopState,
|
|
9
|
+
requestEstopResumeApproval,
|
|
10
|
+
resumeEstop,
|
|
11
|
+
} from '@/lib/server/runtime/estop'
|
|
12
|
+
|
|
13
|
+
export const dynamic = 'force-dynamic'
|
|
14
|
+
|
|
15
|
+
function buildStateResponse(state = loadEstopState()) {
|
|
16
|
+
const approval = state.resumeApprovalId ? findEstopResumeApproval(state.resumeApprovalId) : null
|
|
17
|
+
return {
|
|
18
|
+
...state,
|
|
19
|
+
resumeRequiresApproval: areEstopResumeApprovalsEnabled(),
|
|
20
|
+
approval,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function GET() {
|
|
25
|
+
return NextResponse.json(buildStateResponse())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function POST(req: Request) {
|
|
29
|
+
try {
|
|
30
|
+
const body = await req.json().catch(() => ({})) as Record<string, unknown>
|
|
31
|
+
const action = typeof body.action === 'string' ? body.action.trim().toLowerCase() : 'status'
|
|
32
|
+
|
|
33
|
+
if (action === 'engage') {
|
|
34
|
+
const level = body.level === 'autonomy' ? 'autonomy' : 'all'
|
|
35
|
+
const state = engageEstop({
|
|
36
|
+
level,
|
|
37
|
+
reason: typeof body.reason === 'string' ? body.reason : null,
|
|
38
|
+
engagedBy: typeof body.engagedBy === 'string' ? body.engagedBy : 'user',
|
|
39
|
+
})
|
|
40
|
+
stopDaemon({ source: `api/autonomy/estop:${level}` })
|
|
41
|
+
const cancelled = level === 'all'
|
|
42
|
+
? cancelAllRuns('Cancelled because all estop is engaged.')
|
|
43
|
+
: { cancelledQueued: 0, abortedRunning: 0 }
|
|
44
|
+
return NextResponse.json({ ok: true, state: buildStateResponse(state), cancelled })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (action === 'resume') {
|
|
48
|
+
const approvalId = typeof body.approvalId === 'string' ? body.approvalId : null
|
|
49
|
+
const requiresApproval = areEstopResumeApprovalsEnabled()
|
|
50
|
+
const state = loadEstopState()
|
|
51
|
+
|
|
52
|
+
if (state.level === 'none') {
|
|
53
|
+
return NextResponse.json({ ok: true, state: buildStateResponse(state) })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!requiresApproval) {
|
|
57
|
+
const resumed = resumeEstop({ bypassApproval: true })
|
|
58
|
+
startDaemon({ source: 'api/autonomy/estop:resume', manualStart: true })
|
|
59
|
+
return NextResponse.json({ ok: true, state: buildStateResponse(resumed) })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!approvalId) {
|
|
63
|
+
const existingApproval = state.resumeApprovalId ? findEstopResumeApproval(state.resumeApprovalId) : null
|
|
64
|
+
if (existingApproval?.status === 'approved') {
|
|
65
|
+
const resumed = resumeEstop({ approvalId: existingApproval.id })
|
|
66
|
+
startDaemon({ source: 'api/autonomy/estop:resume', manualStart: true })
|
|
67
|
+
return NextResponse.json({ ok: true, state: buildStateResponse(resumed) })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = requestEstopResumeApproval({
|
|
71
|
+
requester: typeof body.requester === 'string' ? body.requester : 'user',
|
|
72
|
+
})
|
|
73
|
+
return NextResponse.json({
|
|
74
|
+
ok: false,
|
|
75
|
+
requiresApproval: true,
|
|
76
|
+
state: buildStateResponse(result.state),
|
|
77
|
+
approval: result.approval,
|
|
78
|
+
}, { status: 202 })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const resumed = resumeEstop({ approvalId })
|
|
82
|
+
startDaemon({ source: 'api/autonomy/estop:resume', manualStart: true })
|
|
83
|
+
return NextResponse.json({ ok: true, state: buildStateResponse(resumed) })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return NextResponse.json(buildStateResponse())
|
|
87
|
+
} catch (err: unknown) {
|
|
88
|
+
return NextResponse.json({
|
|
89
|
+
error: err instanceof Error ? err.message : 'Failed to update estop state',
|
|
90
|
+
}, { status: 400 })
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { restoreGuardianCheckpoint } from '@/lib/server/agents/guardian'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function POST(req: Request) {
|
|
7
|
+
const body = await req.json().catch(() => ({})) as Record<string, unknown>
|
|
8
|
+
const approvalId = typeof body.approvalId === 'string' ? body.approvalId.trim() : ''
|
|
9
|
+
if (!approvalId) {
|
|
10
|
+
return NextResponse.json({ error: 'approvalId is required' }, { status: 400 })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const result = restoreGuardianCheckpoint(approvalId)
|
|
14
|
+
if (!result.ok) {
|
|
15
|
+
return NextResponse.json({ error: result.reason || 'Restore failed' }, { status: 400 })
|
|
16
|
+
}
|
|
17
|
+
return NextResponse.json({ ok: true, checkpoint: result.checkpoint })
|
|
18
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
3
|
+
import { loadSession } from '@/lib/server/storage'
|
|
4
|
+
import {
|
|
5
|
+
cancelQueuedRunById,
|
|
6
|
+
cancelQueuedRunsForSession,
|
|
7
|
+
enqueueSessionRun,
|
|
8
|
+
getSessionQueueSnapshot,
|
|
9
|
+
} from '@/lib/server/runtime/session-run-manager'
|
|
10
|
+
|
|
11
|
+
export const dynamic = 'force-dynamic'
|
|
12
|
+
|
|
13
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
14
|
+
const { id } = await params
|
|
15
|
+
const session = loadSession(id)
|
|
16
|
+
if (!session) return notFound()
|
|
17
|
+
return NextResponse.json(getSessionQueueSnapshot(id))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
21
|
+
const { id } = await params
|
|
22
|
+
const session = loadSession(id)
|
|
23
|
+
if (!session) return notFound()
|
|
24
|
+
|
|
25
|
+
const body = await req.json().catch(() => ({}))
|
|
26
|
+
const message = typeof body.message === 'string' ? body.message : ''
|
|
27
|
+
const imagePath = typeof body.imagePath === 'string' ? body.imagePath : undefined
|
|
28
|
+
const imageUrl = typeof body.imageUrl === 'string' ? body.imageUrl : undefined
|
|
29
|
+
const attachedFiles = Array.isArray(body.attachedFiles)
|
|
30
|
+
? body.attachedFiles.filter((file: unknown): file is string => typeof file === 'string' && file.trim().length > 0)
|
|
31
|
+
: undefined
|
|
32
|
+
const replyToId = typeof body.replyToId === 'string' ? body.replyToId : undefined
|
|
33
|
+
const hasFiles = !!(imagePath || imageUrl || attachedFiles?.length)
|
|
34
|
+
|
|
35
|
+
if (!message.trim() && !hasFiles) {
|
|
36
|
+
return NextResponse.json({ error: 'message or file is required' }, { status: 400 })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const queued = enqueueSessionRun({
|
|
40
|
+
sessionId: id,
|
|
41
|
+
missionId: session.missionId || null,
|
|
42
|
+
message,
|
|
43
|
+
imagePath,
|
|
44
|
+
imageUrl,
|
|
45
|
+
attachedFiles,
|
|
46
|
+
source: 'chat',
|
|
47
|
+
mode: 'followup',
|
|
48
|
+
replyToId,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return NextResponse.json({
|
|
52
|
+
queued: {
|
|
53
|
+
runId: queued.runId,
|
|
54
|
+
position: queued.position,
|
|
55
|
+
},
|
|
56
|
+
snapshot: getSessionQueueSnapshot(id),
|
|
57
|
+
}, { status: 202 })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function DELETE(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
61
|
+
const { id } = await params
|
|
62
|
+
const session = loadSession(id)
|
|
63
|
+
if (!session) return notFound()
|
|
64
|
+
|
|
65
|
+
const body = await req.json().catch(() => ({}))
|
|
66
|
+
const runId = typeof body.runId === 'string' ? body.runId.trim() : ''
|
|
67
|
+
if (runId) {
|
|
68
|
+
const snapshot = getSessionQueueSnapshot(id)
|
|
69
|
+
if (!snapshot.items.some((item) => item.runId === runId)) {
|
|
70
|
+
return NextResponse.json({ error: 'Queued run not found' }, { status: 404 })
|
|
71
|
+
}
|
|
72
|
+
cancelQueuedRunById(runId, 'Removed from queue')
|
|
73
|
+
return NextResponse.json({
|
|
74
|
+
cancelled: 1,
|
|
75
|
+
snapshot: getSessionQueueSnapshot(id),
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const cancelled = cancelQueuedRunsForSession(id, 'Cleared queued messages')
|
|
80
|
+
return NextResponse.json({
|
|
81
|
+
cancelled,
|
|
82
|
+
snapshot: getSessionQueueSnapshot(id),
|
|
83
|
+
})
|
|
84
|
+
}
|
|
@@ -4,8 +4,9 @@ import { notFound } from '@/lib/server/collection-helpers'
|
|
|
4
4
|
import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
5
5
|
import { resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
|
|
6
6
|
import { clearMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
|
|
7
|
-
import { getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
7
|
+
import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
8
8
|
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
9
|
+
import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
|
|
9
10
|
|
|
10
11
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
11
12
|
const { id } = await params
|
|
@@ -13,11 +14,12 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
13
14
|
if (!session) return notFound()
|
|
14
15
|
|
|
15
16
|
const run = getSessionRunState(id)
|
|
17
|
+
const queue = getSessionQueueSnapshot(id)
|
|
16
18
|
session.active = active.has(id) || !!run.runningRunId
|
|
17
|
-
session.queuedCount =
|
|
19
|
+
session.queuedCount = queue.queueLength
|
|
18
20
|
session.currentRunId = run.runningRunId || null
|
|
19
21
|
|
|
20
|
-
return NextResponse.json(session)
|
|
22
|
+
return NextResponse.json(enrichSessionWithMissionSummary(session))
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -133,7 +135,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
133
135
|
if (!Array.isArray(session.messages)) session.messages = []
|
|
134
136
|
|
|
135
137
|
upsertSession(id, session)
|
|
136
|
-
return NextResponse.json(session)
|
|
138
|
+
return NextResponse.json(enrichSessionWithMissionSummary(session as never))
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -6,13 +6,14 @@ import { perf } from '@/lib/server/runtime/perf'
|
|
|
6
6
|
import { loadSessions, saveSessions, deleteSession, active, loadAgents, upsertStoredItem } from '@/lib/server/storage'
|
|
7
7
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
8
8
|
import { notify } from '@/lib/server/ws-hub'
|
|
9
|
-
import { getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
9
|
+
import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
10
10
|
import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
11
11
|
import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
|
|
12
12
|
import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
|
|
13
13
|
import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
|
|
14
14
|
import { buildSessionListSummary } from '@/lib/chat/session-summary'
|
|
15
15
|
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
16
|
+
import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
|
|
16
17
|
export const dynamic = 'force-dynamic'
|
|
17
18
|
|
|
18
19
|
async function ensureDaemonIfNeeded(source: string) {
|
|
@@ -33,8 +34,9 @@ export async function GET(req: Request) {
|
|
|
33
34
|
const changedSessionIds: string[] = []
|
|
34
35
|
for (const id of Object.keys(sessions)) {
|
|
35
36
|
const run = getSessionRunState(id)
|
|
37
|
+
const queue = getSessionQueueSnapshot(id)
|
|
36
38
|
sessions[id].active = active.has(id) || !!run.runningRunId
|
|
37
|
-
sessions[id].queuedCount =
|
|
39
|
+
sessions[id].queuedCount = queue.queueLength
|
|
38
40
|
sessions[id].currentRunId = run.runningRunId || null
|
|
39
41
|
if (!sessions[id].active && Array.isArray(sessions[id].messages)) {
|
|
40
42
|
if (materializeStreamingAssistantArtifacts(sessions[id].messages)) changedSessionIds.push(id)
|
|
@@ -49,7 +51,7 @@ export async function GET(req: Request) {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const summarized = Object.fromEntries(
|
|
52
|
-
Object.entries(sessions).map(([id, session]) => [id, buildSessionListSummary(session)]),
|
|
54
|
+
Object.entries(sessions).map(([id, session]) => [id, buildSessionListSummary(enrichSessionWithMissionSummary(session))]),
|
|
53
55
|
)
|
|
54
56
|
|
|
55
57
|
const { searchParams } = new URL(req.url)
|
|
@@ -6,6 +6,7 @@ import { loadSkills, saveSkills } from '@/lib/server/storage'
|
|
|
6
6
|
import type { ClawHubSkillBundle } from '@/lib/server/skills/clawhub-client'
|
|
7
7
|
import { fetchClawHubSkillBundle, fetchSkillContent } from '@/lib/server/skills/clawhub-client'
|
|
8
8
|
import { clearDiscoveredSkillsCache, resolveWorkspaceSkillsDir } from '@/lib/server/skills/skill-discovery'
|
|
9
|
+
import { auditSkillBundleFiles, auditSkillContent, mergeSkillAuditResults } from '@/lib/server/skills/skill-audit'
|
|
9
10
|
import { normalizeSkillPayload } from '@/lib/server/skills/skills-normalize'
|
|
10
11
|
|
|
11
12
|
function sanitizeSkillDirName(value: string): string {
|
|
@@ -33,11 +34,8 @@ function stripSharedTopLevelDir(paths: string[]): string[] {
|
|
|
33
34
|
: paths
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
async function materializeClawHubBundle(url: string): Promise<
|
|
37
|
-
|
|
38
|
-
if (!bundle) return null
|
|
39
|
-
await writeClawHubBundleToWorkspace(bundle)
|
|
40
|
-
return bundle.content
|
|
37
|
+
async function materializeClawHubBundle(url: string): Promise<ClawHubSkillBundle | null> {
|
|
38
|
+
return fetchClawHubSkillBundle(url)
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
async function writeClawHubBundleToWorkspace(bundle: ClawHubSkillBundle): Promise<void> {
|
|
@@ -73,10 +71,12 @@ export async function POST(req: Request) {
|
|
|
73
71
|
const body = await req.json()
|
|
74
72
|
const { name, description, url, author, tags } = body
|
|
75
73
|
let { content } = body
|
|
74
|
+
let bundle: ClawHubSkillBundle | null = null
|
|
76
75
|
|
|
77
76
|
if (!content) {
|
|
78
77
|
try {
|
|
79
|
-
|
|
78
|
+
bundle = await materializeClawHubBundle(url)
|
|
79
|
+
content = bundle?.content || await fetchSkillContent(url)
|
|
80
80
|
} catch (err: unknown) {
|
|
81
81
|
return NextResponse.json(
|
|
82
82
|
{ error: err instanceof Error ? err.message : 'Failed to fetch skill content' },
|
|
@@ -93,6 +93,21 @@ export async function POST(req: Request) {
|
|
|
93
93
|
author,
|
|
94
94
|
tags,
|
|
95
95
|
})
|
|
96
|
+
const audit = mergeSkillAuditResults(
|
|
97
|
+
bundle ? auditSkillBundleFiles(bundle.files) : { status: 'pass', findings: [] },
|
|
98
|
+
auditSkillContent({
|
|
99
|
+
content,
|
|
100
|
+
requirements: normalized.skillRequirements,
|
|
101
|
+
installOptions: normalized.installOptions,
|
|
102
|
+
primaryEnv: normalized.primaryEnv,
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
105
|
+
if (audit.status === 'block') {
|
|
106
|
+
return NextResponse.json({ error: 'Skill blocked by static audit', audit }, { status: 400 })
|
|
107
|
+
}
|
|
108
|
+
if (bundle) {
|
|
109
|
+
await writeClawHubBundleToWorkspace(bundle)
|
|
110
|
+
}
|
|
96
111
|
|
|
97
112
|
const skills = loadSkills()
|
|
98
113
|
const duplicate = Object.values(skills).find((skill) => {
|
|
@@ -131,5 +146,5 @@ export async function POST(req: Request) {
|
|
|
131
146
|
}
|
|
132
147
|
saveSkills(skills)
|
|
133
148
|
clearDiscoveredSkillsCache()
|
|
134
|
-
return NextResponse.json(skills[id])
|
|
149
|
+
return NextResponse.json({ ...skills[id], audit })
|
|
135
150
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
3
|
+
import { loadMissionById, performMissionAction } from '@/lib/server/missions/mission-service'
|
|
4
|
+
|
|
5
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const mission = loadMissionById(id)
|
|
8
|
+
if (!mission) return notFound()
|
|
9
|
+
|
|
10
|
+
const body = await req.json().catch(() => ({}))
|
|
11
|
+
const action = body?.action
|
|
12
|
+
if (action !== 'resume' && action !== 'replan' && action !== 'cancel' && action !== 'retry_verification' && action !== 'wait') {
|
|
13
|
+
return NextResponse.json({ error: 'Invalid mission action.' }, { status: 400 })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = performMissionAction({
|
|
17
|
+
missionId: id,
|
|
18
|
+
action,
|
|
19
|
+
reason: typeof body.reason === 'string' ? body.reason : null,
|
|
20
|
+
waitKind: typeof body.waitKind === 'string' ? body.waitKind : undefined,
|
|
21
|
+
untilAt: typeof body.untilAt === 'number' ? body.untilAt : null,
|
|
22
|
+
})
|
|
23
|
+
if (!result) {
|
|
24
|
+
return NextResponse.json({ error: 'Unable to update mission.' }, { status: 409 })
|
|
25
|
+
}
|
|
26
|
+
return NextResponse.json({
|
|
27
|
+
ok: true,
|
|
28
|
+
mission: result.mission,
|
|
29
|
+
appendedEvent: result.event,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
3
|
+
import { listMissionEventsForMission, loadMissionById } from '@/lib/server/missions/mission-service'
|
|
4
|
+
|
|
5
|
+
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const mission = loadMissionById(id)
|
|
8
|
+
if (!mission) return notFound()
|
|
9
|
+
|
|
10
|
+
const { searchParams } = new URL(req.url)
|
|
11
|
+
const limitParam = searchParams.get('limit')
|
|
12
|
+
const limit = limitParam ? Number.parseInt(limitParam, 10) : undefined
|
|
13
|
+
return NextResponse.json(listMissionEventsForMission(id, Number.isFinite(limit) ? limit : undefined))
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
3
|
+
import { getMissionDetail } from '@/lib/server/missions/mission-service'
|
|
4
|
+
|
|
5
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params
|
|
7
|
+
const mission = getMissionDetail(id)
|
|
8
|
+
if (!mission) return notFound()
|
|
9
|
+
return NextResponse.json(mission)
|
|
10
|
+
}
|