@swarmclawai/swarmclaw 1.0.9 → 1.1.1

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 (172) hide show
  1. package/README.md +16 -1
  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/chatrooms/route.ts +6 -1
  7. package/src/app/api/chats/[id]/queue/route.ts +84 -0
  8. package/src/app/api/chats/[id]/route.ts +6 -4
  9. package/src/app/api/chats/route.ts +5 -3
  10. package/src/app/api/clawhub/install/route.ts +22 -7
  11. package/src/app/api/missions/[id]/actions/route.ts +31 -0
  12. package/src/app/api/missions/[id]/events/route.ts +14 -0
  13. package/src/app/api/missions/[id]/route.ts +10 -0
  14. package/src/app/api/missions/route.test.ts +244 -0
  15. package/src/app/api/missions/route.ts +57 -0
  16. package/src/app/api/openclaw/skills/install/route.ts +12 -1
  17. package/src/app/api/projects/[id]/route.ts +4 -4
  18. package/src/app/api/protocols/runs/[id]/actions/route.ts +26 -0
  19. package/src/app/api/protocols/runs/[id]/events/route.ts +16 -0
  20. package/src/app/api/protocols/runs/[id]/route.ts +10 -0
  21. package/src/app/api/protocols/runs/route.test.ts +173 -0
  22. package/src/app/api/protocols/runs/route.ts +51 -0
  23. package/src/app/api/protocols/templates/[id]/route.ts +50 -0
  24. package/src/app/api/protocols/templates/route.test.ts +109 -0
  25. package/src/app/api/protocols/templates/route.ts +30 -0
  26. package/src/app/api/runs/[id]/events/route.ts +22 -0
  27. package/src/app/api/runs/route.ts +4 -1
  28. package/src/app/api/schedules/[id]/route.ts +1 -1
  29. package/src/app/api/search/route.ts +4 -4
  30. package/src/app/api/settings/route.ts +10 -1
  31. package/src/app/api/skills/import/route.ts +14 -0
  32. package/src/app/api/tasks/[id]/route.ts +14 -3
  33. package/src/app/api/tasks/claim/route.test.ts +58 -0
  34. package/src/app/api/tasks/claim/route.ts +18 -0
  35. package/src/app/api/tasks/route.ts +13 -4
  36. package/src/app/api/webhooks/[id]/helpers.ts +9 -2
  37. package/src/app/api/webhooks/route.test.ts +39 -0
  38. package/src/app/autonomy/page.tsx +822 -0
  39. package/src/app/chatrooms/[id]/page.tsx +49 -0
  40. package/src/app/globals.css +22 -1
  41. package/src/app/memory/page.tsx +24 -2
  42. package/src/app/missions/[id]/page.tsx +3 -0
  43. package/src/app/missions/page.tsx +684 -0
  44. package/src/app/protocols/page.tsx +1117 -0
  45. package/src/cli/index.js +34 -0
  46. package/src/cli/spec.js +23 -0
  47. package/src/components/agents/agent-chat-list.tsx +2 -0
  48. package/src/components/chat/chat-area.tsx +29 -4
  49. package/src/components/chat/chat-card.tsx +55 -2
  50. package/src/components/chat/chat-header.tsx +66 -1
  51. package/src/components/chat/message-bubble.test.ts +131 -0
  52. package/src/components/chat/message-bubble.tsx +394 -190
  53. package/src/components/chat/message-list.tsx +51 -42
  54. package/src/components/chatrooms/breakout-command.test.ts +86 -0
  55. package/src/components/chatrooms/breakout-command.ts +119 -0
  56. package/src/components/chatrooms/chatroom-input.tsx +304 -90
  57. package/src/components/chatrooms/chatroom-message.tsx +3 -3
  58. package/src/components/chatrooms/chatroom-view.tsx +247 -25
  59. package/src/components/input/chat-input.tsx +226 -94
  60. package/src/components/input/composer-shell.tsx +44 -0
  61. package/src/components/layout/dashboard-shell.tsx +1 -10
  62. package/src/components/layout/sidebar-rail.tsx +65 -65
  63. package/src/components/projects/project-detail.tsx +103 -2
  64. package/src/components/protocols/structured-session-launcher.tsx +490 -0
  65. package/src/components/runs/run-list.tsx +59 -3
  66. package/src/components/schedules/schedule-sheet.tsx +57 -8
  67. package/src/components/shared/command-palette.tsx +1 -0
  68. package/src/components/tasks/task-card.tsx +38 -1
  69. package/src/components/tasks/task-sheet.tsx +115 -0
  70. package/src/lib/app/navigation.ts +8 -0
  71. package/src/lib/app/view-constants.ts +26 -2
  72. package/src/lib/chat/chat-streaming-state.test.ts +74 -0
  73. package/src/lib/chat/chat-streaming-state.ts +57 -0
  74. package/src/lib/chat/chats.ts +21 -1
  75. package/src/lib/chat/queued-message-queue.test.ts +86 -11
  76. package/src/lib/chat/queued-message-queue.ts +49 -22
  77. package/src/lib/runtime/heartbeat-defaults.ts +5 -1
  78. package/src/lib/runtime/runtime-loop.ts +4 -2
  79. package/src/lib/server/agents/agent-cascade.ts +13 -12
  80. package/src/lib/server/agents/delegation-jobs.test.ts +70 -0
  81. package/src/lib/server/agents/delegation-jobs.ts +28 -3
  82. package/src/lib/server/agents/guardian.ts +202 -24
  83. package/src/lib/server/agents/main-agent-loop.test.ts +86 -0
  84. package/src/lib/server/agents/main-agent-loop.ts +48 -1
  85. package/src/lib/server/agents/subagent-runtime.ts +2 -1
  86. package/src/lib/server/approvals.ts +6 -0
  87. package/src/lib/server/autonomy/supervisor-reflection.test.ts +113 -0
  88. package/src/lib/server/autonomy/supervisor-reflection.ts +152 -16
  89. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +68 -6
  90. package/src/lib/server/chat-execution/chat-execution-utils.ts +11 -0
  91. package/src/lib/server/chat-execution/chat-execution.ts +379 -153
  92. package/src/lib/server/chat-execution/chat-streaming-utils.ts +13 -3
  93. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +474 -0
  94. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +371 -19
  95. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +100 -0
  96. package/src/lib/server/chat-execution/direct-memory-intent.ts +233 -0
  97. package/src/lib/server/chat-execution/exact-output-contract.test.ts +91 -0
  98. package/src/lib/server/chat-execution/exact-output-contract.ts +220 -0
  99. package/src/lib/server/chat-execution/memory-mutation-tools.ts +81 -0
  100. package/src/lib/server/chat-execution/situational-awareness.test.ts +318 -0
  101. package/src/lib/server/chat-execution/situational-awareness.ts +221 -0
  102. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +243 -10
  103. package/src/lib/server/chat-execution/stream-agent-chat.ts +159 -13
  104. package/src/lib/server/chat-execution/stream-continuation.ts +65 -2
  105. package/src/lib/server/chatrooms/chatroom-helpers.ts +1 -1
  106. package/src/lib/server/chatrooms/session-mailbox.ts +10 -0
  107. package/src/lib/server/connectors/connector-routing.test.ts +59 -1
  108. package/src/lib/server/connectors/manager.test.ts +195 -0
  109. package/src/lib/server/connectors/manager.ts +280 -6
  110. package/src/lib/server/connectors/response-media.ts +3 -0
  111. package/src/lib/server/connectors/session-consolidation.ts +4 -4
  112. package/src/lib/server/context-manager.test.ts +19 -0
  113. package/src/lib/server/context-manager.ts +10 -3
  114. package/src/lib/server/dag-validation.ts +10 -0
  115. package/src/lib/server/elevenlabs.ts +1 -1
  116. package/src/lib/server/eval/agent-regression.ts +9 -9
  117. package/src/lib/server/integrity-monitor.ts +4 -1
  118. package/src/lib/server/memory/memory-consolidation.test.ts +89 -0
  119. package/src/lib/server/memory/memory-consolidation.ts +21 -0
  120. package/src/lib/server/memory/memory-db.ts +46 -8
  121. package/src/lib/server/memory/memory-graph.ts +5 -1
  122. package/src/lib/server/memory/temporal-decay.ts +7 -1
  123. package/src/lib/server/missions/mission-intent.test.ts +63 -0
  124. package/src/lib/server/missions/mission-intent.ts +569 -0
  125. package/src/lib/server/missions/mission-service.test.ts +881 -0
  126. package/src/lib/server/missions/mission-service.ts +2254 -0
  127. package/src/lib/server/openclaw/gateway.test.ts +157 -1
  128. package/src/lib/server/openclaw/gateway.ts +55 -34
  129. package/src/lib/server/protocols/protocol-service.test.ts +585 -0
  130. package/src/lib/server/protocols/protocol-service.ts +2765 -0
  131. package/src/lib/server/provider-health.test.ts +38 -0
  132. package/src/lib/server/provider-health.ts +33 -4
  133. package/src/lib/server/query-expansion.ts +2 -1
  134. package/src/lib/server/resolve-image.ts +41 -9
  135. package/src/lib/server/runtime/daemon-guards.test.ts +74 -0
  136. package/src/lib/server/runtime/daemon-state.ts +168 -19
  137. package/src/lib/server/runtime/estop.test.ts +98 -0
  138. package/src/lib/server/runtime/estop.ts +199 -0
  139. package/src/lib/server/runtime/process-manager.ts +45 -2
  140. package/src/lib/server/runtime/queue.test.ts +25 -0
  141. package/src/lib/server/runtime/queue.ts +114 -14
  142. package/src/lib/server/runtime/run-ledger.ts +108 -0
  143. package/src/lib/server/runtime/scheduler.test.ts +161 -0
  144. package/src/lib/server/runtime/scheduler.ts +35 -35
  145. package/src/lib/server/runtime/session-run-manager.test.ts +178 -0
  146. package/src/lib/server/runtime/session-run-manager.ts +439 -52
  147. package/src/lib/server/schedules/schedule-normalization.ts +23 -3
  148. package/src/lib/server/schedules/schedule-service.ts +1 -1
  149. package/src/lib/server/session-tools/context-mgmt.ts +3 -1
  150. package/src/lib/server/session-tools/crud.ts +41 -6
  151. package/src/lib/server/session-tools/delegate.ts +3 -43
  152. package/src/lib/server/session-tools/file-normalize.test.ts +18 -0
  153. package/src/lib/server/session-tools/file.ts +6 -0
  154. package/src/lib/server/session-tools/memory.ts +23 -0
  155. package/src/lib/server/skills/skill-audit.test.ts +33 -0
  156. package/src/lib/server/skills/skill-audit.ts +145 -0
  157. package/src/lib/server/storage.ts +325 -22
  158. package/src/lib/server/tasks/task-lifecycle.ts +2 -1
  159. package/src/lib/server/tasks/task-service.ts +10 -3
  160. package/src/lib/server/tasks/task-validation.ts +2 -2
  161. package/src/lib/server/tool-capability-policy-advanced.test.ts +6 -0
  162. package/src/lib/server/tool-capability-policy.ts +18 -10
  163. package/src/lib/server/tool-loop-detection.ts +9 -9
  164. package/src/lib/server/untrusted-content.ts +113 -0
  165. package/src/lib/validation/schemas.ts +174 -0
  166. package/src/stores/use-chat-store.test.ts +346 -22
  167. package/src/stores/use-chat-store.ts +213 -55
  168. package/src/stores/use-chatroom-store.ts +19 -1
  169. package/src/types/index.ts +620 -3
  170. package/src/views/settings/section-runtime-loop.tsx +28 -0
  171. package/src/components/chat/streaming-bubble.tsx +0 -97
  172. package/src/components/layout/mobile-drawer.tsx +0 -227
package/README.md CHANGED
@@ -17,6 +17,19 @@ Extension tutorial: https://swarmclaw.ai/docs/extension-tutorial
17
17
 
18
18
  ## Release Notes
19
19
 
20
+ ### v1.1.1 Highlights
21
+
22
+ - **Structured Sessions are now contextual**: start bounded structured runs from direct chats, chatrooms, tasks, missions, or schedules, including a new chatroom `/breakout` command that spins up a focused session from the current room with auto-filled participants and kickoff context.
23
+ - **ProtocolRun orchestration matured**: structured sessions now run on the same durable engine for step-based branching, repeat loops, parallel branches, and explicit joins instead of growing a separate orchestration subsystem.
24
+ - **Live-agent runtime hardening**: exact-output contracts, memory preflight behavior, same-channel delivery rendering, inline media, and grounded runtime inspection were all tightened through live-agent validation before release.
25
+
26
+ ### v1.1.0 Highlights
27
+
28
+ - **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.
29
+ - **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.
30
+ - **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.
31
+ - **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.
32
+
20
33
  ### v1.0.9 Highlights
21
34
 
22
35
  - **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.
@@ -27,6 +40,7 @@ Extension tutorial: https://swarmclaw.ai/docs/extension-tutorial
27
40
  ## What SwarmClaw Focuses On
28
41
 
29
42
  - **Delegation and background execution**: delegated work, subagents, durable jobs, checkpointing, and background task execution.
43
+ - **Structured Sessions**: temporary bounded runs for one agent or many, launched from context and backed by durable templates, transcripts, outputs, operator controls, and chatroom breakout flows.
30
44
  - **Autonomy and memory**: heartbeats, schedules, long-running execution, durable memory, reflection memory, human-context learning, document recall, and project-aware context.
31
45
  - **OpenClaw integration**: named gateway profiles, external runtimes, deploy helpers, config sync, approval handling, and OpenClaw agent file editing.
32
46
  - **Runtime skills**: pinned skills, OpenClaw-compatible `SKILL.md` import, on-demand skill execution, and configurable keyword or embedding-based recommendation.
@@ -108,7 +122,8 @@ Then open `http://localhost:3456`.
108
122
 
109
123
  - **Providers**: OpenClaw, OpenAI, Anthropic, Ollama, Google, DeepSeek, Groq, Together, Mistral, xAI, Fireworks, plus compatible custom endpoints.
110
124
  - **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.
125
+ - **Autonomy**: heartbeat loops, schedules, background jobs, task execution, supervisor recovery, mission control, and agent wakeups.
126
+ - **Structured Sessions**: contextual launch from chats, chatrooms, tasks, missions, and schedules, plus reusable templates, chatroom `/breakout`, and durable run state.
112
127
  - **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
128
  - **Wallets**: balances, transfers, signatures, EVM call/quote/swap flows, and approval-gated execution.
114
129
  - **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.9",
3
+ "version": "1.1.1",
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
+ }
@@ -10,7 +10,12 @@ export const dynamic = 'force-dynamic'
10
10
 
11
11
  export async function GET() {
12
12
  const chatrooms = loadChatrooms()
13
- return NextResponse.json(chatrooms)
13
+ const filtered: typeof chatrooms = {}
14
+ for (const [id, chatroom] of Object.entries(chatrooms)) {
15
+ if (chatroom.hidden === true || chatroom.archivedAt) continue
16
+ filtered[id] = chatroom
17
+ }
18
+ return NextResponse.json(filtered)
14
19
  }
15
20
 
16
21
  export async function POST(req: Request) {
@@ -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
+ }