@swarmclawai/swarmclaw 1.2.4 → 1.2.6

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 (262) hide show
  1. package/README.md +14 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/route.test.ts +49 -0
  49. package/src/app/api/providers/ollama/route.ts +6 -5
  50. package/src/app/api/schedules/[id]/route.ts +14 -108
  51. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  52. package/src/app/api/schedules/route.ts +9 -51
  53. package/src/app/api/settings/route.ts +4 -3
  54. package/src/app/api/setup/check-provider/route.ts +23 -1
  55. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  56. package/src/app/api/system/status/route.ts +2 -2
  57. package/src/app/api/tasks/[id]/route.ts +16 -202
  58. package/src/app/api/tasks/bulk/route.ts +5 -86
  59. package/src/app/api/tasks/metrics/route.ts +2 -1
  60. package/src/app/api/tasks/route.ts +11 -171
  61. package/src/app/api/upload/route.ts +1 -1
  62. package/src/app/api/uploads/[filename]/route.ts +1 -1
  63. package/src/app/api/uploads/route.ts +1 -1
  64. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  65. package/src/app/layout.tsx +9 -6
  66. package/src/app/protocols/page.tsx +71 -89
  67. package/src/app/tasks/page.tsx +32 -32
  68. package/src/cli/index.js +1 -0
  69. package/src/cli/spec.js +1 -0
  70. package/src/components/agents/agent-sheet.tsx +5 -5
  71. package/src/components/auth/setup-wizard/index.tsx +4 -4
  72. package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
  73. package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
  74. package/src/components/auth/setup-wizard/utils.ts +1 -1
  75. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  76. package/src/components/connectors/connector-list.tsx +26 -40
  77. package/src/components/connectors/connector-sheet.tsx +95 -149
  78. package/src/components/gateways/gateway-sheet.tsx +61 -110
  79. package/src/components/layout/live-query-sync.tsx +121 -0
  80. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  81. package/src/components/providers/app-query-provider.tsx +17 -0
  82. package/src/components/providers/provider-list.tsx +60 -61
  83. package/src/components/providers/provider-sheet.tsx +74 -56
  84. package/src/components/skills/skill-list.tsx +5 -18
  85. package/src/components/skills/skill-sheet.tsx +21 -20
  86. package/src/components/skills/skills-workspace.tsx +48 -87
  87. package/src/components/tasks/task-card.tsx +20 -13
  88. package/src/components/tasks/task-column.tsx +22 -7
  89. package/src/components/tasks/task-list.tsx +8 -11
  90. package/src/components/tasks/task-sheet.tsx +111 -103
  91. package/src/features/agents/queries.ts +20 -0
  92. package/src/features/chatrooms/queries.ts +20 -0
  93. package/src/features/chats/queries.ts +27 -0
  94. package/src/features/connectors/queries.ts +145 -0
  95. package/src/features/credentials/queries.ts +37 -0
  96. package/src/features/extensions/queries.ts +26 -0
  97. package/src/features/external-agents/queries.ts +36 -0
  98. package/src/features/gateways/queries.ts +274 -0
  99. package/src/features/missions/queries.ts +23 -0
  100. package/src/features/projects/queries.ts +20 -0
  101. package/src/features/protocols/queries.ts +149 -0
  102. package/src/features/providers/queries.ts +142 -0
  103. package/src/features/settings/queries.ts +20 -0
  104. package/src/features/skills/queries.ts +182 -0
  105. package/src/features/tasks/queries.ts +189 -0
  106. package/src/hooks/use-ws.ts +3 -2
  107. package/src/lib/app/api-client.ts +2 -2
  108. package/src/lib/providers/index.test.ts +108 -0
  109. package/src/lib/providers/index.ts +38 -15
  110. package/src/lib/query/client.ts +17 -0
  111. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  112. package/src/lib/server/agents/agent-service.ts +429 -0
  113. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  114. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  115. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  116. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  117. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  118. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  119. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  120. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  121. package/src/lib/server/build-llm.ts +7 -15
  122. package/src/lib/server/capability-router.test.ts +70 -1
  123. package/src/lib/server/capability-router.ts +24 -99
  124. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  125. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  126. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  127. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  128. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  129. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  130. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  131. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  132. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  133. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  134. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  135. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  136. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  137. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  138. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  139. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  140. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  141. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  142. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  143. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  144. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  145. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  146. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  147. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  148. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  149. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  150. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  151. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  152. package/src/lib/server/chats/chat-session-service.ts +410 -0
  153. package/src/lib/server/connectors/access.ts +1 -1
  154. package/src/lib/server/connectors/commands.ts +7 -6
  155. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  156. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  157. package/src/lib/server/connectors/connector-service.ts +453 -0
  158. package/src/lib/server/connectors/delivery.ts +17 -12
  159. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  160. package/src/lib/server/connectors/media.ts +1 -1
  161. package/src/lib/server/connectors/response-media.ts +1 -1
  162. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  163. package/src/lib/server/connectors/session.ts +9 -7
  164. package/src/lib/server/connectors/voice-note.ts +2 -1
  165. package/src/lib/server/context-manager.ts +20 -1
  166. package/src/lib/server/cost.ts +2 -3
  167. package/src/lib/server/credentials/credential-repository.ts +43 -4
  168. package/src/lib/server/credentials/credential-service.ts +112 -0
  169. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  170. package/src/lib/server/daemon/controller.ts +577 -0
  171. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  172. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  173. package/src/lib/server/daemon/types.ts +101 -0
  174. package/src/lib/server/embeddings.ts +3 -9
  175. package/src/lib/server/eval/agent-regression.ts +3 -2
  176. package/src/lib/server/eval/runner.ts +2 -2
  177. package/src/lib/server/execution-brief.test.ts +167 -0
  178. package/src/lib/server/execution-brief.ts +295 -0
  179. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  180. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  181. package/src/lib/server/execution-engine/index.ts +35 -0
  182. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  183. package/src/lib/server/execution-engine/types.ts +33 -0
  184. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  185. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  186. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  187. package/src/lib/server/messages/message-repository.ts +330 -0
  188. package/src/lib/server/missions/mission-service/core.ts +8 -6
  189. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  190. package/src/lib/server/openclaw/doctor.ts +1 -1
  191. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  192. package/src/lib/server/openclaw/gateway.ts +5 -14
  193. package/src/lib/server/openclaw/health.ts +3 -11
  194. package/src/lib/server/openclaw/sync.ts +8 -6
  195. package/src/lib/server/persistence/storage-context.ts +3 -0
  196. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  197. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  198. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  199. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  200. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  201. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  202. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  203. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  204. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  205. package/src/lib/server/protocols/protocol-types.ts +10 -7
  206. package/src/lib/server/provider-endpoint.ts +7 -12
  207. package/src/lib/server/provider-model-discovery.ts +2 -11
  208. package/src/lib/server/query-expansion.ts +5 -6
  209. package/src/lib/server/run-context.test.ts +365 -0
  210. package/src/lib/server/run-context.ts +367 -0
  211. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  212. package/src/lib/server/runtime/queue/core.ts +61 -190
  213. package/src/lib/server/runtime/run-ledger.ts +8 -0
  214. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  215. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  216. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  217. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  218. package/src/lib/server/service-result.ts +16 -0
  219. package/src/lib/server/session-note.ts +2 -3
  220. package/src/lib/server/session-reset-policy.ts +4 -3
  221. package/src/lib/server/session-tools/connector.ts +9 -6
  222. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  223. package/src/lib/server/session-tools/crud.ts +162 -10
  224. package/src/lib/server/session-tools/delegate.ts +1 -1
  225. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  226. package/src/lib/server/session-tools/memory.ts +6 -4
  227. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  228. package/src/lib/server/session-tools/session-info.ts +119 -12
  229. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  230. package/src/lib/server/session-tools/skills.ts +15 -15
  231. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  232. package/src/lib/server/session-tools/subagent.ts +125 -7
  233. package/src/lib/server/session-tools/team-context.ts +4 -3
  234. package/src/lib/server/session-tools/wallet.ts +0 -58
  235. package/src/lib/server/sessions/session-lineage.ts +55 -0
  236. package/src/lib/server/sessions/session-repository.ts +2 -2
  237. package/src/lib/server/skills/learned-skills.ts +24 -23
  238. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  239. package/src/lib/server/skills/skill-repository.ts +136 -13
  240. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  241. package/src/lib/server/storage-normalization.test.ts +44 -267
  242. package/src/lib/server/storage-normalization.ts +75 -0
  243. package/src/lib/server/storage.ts +19 -0
  244. package/src/lib/server/structured-extract.ts +3 -14
  245. package/src/lib/server/tasks/task-followups.ts +16 -11
  246. package/src/lib/server/tasks/task-result.test.ts +25 -29
  247. package/src/lib/server/tasks/task-result.ts +5 -9
  248. package/src/lib/server/tasks/task-route-service.ts +449 -0
  249. package/src/lib/server/text-normalization.ts +41 -0
  250. package/src/lib/server/tool-planning.ts +6 -42
  251. package/src/lib/server/upload-path.ts +5 -0
  252. package/src/lib/server/working-state/extraction.ts +614 -0
  253. package/src/lib/server/working-state/normalization.ts +866 -0
  254. package/src/lib/server/working-state/prompt.ts +60 -0
  255. package/src/lib/server/working-state/repository.ts +38 -0
  256. package/src/lib/server/working-state/service.test.ts +253 -0
  257. package/src/lib/server/working-state/service.ts +293 -0
  258. package/src/lib/validation/schemas.ts +1 -0
  259. package/src/lib/ws-client.ts +3 -3
  260. package/src/stores/slices/task-slice.ts +1 -4
  261. package/src/stores/use-chatroom-store.ts +2 -2
  262. package/src/types/index.ts +277 -12
@@ -1,73 +1,12 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { notFound } from '@/lib/server/collection-helpers'
3
- import { loadSchedule, loadAgents, loadTasks, logActivity, upsertSchedule, upsertTask } from '@/lib/server/storage'
4
- import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
5
- import { enqueueTask } from '@/lib/server/runtime/queue'
6
- import { pushMainLoopEventToMainSessions } from '@/lib/server/agents/main-agent-loop'
7
- import { getScheduleSignatureKey } from '@/lib/schedules/schedule-dedupe'
8
- import { prepareScheduledTaskRun } from '@/lib/server/tasks/task-lifecycle'
9
- import type { Schedule } from '@/types'
10
-
11
- type InFlightTask = {
12
- status?: string
13
- sourceScheduleKey?: string | null
14
- }
3
+ import { runScheduleNow } from '@/lib/server/schedules/schedule-route-service'
15
4
 
16
5
  export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
17
6
  const { id } = await params
18
- const schedule = loadSchedule(id) as Schedule | null
19
- if (!schedule) return notFound()
20
- if (schedule.status === 'archived') {
21
- return NextResponse.json({ error: 'Archived schedules must be restored before they can run.' }, { status: 409 })
22
- }
23
-
24
- const agents = loadAgents()
25
- const agent = agents[schedule.agentId]
26
- if (!agent) return NextResponse.json({ error: 'Agent not found' }, { status: 400 })
27
- if (isAgentDisabled(agent)) {
28
- return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'run schedules') }, { status: 409 })
29
- }
30
-
31
- const tasks = loadTasks()
32
- const scheduleSignature = getScheduleSignatureKey(schedule)
33
- if (scheduleSignature) {
34
- const inFlight = Object.values(tasks as Record<string, InFlightTask>).some((task) =>
35
- task
36
- && (task.status === 'queued' || task.status === 'running')
37
- && task.sourceScheduleKey === scheduleSignature,
38
- )
39
- if (inFlight) {
40
- return NextResponse.json({ ok: true, queued: false, reason: 'in_flight' })
41
- }
42
- }
43
-
44
- const now = Date.now()
45
- schedule.runNumber = (schedule.runNumber || 0) + 1
46
-
47
- const { taskId } = prepareScheduledTaskRun({
48
- schedule,
49
- tasks,
50
- now,
51
- scheduleSignature,
52
- })
53
-
54
- upsertTask(taskId, tasks[taskId])
55
- enqueueTask(taskId)
56
- pushMainLoopEventToMainSessions({
57
- type: 'schedule_fired',
58
- text: `Schedule fired manually: "${schedule.name}" (${schedule.id}) run #${schedule.runNumber} — task ${taskId}`,
59
- })
60
-
61
- schedule.lastRunAt = now
62
- upsertSchedule(schedule.id, schedule)
63
- logActivity({
64
- entityType: 'schedule',
65
- entityId: schedule.id,
66
- action: 'started',
67
- actor: 'user',
68
- summary: `Schedule run started: "${schedule.name}"`,
69
- detail: { taskId, runNumber: schedule.runNumber },
70
- })
71
-
72
- return NextResponse.json({ ok: true, queued: true, taskId, runNumber: schedule.runNumber })
7
+ const result = runScheduleNow(id)
8
+ if (!result.ok && result.status === 404) return notFound()
9
+ return result.ok
10
+ ? NextResponse.json(result.payload)
11
+ : NextResponse.json(result.payload, { status: result.status })
73
12
  }
@@ -1,64 +1,22 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadAgents, loadSchedules, logActivity, upsertSchedule, upsertSchedules } from '@/lib/server/storage'
3
- import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
4
- import { WORKSPACE_DIR } from '@/lib/server/data-dir'
5
- import { prepareScheduleCreate } from '@/lib/server/schedules/schedule-service'
6
- import { notify } from '@/lib/server/ws-hub'
7
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
+ import {
4
+ createScheduleFromRoute,
5
+ listSchedulesForApi,
6
+ } from '@/lib/server/schedules/schedule-route-service'
8
7
  export const dynamic = 'force-dynamic'
9
8
 
10
9
  export async function GET(req: Request) {
11
10
  const { searchParams } = new URL(req.url)
12
11
  const includeArchived = searchParams.get('includeArchived') === 'true'
13
- const schedules = loadSchedules()
14
- if (includeArchived) return NextResponse.json(schedules)
15
-
16
- const filtered: typeof schedules = {}
17
- for (const [id, schedule] of Object.entries(schedules)) {
18
- if (schedule.status === 'archived') continue
19
- filtered[id] = schedule
20
- }
21
- return NextResponse.json(filtered)
12
+ return NextResponse.json(listSchedulesForApi(includeArchived))
22
13
  }
23
14
 
24
15
  export async function POST(req: Request) {
25
16
  const { data: body, error } = await safeParseBody(req)
26
17
  if (error) return error
27
- const now = Date.now()
28
- const schedules = loadSchedules()
29
- const agents = loadAgents()
30
- const candidateAgentId = typeof body?.agentId === 'string' ? body.agentId.trim() : ''
31
- const agent = agents[candidateAgentId]
32
- if (!agent) {
33
- return NextResponse.json({ error: `Agent not found: ${String(body?.agentId)}` }, { status: 400 })
34
- }
35
- if (isAgentDisabled(agent)) {
36
- return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'take scheduled work') }, { status: 409 })
37
- }
38
- const prepared = prepareScheduleCreate({
39
- input: body as Record<string, unknown>,
40
- schedules,
41
- now,
42
- cwd: WORKSPACE_DIR,
43
- })
44
- if (!prepared.ok) {
45
- return NextResponse.json({ error: prepared.error }, { status: 400 })
46
- }
47
- if (prepared.kind === 'duplicate') {
48
- if (prepared.entries.length === 1) upsertSchedule(prepared.scheduleId, prepared.schedule)
49
- else if (prepared.entries.length > 1) upsertSchedules(prepared.entries)
50
- if (prepared.entries.length > 0) notify('schedules')
51
- return NextResponse.json(prepared.schedule)
52
- }
53
-
54
- upsertSchedule(prepared.scheduleId, prepared.schedule)
55
- logActivity({
56
- entityType: 'schedule',
57
- entityId: prepared.scheduleId,
58
- action: 'created',
59
- actor: 'user',
60
- summary: `Schedule created: "${prepared.schedule.name}"`,
61
- })
62
- notify('schedules')
63
- return NextResponse.json(prepared.schedule)
18
+ const result = createScheduleFromRoute(body as Record<string, unknown>)
19
+ return result.ok
20
+ ? NextResponse.json(result.payload)
21
+ : NextResponse.json(result.payload, { status: result.status })
64
22
  }
@@ -5,6 +5,7 @@ import { normalizeWhatsAppApprovedContacts } from '@/lib/server/connectors/pairi
5
5
  import { loadPublicSettings, loadSettings, saveSettings } from '@/lib/server/storage'
6
6
  import { normalizeRuntimeSettingFields } from '@/lib/runtime/runtime-loop'
7
7
  import { normalizeSupervisorSettings } from '@/lib/autonomy/supervisor-settings'
8
+ import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
8
9
  export const dynamic = 'force-dynamic'
9
10
 
10
11
 
@@ -166,9 +167,9 @@ export async function PUT(req: Request) {
166
167
  saveSettings(settings)
167
168
 
168
169
  if ('daemonAutostartEnabled' in sanitizedBody && settings.daemonAutostartEnabled) {
169
- import('@/lib/server/runtime/daemon-state').then(({ startDaemon }) => {
170
- startDaemon({ source: 'api/settings:put:daemon-autostart', manualStart: true })
171
- }).catch(() => { /* daemon runtime may not be initialized yet */ })
170
+ void ensureDaemonProcessRunning('api/settings:put:daemon-autostart', { manualStart: true }).catch(() => {
171
+ // The daemon launcher may not be available during early bootstrap.
172
+ })
172
173
  }
173
174
 
174
175
  // Restart heartbeat service when heartbeat-related settings change
@@ -326,8 +326,30 @@ export async function POST(req: Request) {
326
326
  const result = await checkOpenClaw(apiKey, endpoint)
327
327
  return NextResponse.json(result)
328
328
  }
329
- default:
329
+ default: {
330
+ let configs: Record<string, { name?: string; baseUrl?: string; isEnabled?: boolean }>
331
+ try {
332
+ const storage = await import('@/lib/server/storage')
333
+ configs = storage.loadProviderConfigs() as Record<string, { name?: string; baseUrl?: string; isEnabled?: boolean }>
334
+ } catch {
335
+ return NextResponse.json(
336
+ { ok: false, message: `Failed to load provider configurations while checking ${provider}.` },
337
+ { status: 500 },
338
+ )
339
+ }
340
+ const custom = configs[provider]
341
+ if (custom?.baseUrl) {
342
+ const result = await checkOpenAiCompatible(
343
+ custom.name || 'Custom Provider',
344
+ apiKey || '',
345
+ endpoint || custom.baseUrl,
346
+ custom.baseUrl,
347
+ model
348
+ )
349
+ return NextResponse.json(result)
350
+ }
330
351
  return NextResponse.json({ ok: false, message: `Unsupported provider: ${provider}` }, { status: 400 })
352
+ }
331
353
  }
332
354
  } catch (err: unknown) {
333
355
  const message = err instanceof Error && err.name === 'TimeoutError'
@@ -7,7 +7,7 @@ export async function GET(_req: Request) {
7
7
  try {
8
8
  const deviceId = getDeviceId()
9
9
  return NextResponse.json({ deviceId })
10
- } catch (err: any) {
11
- return NextResponse.json({ deviceId: null, error: err?.message }, { status: 500 })
10
+ } catch (err: unknown) {
11
+ return NextResponse.json({ deviceId: null, error: err instanceof Error ? err.message : 'Unknown error' }, { status: 500 })
12
12
  }
13
13
  }
@@ -1,9 +1,9 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { getDaemonHealthSummary } from '@/lib/server/runtime/daemon-state'
2
+ import { getDaemonHealthSummarySnapshot } from '@/lib/server/daemon/controller'
3
3
  import packageJson from '../../../../../package.json'
4
4
 
5
5
  export async function GET() {
6
- const summary = getDaemonHealthSummary()
6
+ const summary = await getDaemonHealthSummarySnapshot()
7
7
  return NextResponse.json({
8
8
  ...summary,
9
9
  version: packageJson.version,
@@ -1,221 +1,35 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import { loadAgents, loadSettings, loadTasks, logActivity, upsertStoredItems, upsertTask } from '@/lib/server/storage'
4
3
  import { notFound } from '@/lib/server/collection-helpers'
5
- import { disableSessionHeartbeat, enqueueTask, recoverStalledRunningTasks, validateCompletedTasksQueue } from '@/lib/server/runtime/queue'
6
- import { pushMainLoopEventToMainSessions } from '@/lib/server/agents/main-agent-loop'
7
- import { notify } from '@/lib/server/ws-hub'
8
- import { createNotification } from '@/lib/server/create-notification'
9
- import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
10
- import { dispatchWake } from '@/lib/server/runtime/wake-dispatcher'
11
- import { validateDag, cascadeUnblock } from '@/lib/server/dag-validation'
12
- import { getExtensionManager } from '@/lib/server/extensions'
13
- import { getEnabledCapabilityIds } from '@/lib/capability-selection'
4
+ import { loadTask } from '@/lib/server/tasks/task-repository'
14
5
  import {
15
- applyTaskPatch,
16
- } from '@/lib/server/tasks/task-service'
17
- import type { BoardTask } from '@/types'
18
- import { ensureMissionForTask, enrichTaskWithMissionSummary, noteMissionTaskFinished } from '@/lib/server/missions/mission-service'
19
- import '@/lib/server/builtin-extensions'
6
+ archiveTaskFromRoute,
7
+ prepareTasksForListing,
8
+ updateTaskFromRoute,
9
+ } from '@/lib/server/tasks/task-route-service'
20
10
 
21
11
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
22
- // Keep completed queue integrity even if daemon is not running.
23
- validateCompletedTasksQueue()
24
- recoverStalledRunningTasks()
25
-
26
12
  const { id } = await params
27
- const tasks = loadTasks()
13
+ const tasks = prepareTasksForListing()
28
14
  if (!tasks[id]) return notFound()
29
- return NextResponse.json(enrichTaskWithMissionSummary(tasks[id]))
15
+ return NextResponse.json(tasks[id])
30
16
  }
31
17
 
32
18
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
33
19
  const { id } = await params
34
20
  const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
35
21
  if (error) return error
36
- const settings = loadSettings()
37
- const tasks = loadTasks()
38
- if (!tasks[id]) return notFound()
39
-
40
- const prevStatus = tasks[id].status
41
-
42
- // DAG validation: reject if proposed blockedBy would create a cycle
43
- if (Array.isArray(body.blockedBy)) {
44
- const dagResult = validateDag(tasks, id, body.blockedBy)
45
- if (!dagResult.valid) {
46
- return NextResponse.json(
47
- { error: 'Dependency cycle detected', cycle: dagResult.cycle },
48
- { status: 400 },
49
- )
50
- }
51
- }
52
-
53
- // Support atomic comment append to avoid race conditions
54
- if (body.appendComment) {
55
- if (!tasks[id].comments) tasks[id].comments = []
56
- tasks[id].comments.push(body.appendComment)
57
- tasks[id].updatedAt = Date.now()
58
- } else {
59
- applyTaskPatch({
60
- task: tasks[id],
61
- patch: body as Record<string, unknown>,
62
- now: Date.now(),
63
- settings,
64
- preserveCompletedAt: true,
65
- clearProjectIdWhenNull: true,
66
- invalidCompletionCommentAuthor: 'System',
67
- })
68
- }
69
- tasks[id].id = id // prevent id overwrite
70
-
71
- // Maintain parent/child subtask links
72
- if (typeof body.parentTaskId === 'string' || body.parentTaskId === null) {
73
- const oldParentId = tasks[id].parentTaskId
74
- const newParentId = typeof body.parentTaskId === 'string' && body.parentTaskId.trim() ? body.parentTaskId.trim() : null
75
- // Remove from old parent's subtaskIds
76
- if (oldParentId && oldParentId !== newParentId && tasks[oldParentId]) {
77
- const oldSubs = Array.isArray(tasks[oldParentId].subtaskIds) ? tasks[oldParentId].subtaskIds : []
78
- tasks[oldParentId].subtaskIds = oldSubs.filter((s: string) => s !== id)
79
- tasks[oldParentId].updatedAt = Date.now()
80
- upsertTask(oldParentId, tasks[oldParentId])
81
- }
82
- // Add to new parent's subtaskIds
83
- if (newParentId && tasks[newParentId]) {
84
- const newSubs = Array.isArray(tasks[newParentId].subtaskIds) ? tasks[newParentId].subtaskIds : []
85
- if (!newSubs.includes(id)) {
86
- tasks[newParentId].subtaskIds = [...newSubs, id]
87
- tasks[newParentId].updatedAt = Date.now()
88
- upsertTask(newParentId, tasks[newParentId])
89
- }
90
- }
91
- tasks[id].parentTaskId = newParentId
92
- }
93
-
94
- // Set archivedAt when transitioning to archived
95
- if (prevStatus !== 'archived' && tasks[id].status === 'archived') {
96
- tasks[id].archivedAt = Date.now()
97
- }
98
-
99
- upsertTask(id, tasks[id])
100
- const mission = ensureMissionForTask(tasks[id], { source: 'manual' })
101
- if (tasks[id].status === 'completed' || tasks[id].status === 'failed' || tasks[id].status === 'cancelled') {
102
- noteMissionTaskFinished(tasks[id], tasks[id].status, tasks[id].id)
103
- }
104
- logActivity({ entityType: 'task', entityId: id, action: 'updated', actor: 'user', summary: `Task updated: "${tasks[id].title}" (${prevStatus} → ${tasks[id].status})` })
105
- if (prevStatus !== tasks[id].status) {
106
- pushMainLoopEventToMainSessions({
107
- type: 'task_status_changed',
108
- text: `Task "${tasks[id].title}" (${id}) moved ${prevStatus} → ${tasks[id].status}.`,
109
- })
110
- }
111
-
112
- if (prevStatus !== tasks[id].status && tasks[id].status === 'cancelled') {
113
- disableSessionHeartbeat(tasks[id].sessionId)
114
- notify('tasks')
115
- return NextResponse.json(enrichTaskWithMissionSummary({
116
- ...tasks[id],
117
- missionId: mission?.id || tasks[id].missionId || null,
118
- }))
119
- }
120
-
121
- // If task is manually transitioned to a terminal status, disable session heartbeat.
122
- if (prevStatus !== tasks[id].status && (tasks[id].status === 'completed' || tasks[id].status === 'failed')) {
123
- disableSessionHeartbeat(tasks[id].sessionId)
124
- createNotification({
125
- type: tasks[id].status === 'completed' ? 'success' : 'error',
126
- title: `Task ${tasks[id].status}: "${tasks[id].title}"`,
127
- message: tasks[id].status === 'failed' ? tasks[id].error?.slice(0, 200) : undefined,
128
- entityType: 'task',
129
- entityId: id,
130
- })
131
-
132
- if (tasks[id].status === 'completed') {
133
- const agentExtensions = tasks[id].agentId ? getEnabledCapabilityIds(loadAgents()[tasks[id].agentId]) : []
134
- getExtensionManager().runHook(
135
- 'onTaskComplete',
136
- { taskId: id, result: tasks[id].result },
137
- { enabledIds: agentExtensions },
138
- )
139
- }
140
-
141
- // Enqueue system event + heartbeat wake
142
- if (tasks[id].sessionId) {
143
- enqueueSystemEvent(tasks[id].sessionId, `Task ${tasks[id].status}: ${tasks[id].title}`)
144
- }
145
- if (tasks[id].agentId) {
146
- dispatchWake({
147
- mode: 'immediate',
148
- agentId: tasks[id].agentId,
149
- sessionId: tasks[id].sessionId || undefined,
150
- eventId: `task:${id}:${tasks[id].status}`,
151
- reason: 'task-completed',
152
- source: `task:${id}`,
153
- resumeMessage: `Task ${tasks[id].status}: ${tasks[id].title}`,
154
- detail: tasks[id].status === 'failed'
155
- ? String(tasks[id].error || '').slice(0, 400)
156
- : JSON.stringify(tasks[id].result || '').slice(0, 400),
157
- })
158
- }
159
- }
160
-
161
- // Dependency check: cannot queue a task if any blocker is incomplete
162
- if (tasks[id].status === 'queued') {
163
- const blockers = Array.isArray(tasks[id].blockedBy) ? tasks[id].blockedBy : []
164
- const incompleteBlocker = blockers.find((bid: string) => tasks[bid] && tasks[bid].status !== 'completed')
165
- if (incompleteBlocker) {
166
- // Revert status change and reject
167
- tasks[id].status = prevStatus
168
- tasks[id].updatedAt = Date.now()
169
- upsertTask(id, tasks[id])
170
- return NextResponse.json(
171
- { error: 'Cannot queue: blocked by incomplete tasks', blockedBy: incompleteBlocker },
172
- { status: 409 },
173
- )
174
- }
175
- }
176
-
177
- // When a task is completed, cascade unblock dependent tasks
178
- if (tasks[id].status === 'completed') {
179
- const unblockedIds = cascadeUnblock(tasks, id)
180
- if (unblockedIds.length > 0) {
181
- upsertStoredItems('tasks', [
182
- [id, tasks[id]],
183
- ...unblockedIds.map((uid) => [uid, tasks[uid]] as [string, BoardTask]),
184
- ])
185
- for (const uid of unblockedIds) {
186
- enqueueTask(uid)
187
- }
188
- }
189
- }
190
-
191
- // If status changed to 'queued', enqueue it
192
- if (prevStatus !== 'queued' && tasks[id].status === 'queued') {
193
- enqueueTask(id)
194
- }
195
-
196
- notify('tasks')
197
- return NextResponse.json(enrichTaskWithMissionSummary({
198
- ...tasks[id],
199
- missionId: mission?.id || tasks[id].missionId || null,
200
- }))
22
+ const result = updateTaskFromRoute(id, body)
23
+ if (!result.ok && result.status === 404) return notFound()
24
+ return result.ok
25
+ ? NextResponse.json(result.payload)
26
+ : NextResponse.json(result.payload, { status: result.status })
201
27
  }
202
28
 
203
29
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
204
30
  const { id } = await params
205
- const tasks = loadTasks()
206
- if (!tasks[id]) return notFound()
207
-
208
- // Soft delete: move to archived status instead of hard delete
209
- tasks[id].status = 'archived'
210
- tasks[id].archivedAt = Date.now()
211
- tasks[id].updatedAt = Date.now()
212
- upsertTask(id, tasks[id])
213
- logActivity({ entityType: 'task', entityId: id, action: 'deleted', actor: 'user', summary: `Task archived: "${tasks[id].title}"` })
214
- pushMainLoopEventToMainSessions({
215
- type: 'task_archived',
216
- text: `Task archived: "${tasks[id].title}" (${id}).`,
217
- })
218
-
219
- notify('tasks')
220
- return NextResponse.json(tasks[id])
31
+ if (!loadTask(id)) return notFound()
32
+ const result = archiveTaskFromRoute(id)
33
+ if (!result.ok) return notFound()
34
+ return NextResponse.json(result.payload)
221
35
  }
@@ -1,13 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import { loadTasks, logActivity, upsertStoredItems } from '@/lib/server/storage'
4
- import { enqueueTask, disableSessionHeartbeat } from '@/lib/server/runtime/queue'
5
- import { pushMainLoopEventToMainSessions } from '@/lib/server/agents/main-agent-loop'
6
- import { notify } from '@/lib/server/ws-hub'
7
- import { createNotification } from '@/lib/server/create-notification'
8
- import type { BoardTask, BoardTaskStatus } from '@/types'
9
-
10
- const VALID_STATUSES: BoardTaskStatus[] = ['backlog', 'queued', 'running', 'completed', 'failed', 'archived']
3
+ import { bulkUpdateTasksFromRoute } from '@/lib/server/tasks/task-route-service'
11
4
 
12
5
  /**
13
6
  * Bulk update tasks — batch status changes, agent/project reassignment, or archive/delete.
@@ -21,82 +14,8 @@ const VALID_STATUSES: BoardTaskStatus[] = ['backlog', 'queued', 'running', 'comp
21
14
  export async function POST(req: Request) {
22
15
  const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
23
16
  if (error) return error
24
- const ids: unknown = body.ids
25
- if (!Array.isArray(ids) || ids.length === 0) {
26
- return NextResponse.json({ error: 'ids must be a non-empty array' }, { status: 400 })
27
- }
28
-
29
- const taskIds = ids.filter((id): id is string => typeof id === 'string')
30
- if (taskIds.length === 0) {
31
- return NextResponse.json({ error: 'No valid task IDs provided' }, { status: 400 })
32
- }
33
-
34
- const tasks = loadTasks()
35
- let updated = 0
36
- const results: string[] = []
37
-
38
- for (const id of taskIds) {
39
- if (!tasks[id]) continue
40
- const prevStatus = tasks[id].status
41
-
42
- if (typeof body.status === 'string' && VALID_STATUSES.includes(body.status as BoardTaskStatus)) {
43
- tasks[id].status = body.status as BoardTaskStatus
44
- if (body.status === 'archived' && prevStatus !== 'archived') {
45
- tasks[id].archivedAt = Date.now()
46
- }
47
- }
48
-
49
- if ('agentId' in body) {
50
- tasks[id].agentId = body.agentId === null ? '' : String(body.agentId)
51
- }
52
-
53
- if ('projectId' in body) {
54
- if (body.projectId === null) {
55
- delete tasks[id].projectId
56
- } else {
57
- tasks[id].projectId = String(body.projectId)
58
- }
59
- }
60
-
61
- tasks[id].updatedAt = Date.now()
62
- updated++
63
- results.push(id)
64
-
65
- // Side-effects for status transitions
66
- if (prevStatus !== tasks[id].status) {
67
- logActivity({
68
- entityType: 'task',
69
- entityId: id,
70
- action: 'updated',
71
- actor: 'user',
72
- summary: `Bulk update: "${tasks[id].title}" (${prevStatus} → ${tasks[id].status})`,
73
- })
74
- pushMainLoopEventToMainSessions({
75
- type: 'task_status_changed',
76
- text: `Task "${tasks[id].title}" (${id}) moved ${prevStatus} → ${tasks[id].status}.`,
77
- })
78
- if (tasks[id].status === 'completed' || tasks[id].status === 'failed') {
79
- disableSessionHeartbeat(tasks[id].sessionId)
80
- }
81
- if (prevStatus !== 'queued' && tasks[id].status === 'queued') {
82
- enqueueTask(id)
83
- }
84
- }
85
- }
86
-
87
- upsertStoredItems('tasks', results.map((id) => [id, tasks[id]] as [string, BoardTask]))
88
-
89
- if (updated > 0) {
90
- const action = body.status
91
- ? `moved ${updated} task(s) to ${body.status}`
92
- : `updated ${updated} task(s)`
93
- createNotification({
94
- type: 'success',
95
- title: `Bulk update: ${action}`,
96
- entityType: 'task',
97
- })
98
- }
99
-
100
- notify('tasks')
101
- return NextResponse.json({ updated, ids: results })
17
+ const result = bulkUpdateTasksFromRoute(body)
18
+ return result.ok
19
+ ? NextResponse.json(result.payload)
20
+ : NextResponse.json(result.payload, { status: result.status })
102
21
  }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadTasks, loadAgents } from '@/lib/server/storage'
2
+ import { loadAgents } from '@/lib/server/agents/agent-repository'
3
+ import { loadTasks } from '@/lib/server/tasks/task-repository'
3
4
 
4
5
  type Range = '24h' | '7d' | '30d'
5
6