@swarmclawai/swarmclaw 1.0.8 → 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.
Files changed (141) hide show
  1. package/README.md +13 -6
  2. package/package.json +1 -1
  3. package/src/app/api/activity/route.ts +1 -1
  4. package/src/app/api/autonomy/estop/route.ts +92 -0
  5. package/src/app/api/autonomy/guardian/restore/route.ts +18 -0
  6. package/src/app/api/chats/[id]/queue/route.ts +84 -0
  7. package/src/app/api/chats/[id]/route.ts +6 -4
  8. package/src/app/api/chats/route.ts +5 -3
  9. package/src/app/api/clawhub/install/route.ts +22 -7
  10. package/src/app/api/missions/[id]/actions/route.ts +31 -0
  11. package/src/app/api/missions/[id]/events/route.ts +14 -0
  12. package/src/app/api/missions/[id]/route.ts +10 -0
  13. package/src/app/api/missions/route.test.ts +244 -0
  14. package/src/app/api/missions/route.ts +57 -0
  15. package/src/app/api/openclaw/skills/install/route.ts +12 -1
  16. package/src/app/api/projects/[id]/route.ts +4 -4
  17. package/src/app/api/runs/[id]/events/route.ts +22 -0
  18. package/src/app/api/runs/route.ts +4 -1
  19. package/src/app/api/schedules/[id]/route.ts +1 -1
  20. package/src/app/api/search/route.ts +4 -4
  21. package/src/app/api/settings/route.ts +10 -1
  22. package/src/app/api/skills/import/route.ts +14 -0
  23. package/src/app/api/tasks/[id]/route.ts +14 -3
  24. package/src/app/api/tasks/route.ts +13 -4
  25. package/src/app/api/webhooks/[id]/helpers.ts +9 -2
  26. package/src/app/api/webhooks/route.test.ts +39 -0
  27. package/src/app/autonomy/page.tsx +822 -0
  28. package/src/app/globals.css +22 -1
  29. package/src/app/memory/page.tsx +24 -2
  30. package/src/app/missions/[id]/page.tsx +3 -0
  31. package/src/app/missions/page.tsx +631 -0
  32. package/src/cli/index.js +17 -0
  33. package/src/cli/spec.js +23 -0
  34. package/src/components/agents/agent-chat-list.tsx +2 -0
  35. package/src/components/chat/chat-area.tsx +29 -4
  36. package/src/components/chat/chat-card.tsx +55 -2
  37. package/src/components/chat/message-bubble.test.ts +94 -0
  38. package/src/components/chat/message-bubble.tsx +383 -190
  39. package/src/components/chat/message-list.tsx +51 -42
  40. package/src/components/chatrooms/chatroom-input.tsx +86 -19
  41. package/src/components/chatrooms/chatroom-message.tsx +3 -3
  42. package/src/components/input/chat-input.tsx +173 -38
  43. package/src/components/layout/dashboard-shell.tsx +1 -10
  44. package/src/components/layout/sidebar-rail.tsx +59 -65
  45. package/src/components/projects/project-detail.tsx +103 -2
  46. package/src/components/runs/run-list.tsx +59 -3
  47. package/src/components/shared/command-palette.tsx +1 -0
  48. package/src/components/tasks/task-card.tsx +38 -1
  49. package/src/components/tasks/task-sheet.tsx +50 -0
  50. package/src/lib/app/navigation.ts +7 -0
  51. package/src/lib/app/view-constants.ts +18 -2
  52. package/src/lib/chat/chat-streaming-state.test.ts +74 -0
  53. package/src/lib/chat/chat-streaming-state.ts +57 -0
  54. package/src/lib/chat/chats.ts +21 -1
  55. package/src/lib/chat/queued-message-queue.test.ts +86 -11
  56. package/src/lib/chat/queued-message-queue.ts +49 -22
  57. package/src/lib/runtime/heartbeat-defaults.ts +5 -1
  58. package/src/lib/runtime/runtime-loop.ts +4 -2
  59. package/src/lib/server/agents/agent-cascade.ts +13 -12
  60. package/src/lib/server/agents/delegation-jobs.test.ts +70 -0
  61. package/src/lib/server/agents/delegation-jobs.ts +28 -3
  62. package/src/lib/server/agents/guardian.ts +202 -24
  63. package/src/lib/server/agents/main-agent-loop.test.ts +86 -0
  64. package/src/lib/server/agents/main-agent-loop.ts +33 -1
  65. package/src/lib/server/agents/subagent-runtime.ts +2 -1
  66. package/src/lib/server/approvals.ts +6 -0
  67. package/src/lib/server/autonomy/supervisor-reflection.test.ts +113 -0
  68. package/src/lib/server/autonomy/supervisor-reflection.ts +152 -16
  69. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +68 -6
  70. package/src/lib/server/chat-execution/chat-execution-utils.test.ts +24 -0
  71. package/src/lib/server/chat-execution/chat-execution-utils.ts +16 -2
  72. package/src/lib/server/chat-execution/chat-execution.ts +171 -19
  73. package/src/lib/server/chat-execution/chat-streaming-utils.ts +15 -40
  74. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +340 -0
  75. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +237 -19
  76. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +85 -0
  77. package/src/lib/server/chat-execution/direct-memory-intent.ts +230 -0
  78. package/src/lib/server/chat-execution/memory-mutation-tools.ts +134 -0
  79. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +164 -4
  80. package/src/lib/server/chat-execution/stream-agent-chat.ts +142 -25
  81. package/src/lib/server/chat-execution/stream-continuation.ts +82 -2
  82. package/src/lib/server/chatrooms/chatroom-helpers.ts +1 -1
  83. package/src/lib/server/chatrooms/session-mailbox.ts +10 -0
  84. package/src/lib/server/connectors/contact-preferences.ts +118 -0
  85. package/src/lib/server/connectors/manager-roundtrip.test.ts +8 -2
  86. package/src/lib/server/connectors/manager.test.ts +426 -9
  87. package/src/lib/server/connectors/manager.ts +498 -78
  88. package/src/lib/server/connectors/session-consolidation.ts +4 -4
  89. package/src/lib/server/connectors/types.ts +2 -0
  90. package/src/lib/server/connectors/voice-note.ts +80 -0
  91. package/src/lib/server/context-manager.test.ts +19 -0
  92. package/src/lib/server/context-manager.ts +10 -3
  93. package/src/lib/server/elevenlabs.ts +1 -1
  94. package/src/lib/server/eval/agent-regression.ts +9 -9
  95. package/src/lib/server/integrity-monitor.ts +4 -1
  96. package/src/lib/server/memory/memory-db.ts +39 -4
  97. package/src/lib/server/memory/memory-graph.ts +5 -1
  98. package/src/lib/server/memory/temporal-decay.ts +7 -1
  99. package/src/lib/server/missions/mission-intent.test.ts +63 -0
  100. package/src/lib/server/missions/mission-intent.ts +569 -0
  101. package/src/lib/server/missions/mission-service.test.ts +881 -0
  102. package/src/lib/server/missions/mission-service.ts +2202 -0
  103. package/src/lib/server/provider-health.test.ts +38 -0
  104. package/src/lib/server/provider-health.ts +33 -4
  105. package/src/lib/server/query-expansion.ts +2 -1
  106. package/src/lib/server/runtime/daemon-state.ts +29 -0
  107. package/src/lib/server/runtime/estop.test.ts +98 -0
  108. package/src/lib/server/runtime/estop.ts +199 -0
  109. package/src/lib/server/runtime/heartbeat-wake.test.ts +2 -2
  110. package/src/lib/server/runtime/heartbeat-wake.ts +8 -4
  111. package/src/lib/server/runtime/queue-reconcile.test.ts +262 -2
  112. package/src/lib/server/runtime/queue.ts +189 -239
  113. package/src/lib/server/runtime/run-ledger.ts +108 -0
  114. package/src/lib/server/runtime/scheduler.test.ts +212 -0
  115. package/src/lib/server/runtime/scheduler.ts +19 -6
  116. package/src/lib/server/runtime/session-run-manager.test.ts +178 -0
  117. package/src/lib/server/runtime/session-run-manager.ts +406 -52
  118. package/src/lib/server/schedules/schedule-service.ts +1 -1
  119. package/src/lib/server/session-tools/connector.ts +24 -43
  120. package/src/lib/server/session-tools/context-mgmt.ts +3 -1
  121. package/src/lib/server/session-tools/crud.ts +32 -4
  122. package/src/lib/server/session-tools/delegate.ts +3 -43
  123. package/src/lib/server/session-tools/file-normalize.test.ts +18 -0
  124. package/src/lib/server/session-tools/file.ts +6 -0
  125. package/src/lib/server/session-tools/memory.ts +38 -0
  126. package/src/lib/server/skills/skill-audit.test.ts +33 -0
  127. package/src/lib/server/skills/skill-audit.ts +145 -0
  128. package/src/lib/server/storage.ts +267 -22
  129. package/src/lib/server/tasks/task-lifecycle.ts +2 -1
  130. package/src/lib/server/tasks/task-service.ts +10 -3
  131. package/src/lib/server/tasks/task-validation.ts +2 -2
  132. package/src/lib/server/tool-capability-policy-advanced.test.ts +6 -0
  133. package/src/lib/server/tool-capability-policy.ts +18 -10
  134. package/src/lib/server/untrusted-content.ts +113 -0
  135. package/src/stores/use-chat-store.test.ts +346 -22
  136. package/src/stores/use-chat-store.ts +213 -55
  137. package/src/stores/use-chatroom-store.ts +4 -0
  138. package/src/types/index.ts +392 -5
  139. package/src/views/settings/section-runtime-loop.tsx +28 -0
  140. package/src/components/chat/streaming-bubble.tsx +0 -97
  141. package/src/components/layout/mobile-drawer.tsx +0 -227
package/README.md CHANGED
@@ -17,12 +17,19 @@ Extension tutorial: https://swarmclaw.ai/docs/extension-tutorial
17
17
 
18
18
  ## Release Notes
19
19
 
20
- ### v1.0.8 Highlights
20
+ ### v1.1.0 Highlights
21
21
 
22
- - **Learned skills and self-healing**: SwarmClaw now keeps agent-scoped learned skills and shadow revisions so repeated successes and repeated external integration failures can harden into reusable local behavior without silently mutating the shared skill library.
23
- - **Direct chat stability**: chat-origin runs now stop after the visible answer instead of enqueueing hidden follow-up loops, leaked control tokens such as `NO_MESSAGE` stay out of the user transcript, and repeated internal reruns no longer replace the reply the user already saw.
24
- - **OpenClaw and Ollama route hardening**: agent thread sessions now repair stale credential/endpoint resolution more aggressively, including Ollama Cloud vs local endpoint selection and OpenClaw gateway fallback behavior.
25
- - **Operator UX fixes**: new agents appear in the list immediately, gateway-disconnected chat CTAs now route to the current agent's settings instead of global settings, setup-wizard flicker after access-key login is gone, and screenshot-heavy tool runs no longer render duplicate previews in chat.
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
+
27
+ ### v1.0.9 Highlights
28
+
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.
30
+ - **Sender-aware direct inbox replies**: direct connector sessions can honor stored sender display names and reply-medium preferences, including voice-note-first replies when the connector supports binary media and the agent has a configured voice.
31
+ - **Cleaner connector delivery reconciliation**: connector delivery markers now track what was actually sent, response previews prefer the delivered transcript, and task/connector followups resolve local output files more reliably.
32
+ - **Memory-write followthrough hardening**: successful memory store/update turns terminate more cleanly, which reduces unnecessary post-tool loops while still allowing a natural acknowledgement when the user needs one.
26
33
 
27
34
  ## What SwarmClaw Focuses On
28
35
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "Self-hosted AI runtime for OpenClaw, delegation, autonomy, runtime skills, crypto wallets, and chat platform connectors.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -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 = run.queueLength
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 = run.queueLength
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<string | null> {
37
- const bundle = await fetchClawHubSkillBundle(url)
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
- content = await materializeClawHubBundle(url) || await fetchSkillContent(url)
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
+ }