@swarmclawai/swarmclaw 1.2.3 → 1.2.5

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 (273) hide show
  1. package/README.md +20 -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]/models/route.test.ts +60 -0
  49. package/src/app/api/providers/[id]/models/route.ts +33 -1
  50. package/src/app/api/providers/[id]/route.test.ts +49 -0
  51. package/src/app/api/providers/[id]/route.ts +30 -1
  52. package/src/app/api/providers/ollama/route.ts +6 -5
  53. package/src/app/api/schedules/[id]/route.ts +14 -108
  54. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  55. package/src/app/api/schedules/route.ts +9 -51
  56. package/src/app/api/settings/route.ts +4 -3
  57. package/src/app/api/setup/check-provider/route.ts +15 -1
  58. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  59. package/src/app/api/system/status/route.ts +2 -2
  60. package/src/app/api/tasks/[id]/route.ts +16 -202
  61. package/src/app/api/tasks/bulk/route.ts +5 -86
  62. package/src/app/api/tasks/metrics/route.ts +2 -1
  63. package/src/app/api/tasks/route.ts +11 -171
  64. package/src/app/api/upload/route.ts +1 -1
  65. package/src/app/api/uploads/[filename]/route.ts +1 -1
  66. package/src/app/api/uploads/route.ts +1 -1
  67. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  68. package/src/app/layout.tsx +9 -6
  69. package/src/app/protocols/page.tsx +71 -89
  70. package/src/app/tasks/page.tsx +32 -32
  71. package/src/cli/index.js +1 -0
  72. package/src/cli/spec.js +1 -0
  73. package/src/components/agents/agent-sheet.tsx +51 -25
  74. package/src/components/agents/inspector-panel.tsx +15 -4
  75. package/src/components/auth/setup-wizard/index.tsx +27 -18
  76. package/src/components/auth/setup-wizard/shared.tsx +2 -2
  77. package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
  78. package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
  79. package/src/components/auth/setup-wizard/types.ts +6 -4
  80. package/src/components/auth/setup-wizard/utils.test.ts +38 -8
  81. package/src/components/auth/setup-wizard/utils.ts +14 -8
  82. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  83. package/src/components/connectors/connector-list.tsx +26 -40
  84. package/src/components/connectors/connector-sheet.tsx +95 -149
  85. package/src/components/gateways/gateway-sheet.tsx +61 -110
  86. package/src/components/layout/live-query-sync.tsx +121 -0
  87. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  88. package/src/components/providers/app-query-provider.tsx +17 -0
  89. package/src/components/providers/provider-list.tsx +150 -77
  90. package/src/components/providers/provider-sheet.tsx +102 -77
  91. package/src/components/shared/model-combobox.tsx +5 -4
  92. package/src/components/skills/skill-list.tsx +5 -18
  93. package/src/components/skills/skill-sheet.tsx +21 -20
  94. package/src/components/skills/skills-workspace.tsx +48 -87
  95. package/src/components/tasks/task-card.tsx +20 -13
  96. package/src/components/tasks/task-column.tsx +22 -7
  97. package/src/components/tasks/task-list.tsx +8 -11
  98. package/src/components/tasks/task-sheet.tsx +111 -103
  99. package/src/features/agents/queries.ts +20 -0
  100. package/src/features/chatrooms/queries.ts +20 -0
  101. package/src/features/chats/queries.ts +27 -0
  102. package/src/features/connectors/queries.ts +145 -0
  103. package/src/features/credentials/queries.ts +37 -0
  104. package/src/features/extensions/queries.ts +26 -0
  105. package/src/features/external-agents/queries.ts +36 -0
  106. package/src/features/gateways/queries.ts +274 -0
  107. package/src/features/missions/queries.ts +23 -0
  108. package/src/features/projects/queries.ts +20 -0
  109. package/src/features/protocols/queries.ts +149 -0
  110. package/src/features/providers/queries.ts +142 -0
  111. package/src/features/settings/queries.ts +20 -0
  112. package/src/features/skills/queries.ts +182 -0
  113. package/src/features/tasks/queries.ts +189 -0
  114. package/src/hooks/use-ws.ts +3 -2
  115. package/src/lib/agent-provider-options.test.ts +152 -0
  116. package/src/lib/agent-provider-options.ts +84 -0
  117. package/src/lib/app/api-client.ts +2 -2
  118. package/src/lib/providers/index.test.ts +78 -0
  119. package/src/lib/providers/index.ts +13 -10
  120. package/src/lib/query/client.ts +17 -0
  121. package/src/lib/server/agents/agent-runtime-config.ts +6 -6
  122. package/src/lib/server/agents/agent-service.ts +429 -0
  123. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  124. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  125. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  126. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  127. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  128. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  129. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  130. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  131. package/src/lib/server/build-llm.ts +7 -15
  132. package/src/lib/server/capability-router.test.ts +70 -1
  133. package/src/lib/server/capability-router.ts +24 -99
  134. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  135. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  136. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  137. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  138. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  139. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  140. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  141. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  142. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  143. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  144. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  145. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  146. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  147. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  148. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  149. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  150. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  151. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  152. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  153. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  154. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  155. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  156. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  157. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  158. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  159. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  160. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  161. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  162. package/src/lib/server/chats/chat-session-service.ts +410 -0
  163. package/src/lib/server/connectors/access.ts +1 -1
  164. package/src/lib/server/connectors/commands.ts +7 -6
  165. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  166. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  167. package/src/lib/server/connectors/connector-service.ts +453 -0
  168. package/src/lib/server/connectors/delivery.ts +17 -12
  169. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  170. package/src/lib/server/connectors/media.ts +1 -1
  171. package/src/lib/server/connectors/response-media.ts +1 -1
  172. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  173. package/src/lib/server/connectors/session.ts +9 -7
  174. package/src/lib/server/connectors/voice-note.ts +2 -1
  175. package/src/lib/server/context-manager.ts +20 -1
  176. package/src/lib/server/cost.ts +2 -3
  177. package/src/lib/server/credentials/credential-repository.ts +43 -4
  178. package/src/lib/server/credentials/credential-service.ts +112 -0
  179. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  180. package/src/lib/server/daemon/controller.ts +577 -0
  181. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  182. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  183. package/src/lib/server/daemon/types.ts +101 -0
  184. package/src/lib/server/embeddings.ts +3 -9
  185. package/src/lib/server/eval/agent-regression.ts +3 -2
  186. package/src/lib/server/eval/runner.ts +2 -2
  187. package/src/lib/server/execution-brief.test.ts +167 -0
  188. package/src/lib/server/execution-brief.ts +295 -0
  189. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  190. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  191. package/src/lib/server/execution-engine/index.ts +35 -0
  192. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  193. package/src/lib/server/execution-engine/types.ts +33 -0
  194. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  195. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  196. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  197. package/src/lib/server/messages/message-repository.ts +330 -0
  198. package/src/lib/server/missions/mission-service/core.ts +8 -6
  199. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  200. package/src/lib/server/openclaw/doctor.ts +1 -1
  201. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  202. package/src/lib/server/openclaw/gateway.ts +5 -14
  203. package/src/lib/server/openclaw/health.ts +3 -11
  204. package/src/lib/server/openclaw/sync.ts +8 -6
  205. package/src/lib/server/persistence/storage-context.ts +3 -0
  206. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  207. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  208. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  209. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  210. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  211. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  212. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  213. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  214. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  215. package/src/lib/server/protocols/protocol-types.ts +10 -7
  216. package/src/lib/server/provider-endpoint.ts +7 -12
  217. package/src/lib/server/provider-model-discovery.ts +2 -11
  218. package/src/lib/server/query-expansion.ts +5 -6
  219. package/src/lib/server/run-context.test.ts +365 -0
  220. package/src/lib/server/run-context.ts +367 -0
  221. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  222. package/src/lib/server/runtime/queue/core.ts +61 -190
  223. package/src/lib/server/runtime/run-ledger.ts +8 -0
  224. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  225. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  226. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  227. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  228. package/src/lib/server/service-result.ts +16 -0
  229. package/src/lib/server/session-note.ts +2 -3
  230. package/src/lib/server/session-reset-policy.ts +4 -3
  231. package/src/lib/server/session-tools/connector.ts +9 -6
  232. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  233. package/src/lib/server/session-tools/crud.ts +162 -10
  234. package/src/lib/server/session-tools/delegate.ts +1 -1
  235. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  236. package/src/lib/server/session-tools/memory.ts +6 -4
  237. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  238. package/src/lib/server/session-tools/session-info.ts +119 -12
  239. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  240. package/src/lib/server/session-tools/skills.ts +15 -15
  241. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  242. package/src/lib/server/session-tools/subagent.ts +125 -7
  243. package/src/lib/server/session-tools/team-context.ts +4 -3
  244. package/src/lib/server/session-tools/wallet.ts +0 -58
  245. package/src/lib/server/sessions/session-lineage.ts +55 -0
  246. package/src/lib/server/sessions/session-repository.ts +2 -2
  247. package/src/lib/server/skills/learned-skills.ts +24 -23
  248. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  249. package/src/lib/server/skills/skill-repository.ts +136 -13
  250. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  251. package/src/lib/server/storage-normalization.test.ts +42 -215
  252. package/src/lib/server/storage-normalization.ts +98 -0
  253. package/src/lib/server/storage.ts +19 -0
  254. package/src/lib/server/structured-extract.ts +3 -14
  255. package/src/lib/server/tasks/task-followups.ts +16 -11
  256. package/src/lib/server/tasks/task-result.test.ts +25 -29
  257. package/src/lib/server/tasks/task-result.ts +5 -9
  258. package/src/lib/server/tasks/task-route-service.ts +449 -0
  259. package/src/lib/server/text-normalization.ts +41 -0
  260. package/src/lib/server/tool-planning.ts +6 -42
  261. package/src/lib/server/upload-path.ts +5 -0
  262. package/src/lib/server/working-state/extraction.ts +614 -0
  263. package/src/lib/server/working-state/normalization.ts +866 -0
  264. package/src/lib/server/working-state/prompt.ts +60 -0
  265. package/src/lib/server/working-state/repository.ts +38 -0
  266. package/src/lib/server/working-state/service.test.ts +253 -0
  267. package/src/lib/server/working-state/service.ts +293 -0
  268. package/src/lib/validation/schemas.ts +1 -0
  269. package/src/lib/ws-client.ts +3 -3
  270. package/src/stores/slices/task-slice.ts +1 -4
  271. package/src/stores/use-chatroom-store.ts +2 -2
  272. package/src/types/index.ts +288 -22
  273. package/src/views/settings/section-providers.tsx +2 -2
@@ -5,13 +5,20 @@ import { genId } from '@/lib/id'
5
5
  import type { Agent, Session, Skill, SkillSuggestion } from '@/types'
6
6
  import { errorMessage } from '@/lib/shared-utils'
7
7
  import {
8
- loadAgents,
9
- loadSessions,
8
+ loadAgent,
9
+ } from '@/lib/server/agents/agent-repository'
10
+ import {
11
+ loadSession,
12
+ } from '@/lib/server/sessions/session-repository'
13
+ import { getMessages, getRecentMessages } from '@/lib/server/messages/message-repository'
14
+ import {
15
+ loadSkill,
16
+ loadSkillSuggestion,
10
17
  loadSkillSuggestions,
11
18
  loadSkills,
12
- saveSkillSuggestions,
13
- saveSkills,
14
- } from '@/lib/server/storage'
19
+ saveSkill,
20
+ upsertSkillSuggestion,
21
+ } from '@/lib/server/skills/skill-repository'
15
22
  import { buildLLM, type GenerationModelPreference } from '@/lib/server/build-llm'
16
23
  import { notify } from '@/lib/server/ws-hub'
17
24
  import { resolveAgentRouteCandidates } from '@/lib/server/agents/agent-runtime-config'
@@ -91,7 +98,7 @@ function ensureHeading(name: string, content: string): string {
91
98
  }
92
99
 
93
100
  export function buildSessionTranscript(session: Session, maxMessages = DEFAULT_TRANSCRIPT_MESSAGES): string {
94
- const messages = Array.isArray(session.messages) ? session.messages.slice(-maxMessages) : []
101
+ const messages = getRecentMessages(session.id, maxMessages)
95
102
  const lines: string[] = []
96
103
  for (const message of messages) {
97
104
  if (!message || message.suppressed) continue
@@ -109,9 +116,8 @@ export function buildSessionTranscript(session: Session, maxMessages = DEFAULT_T
109
116
  }
110
117
 
111
118
  function getSessionMessageCount(session: Session): number {
112
- return Array.isArray(session.messages)
113
- ? session.messages.filter((message) => message && !message.suppressed && (message.text || message.toolEvents?.length)).length
114
- : 0
119
+ return getMessages(session.id)
120
+ .filter((message) => message && !message.suppressed && (message.text || message.toolEvents?.length)).length
115
121
  }
116
122
 
117
123
  function buildSuggestionPrompt(params: {
@@ -234,11 +240,9 @@ export async function createSkillSuggestionFromSession(
234
240
  sessionId: string,
235
241
  options?: { generateText?: (prompt: string) => Promise<string> },
236
242
  ): Promise<SkillSuggestion> {
237
- const sessions = loadSessions()
238
- const session = sessions[sessionId] as unknown as Session | undefined
243
+ const session = loadSession(sessionId)
239
244
  if (!session) throw new Error(`Session "${sessionId}" not found.`)
240
- const agents = loadAgents()
241
- const agent = session.agentId ? agents[session.agentId] : null
245
+ const agent = session.agentId ? loadAgent(session.agentId) : null
242
246
 
243
247
  const transcript = buildSessionTranscript(session)
244
248
  const sourceMessageCount = getSessionMessageCount(session)
@@ -331,18 +335,16 @@ export async function createSkillSuggestionFromSession(
331
335
  updatedAt: now,
332
336
  }
333
337
 
334
- suggestions[suggestion.id] = suggestion
335
- saveSkillSuggestions(suggestions)
338
+ upsertSkillSuggestion(suggestion.id, suggestion)
336
339
  notify('skill_suggestions')
337
340
  return suggestion
338
341
  }
339
342
 
340
343
  export function materializeSkillSuggestion(id: string): { suggestion: SkillSuggestion; skill: Skill } {
341
- const suggestions = loadSkillSuggestions()
342
- const suggestion = suggestions[id]
344
+ const suggestion = loadSkillSuggestion(id)
343
345
  if (!suggestion) throw new Error(`Skill suggestion "${id}" not found.`)
344
346
  if (suggestion.status === 'approved' && suggestion.createdSkillId) {
345
- const existing = loadSkills()[suggestion.createdSkillId]
347
+ const existing = loadSkill(suggestion.createdSkillId)
346
348
  if (existing) return { suggestion, skill: existing }
347
349
  }
348
350
 
@@ -370,8 +372,7 @@ export function materializeSkillSuggestion(id: string): { suggestion: SkillSugge
370
372
  approvedAt: now,
371
373
  updatedAt: now,
372
374
  }
373
- suggestions[id] = approved
374
- saveSkillSuggestions(suggestions)
375
+ upsertSkillSuggestion(id, approved)
375
376
  notify('skill_suggestions')
376
377
  return { suggestion: approved, skill: existingSkill }
377
378
  }
@@ -399,8 +400,7 @@ export function materializeSkillSuggestion(id: string): { suggestion: SkillSugge
399
400
  updatedAt: now,
400
401
  }
401
402
 
402
- skills[skill.id] = skill
403
- saveSkills(skills)
403
+ saveSkill(skill.id, skill)
404
404
  clearDiscoveredSkillsCache()
405
405
 
406
406
  const approved: SkillSuggestion = {
@@ -410,16 +410,14 @@ export function materializeSkillSuggestion(id: string): { suggestion: SkillSugge
410
410
  approvedAt: now,
411
411
  updatedAt: now,
412
412
  }
413
- suggestions[id] = approved
414
- saveSkillSuggestions(suggestions)
413
+ upsertSkillSuggestion(id, approved)
415
414
  notify('skills')
416
415
  notify('skill_suggestions')
417
416
  return { suggestion: approved, skill }
418
417
  }
419
418
 
420
419
  export function rejectSkillSuggestion(id: string): SkillSuggestion {
421
- const suggestions = loadSkillSuggestions()
422
- const suggestion = suggestions[id]
420
+ const suggestion = loadSkillSuggestion(id)
423
421
  if (!suggestion) throw new Error(`Skill suggestion "${id}" not found.`)
424
422
  const rejected: SkillSuggestion = {
425
423
  ...suggestion,
@@ -427,8 +425,7 @@ export function rejectSkillSuggestion(id: string): SkillSuggestion {
427
425
  rejectedAt: Date.now(),
428
426
  updatedAt: Date.now(),
429
427
  }
430
- suggestions[id] = rejected
431
- saveSkillSuggestions(suggestions)
428
+ upsertSkillSuggestion(id, rejected)
432
429
  notify('skill_suggestions')
433
430
  return rejected
434
431
  }
@@ -1,220 +1,47 @@
1
1
  import assert from 'node:assert/strict'
2
- import { after, before, describe, it } from 'node:test'
3
-
4
- let normalizeStoredRecord: typeof import('@/lib/server/storage-normalization').normalizeStoredRecord
5
-
6
- const noopLoader = () => null
7
-
8
- before(async () => {
9
- process.env.SWARMCLAW_BUILD_MODE = '1'
10
- const mod = await import('@/lib/server/storage-normalization')
11
- normalizeStoredRecord = mod.normalizeStoredRecord
12
- })
13
-
14
- after(() => {
15
- delete process.env.SWARMCLAW_BUILD_MODE
16
- })
17
-
18
- describe('storage-normalization', () => {
19
- // ---- Agent normalization ----
20
- describe('agents', () => {
21
- it('adds default capabilities, delegation, role, orgChart fields', () => {
22
- const agent = { id: 'a1', name: 'Test' } as Record<string, unknown>
23
- const result = normalizeStoredRecord('agents', agent, noopLoader).value as Record<string, unknown>
24
- assert.deepEqual(result.capabilities, [])
25
- assert.equal(result.role, 'worker')
26
- assert.equal(result.delegationEnabled, false)
27
- assert.equal(result.orgChart, null)
28
- assert.ok(Array.isArray(result.tools))
29
- assert.ok(Array.isArray(result.extensions))
30
- })
31
-
32
- it('preserves existing values', () => {
33
- const agent = {
34
- id: 'a1',
35
- name: 'Test',
36
- capabilities: ['web', 'code'],
37
- role: 'coordinator',
38
- delegationEnabled: true,
39
- delegationTargetMode: 'all',
40
- delegationTargetAgentIds: [],
41
- orgChart: { parentId: 'p1', teamLabel: 'Eng', teamColor: '#fff', x: 10, y: 20 },
42
- } as Record<string, unknown>
43
- const result = normalizeStoredRecord('agents', agent, noopLoader).value as Record<string, unknown>
44
- assert.deepEqual(result.capabilities, ['web', 'code'])
45
- assert.equal(result.role, 'coordinator')
46
- assert.equal(result.delegationEnabled, true)
47
- })
48
-
49
- it('coordinator always has delegation enabled', () => {
50
- const agent = { id: 'a1', name: 'Test', role: 'coordinator', delegationEnabled: false } as Record<string, unknown>
51
- const result = normalizeStoredRecord('agents', agent, noopLoader).value as Record<string, unknown>
52
- assert.equal(result.delegationEnabled, true)
53
- })
54
-
55
- it('migrates legacy platformAssignScope', () => {
56
- const agent = { id: 'a1', name: 'Test', platformAssignScope: 'all' } as Record<string, unknown>
57
- const result = normalizeStoredRecord('agents', agent, noopLoader).value as Record<string, unknown>
58
- assert.equal(result.delegationEnabled, true)
59
- assert.equal(result.platformAssignScope, undefined)
60
- })
61
-
62
- it('migrates legacy subAgentIds to delegationTargetAgentIds', () => {
63
- const agent = { id: 'a1', name: 'Test', subAgentIds: ['b1', 'b2'] } as Record<string, unknown>
64
- const result = normalizeStoredRecord('agents', agent, noopLoader).value as Record<string, unknown>
65
- assert.deepEqual(result.delegationTargetAgentIds, ['b1', 'b2'])
66
- assert.equal(result.delegationTargetMode, 'selected')
67
- assert.equal(result.subAgentIds, undefined)
68
- })
69
-
70
- it('deletes legacy plugins field', () => {
71
- const agent = { id: 'a1', name: 'Test', plugins: ['old'] } as Record<string, unknown>
72
- const result = normalizeStoredRecord('agents', agent, noopLoader).value as Record<string, unknown>
73
- assert.equal(result.plugins, undefined)
74
- })
75
- })
76
-
77
- // ---- Session normalization ----
78
- describe('sessions', () => {
79
- it('type migration orchestrated → delegated', () => {
80
- const session = { id: 's1', sessionType: 'orchestrated' } as Record<string, unknown>
81
- const result = normalizeStoredRecord('sessions', session, noopLoader).value as Record<string, unknown>
82
- assert.equal(result.sessionType, 'delegated')
83
- })
84
-
85
- it('defaults invalid sessionType to human', () => {
86
- const session = { id: 's1', sessionType: 'bogus' } as Record<string, unknown>
87
- const result = normalizeStoredRecord('sessions', session, noopLoader).value as Record<string, unknown>
88
- assert.equal(result.sessionType, 'human')
89
- })
90
-
91
- it('detects shortcut session from id prefix', () => {
92
- const session = { id: 'agent-thread-abc', agentId: 'a1', sessionType: 'human' } as Record<string, unknown>
93
- const result = normalizeStoredRecord('sessions', session, noopLoader).value as Record<string, unknown>
94
- assert.equal(result.shortcutForAgentId, 'a1')
95
- })
96
-
97
- it('normalizes capabilities', () => {
98
- const session = { id: 's1', sessionType: 'human', tools: ['web'] } as Record<string, unknown>
99
- const result = normalizeStoredRecord('sessions', session, noopLoader).value as Record<string, unknown>
100
- assert.ok(Array.isArray(result.tools))
101
- assert.ok(Array.isArray(result.extensions))
102
- })
103
-
104
- it('deletes legacy plugins and mainLoopState', () => {
105
- const session = { id: 's1', sessionType: 'human', plugins: ['old'], mainLoopState: {} } as Record<string, unknown>
106
- const result = normalizeStoredRecord('sessions', session, noopLoader).value as Record<string, unknown>
107
- assert.equal(result.plugins, undefined)
108
- assert.equal(result.mainLoopState, undefined)
109
- })
110
- })
111
-
112
- // ---- Schedule normalization ----
113
- describe('schedules', () => {
114
- it('type resolution from scheduleType', () => {
115
- const schedule = { id: 'sch1', scheduleType: 'cron', status: 'active' } as Record<string, unknown>
116
- const result = normalizeStoredRecord('schedules', schedule, noopLoader).value as Record<string, unknown>
117
- assert.equal(result.scheduleType, 'cron')
118
- })
119
-
120
- it('legacy type field migrated', () => {
121
- const schedule = { id: 'sch1', type: 'once', status: 'active' } as Record<string, unknown>
122
- const result = normalizeStoredRecord('schedules', schedule, noopLoader).value as Record<string, unknown>
123
- assert.equal(result.scheduleType, 'once')
124
- assert.equal(result.type, undefined)
125
- })
126
-
127
- it('invalid status defaults to active', () => {
128
- const schedule = { id: 'sch1', status: 'bogus' } as Record<string, unknown>
129
- const result = normalizeStoredRecord('schedules', schedule, noopLoader).value as Record<string, unknown>
130
- assert.equal(result.status, 'active')
131
- })
132
-
133
- it('timestamp parsing from number', () => {
134
- const schedule = { id: 'sch1', status: 'active', lastRunAt: 1700000000000 } as Record<string, unknown>
135
- const result = normalizeStoredRecord('schedules', schedule, noopLoader).value as Record<string, unknown>
136
- assert.equal(result.lastRunAt, 1700000000000)
137
- })
138
- })
139
-
140
- // ---- Task normalization ----
141
- describe('tasks', () => {
142
- it('subtaskIds default to empty array', () => {
143
- const task = { id: 't1', title: 'Test' } as Record<string, unknown>
144
- const result = normalizeStoredRecord('tasks', task, noopLoader).value as Record<string, unknown>
145
- assert.deepEqual(result.subtaskIds, [])
146
- })
147
-
148
- it('preserves existing subtaskIds', () => {
149
- const task = { id: 't1', title: 'Test', subtaskIds: ['t2'] } as Record<string, unknown>
150
- const result = normalizeStoredRecord('tasks', task, noopLoader).value as Record<string, unknown>
151
- assert.deepEqual(result.subtaskIds, ['t2'])
152
- })
153
-
154
- it('removes missionSummary field', () => {
155
- const task = { id: 't1', missionSummary: 'old' } as Record<string, unknown>
156
- const result = normalizeStoredRecord('tasks', task, noopLoader).value as Record<string, unknown>
157
- assert.equal(result.missionSummary, undefined)
158
- })
159
- })
160
-
161
- // ---- Mission normalization ----
162
- describe('missions', () => {
163
- it('defaults status/phase/sourceRef', () => {
164
- const mission = { id: 'm1' } as Record<string, unknown>
165
- const result = normalizeStoredRecord('missions', mission, noopLoader).value as Record<string, unknown>
166
- assert.equal(result.status, 'active')
167
- assert.equal(result.phase, 'planning')
168
- assert.deepEqual(result.sourceRef, { kind: 'manual' })
169
- })
170
-
171
- it('preserves valid status and phase', () => {
172
- const mission = { id: 'm1', status: 'waiting', phase: 'executing' } as Record<string, unknown>
173
- const result = normalizeStoredRecord('missions', mission, noopLoader).value as Record<string, unknown>
174
- assert.equal(result.status, 'waiting')
175
- assert.equal(result.phase, 'executing')
176
- })
177
-
178
- it('sourceRef from sessionId', () => {
179
- const mission = { id: 'm1', sessionId: 'sess-1' } as Record<string, unknown>
180
- const result = normalizeStoredRecord('missions', mission, noopLoader).value as Record<string, unknown>
181
- assert.deepEqual(result.sourceRef, { kind: 'chat', sessionId: 'sess-1' })
182
- })
183
-
184
- it('waitState defaults', () => {
185
- const mission = {
186
- id: 'm1',
187
- waitState: { kind: 'approval', reason: 'Needs sign-off' },
188
- } as Record<string, unknown>
189
- const result = normalizeStoredRecord('missions', mission, noopLoader).value as Record<string, unknown>
190
- const ws = result.waitState as Record<string, unknown>
191
- assert.equal(ws.kind, 'approval')
192
- assert.equal(ws.reason, 'Needs sign-off')
193
- })
194
- })
195
-
196
- // ---- DelegationJob normalization ----
197
- describe('delegation_jobs', () => {
198
- it('missionId cleanup', () => {
199
- const job = { id: 'j1', missionId: ' m1 ' } as Record<string, unknown>
200
- const result = normalizeStoredRecord('delegation_jobs', job, noopLoader).value as Record<string, unknown>
201
- assert.equal(result.missionId, 'm1')
202
- })
203
-
204
- it('empty missionId is deleted', () => {
205
- const job = { id: 'j1', missionId: ' ' } as Record<string, unknown>
206
- const result = normalizeStoredRecord('delegation_jobs', job, noopLoader).value as Record<string, unknown>
207
- assert.equal(result.missionId, undefined)
208
- })
2
+ import { describe, it } from 'node:test'
3
+
4
+ import { normalizeStoredRecord } from '@/lib/server/storage-normalization'
5
+
6
+ const loadItem = () => null
7
+
8
+ describe('storage normalization for runtime execution records', () => {
9
+ it('backfills execution metadata on legacy runtime runs', () => {
10
+ const { value, changed } = normalizeStoredRecord('runtime_runs', {
11
+ id: 'run_1',
12
+ sessionId: 'sess_1',
13
+ source: 'task',
14
+ internal: false,
15
+ mode: 'followup',
16
+ status: 'queued',
17
+ messagePreview: 'Build the feature',
18
+ queuedAt: 123,
19
+ }, loadItem)
20
+
21
+ const record = value as Record<string, unknown>
22
+ assert.equal(changed, true)
23
+ assert.equal(record.kind, 'session_turn')
24
+ assert.equal(record.ownerType, 'session')
25
+ assert.equal(record.ownerId, 'sess_1')
26
+ assert.equal(record.parentExecutionId, null)
27
+ assert.equal(record.recoveryPolicy, 'restart_recoverable')
209
28
  })
210
29
 
211
- // ---- Unknown collection ----
212
- describe('unknown collection', () => {
213
- it('passes through unchanged', () => {
214
- const data = { id: 'x1', foo: 'bar' }
215
- const result = normalizeStoredRecord('widgets', data, noopLoader)
216
- assert.deepEqual(result.value, data)
217
- assert.equal(result.changed, false)
218
- })
30
+ it('backfills execution metadata on legacy runtime run events', () => {
31
+ const { value, changed } = normalizeStoredRecord('runtime_run_events', {
32
+ id: 'evt_1',
33
+ runId: 'run_1',
34
+ sessionId: 'sess_1',
35
+ timestamp: 123,
36
+ phase: 'status',
37
+ event: { t: 'md', text: '{}' },
38
+ }, loadItem)
39
+
40
+ const record = value as Record<string, unknown>
41
+ assert.equal(changed, true)
42
+ assert.equal(record.kind, 'session_turn')
43
+ assert.equal(record.ownerType, 'session')
44
+ assert.equal(record.ownerId, 'sess_1')
45
+ assert.equal(record.parentExecutionId, null)
219
46
  })
220
47
  })
@@ -369,6 +369,47 @@ function normalizeStoredDelegationJobRecord(value: unknown): unknown {
369
369
  return job
370
370
  }
371
371
 
372
+ function normalizeStoredRuntimeRunRecord(value: unknown): unknown {
373
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value
374
+ const run = value as StoredObject
375
+
376
+ if (typeof run.kind !== 'string' || !run.kind.trim()) run.kind = 'session_turn'
377
+ if (run.ownerType === undefined) run.ownerType = 'session'
378
+ if (run.ownerId === undefined) {
379
+ const sessionId = typeof run.sessionId === 'string' && run.sessionId.trim() ? run.sessionId.trim() : ''
380
+ run.ownerId = sessionId || null
381
+ }
382
+ if (run.parentExecutionId === undefined) run.parentExecutionId = null
383
+ if (run.recoveryPolicy === undefined) {
384
+ const source = typeof run.source === 'string' ? run.source.trim().toLowerCase() : ''
385
+ run.recoveryPolicy = source === 'heartbeat'
386
+ || source === 'heartbeat-wake'
387
+ || source === 'schedule'
388
+ || source === 'task'
389
+ || source === 'delegation'
390
+ || source === 'subagent'
391
+ ? 'restart_recoverable'
392
+ : 'ephemeral'
393
+ }
394
+
395
+ return run
396
+ }
397
+
398
+ function normalizeStoredRuntimeRunEventRecord(value: unknown): unknown {
399
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value
400
+ const event = value as StoredObject
401
+
402
+ if (typeof event.kind !== 'string' || !event.kind.trim()) event.kind = 'session_turn'
403
+ if (event.ownerType === undefined) event.ownerType = 'session'
404
+ if (event.ownerId === undefined) {
405
+ const sessionId = typeof event.sessionId === 'string' && event.sessionId.trim() ? event.sessionId.trim() : ''
406
+ event.ownerId = sessionId || null
407
+ }
408
+ if (event.parentExecutionId === undefined) event.parentExecutionId = null
409
+
410
+ return event
411
+ }
412
+
372
413
  // --- Main dispatch function ---
373
414
 
374
415
  export interface NormalizationResult {
@@ -392,6 +433,8 @@ export function normalizeStoredRecord(
392
433
  table !== 'agents' && table !== 'tasks' && table !== 'missions'
393
434
  && table !== 'mission_events' && table !== 'delegation_jobs'
394
435
  && table !== 'schedules' && table !== 'sessions'
436
+ && table !== 'provider_configs'
437
+ && table !== 'runtime_runs' && table !== 'runtime_run_events'
395
438
  ) {
396
439
  return { value, changed: false }
397
440
  }
@@ -482,6 +525,28 @@ function normalizeStoredRecordInner(
482
525
  return task
483
526
  }
484
527
 
528
+ if (table === 'provider_configs') {
529
+ const provider = value as StoredObject
530
+ provider.type = provider.type === 'builtin' ? 'builtin' : 'custom'
531
+ if (typeof provider.name !== 'string' || !provider.name.trim()) {
532
+ provider.name = provider.type === 'builtin' ? 'Built-in Provider' : 'Custom Provider'
533
+ } else {
534
+ provider.name = provider.name.trim()
535
+ }
536
+ provider.baseUrl = typeof provider.baseUrl === 'string' ? provider.baseUrl.trim() : ''
537
+ provider.models = normalizeStoredStringArray(provider.models)
538
+
539
+ if (typeof provider.requiresApiKey !== 'boolean') provider.requiresApiKey = true
540
+ if (typeof provider.isEnabled !== 'boolean') provider.isEnabled = true
541
+
542
+ const credentialId = typeof provider.credentialId === 'string' ? provider.credentialId.trim() : ''
543
+ provider.credentialId = credentialId || null
544
+
545
+ if (typeof provider.createdAt !== 'number') provider.createdAt = Date.now()
546
+ if (typeof provider.updatedAt !== 'number') provider.updatedAt = provider.createdAt as number
547
+ return provider
548
+ }
549
+
485
550
  if (table === 'missions') {
486
551
  return normalizeStoredMissionRecord(value)
487
552
  }
@@ -494,6 +559,14 @@ function normalizeStoredRecordInner(
494
559
  return normalizeStoredDelegationJobRecord(value)
495
560
  }
496
561
 
562
+ if (table === 'runtime_runs') {
563
+ return normalizeStoredRuntimeRunRecord(value)
564
+ }
565
+
566
+ if (table === 'runtime_run_events') {
567
+ return normalizeStoredRuntimeRunEventRecord(value)
568
+ }
569
+
497
570
  if (table === 'schedules') {
498
571
  return normalizeStoredScheduleRecord(value, loadItem)
499
572
  }
@@ -524,11 +597,36 @@ function normalizeStoredRecordInner(
524
597
  if ('plugins' in session) delete session.plugins
525
598
  if ('mainLoopState' in session) delete session.mainLoopState
526
599
  if ('missionSummary' in session) delete session.missionSummary
600
+ // Messages are now stored in session_messages table — ensure default empty array
601
+ if (!Array.isArray(session.messages)) session.messages = []
602
+ // Default messageCount for pre-migration blobs
603
+ if (typeof session.messageCount !== 'number') {
604
+ session.messageCount = (session.messages as unknown[]).length
605
+ }
527
606
  // Default geminiSessionId for new field
528
607
  if (session.geminiSessionId === undefined) session.geminiSessionId = null
529
608
  // Default injectedMemoryIds for proactive recall dedup
530
609
  if (!session.injectedMemoryIds || typeof session.injectedMemoryIds !== 'object') {
531
610
  session.injectedMemoryIds = {}
532
611
  }
612
+ // Validate runContext if present — leave null/undefined alone (created on demand)
613
+ if (session.runContext != null) {
614
+ if (typeof session.runContext !== 'object' || Array.isArray(session.runContext)) {
615
+ session.runContext = null
616
+ } else {
617
+ const rc = session.runContext as Record<string, unknown>
618
+ if (typeof rc.objective !== 'string' && rc.objective !== null) rc.objective = null
619
+ if (!Array.isArray(rc.constraints)) rc.constraints = []
620
+ if (!Array.isArray(rc.keyFacts)) rc.keyFacts = []
621
+ if (!Array.isArray(rc.discoveries)) rc.discoveries = []
622
+ if (!Array.isArray(rc.failedApproaches)) rc.failedApproaches = []
623
+ if (!Array.isArray(rc.currentPlan)) rc.currentPlan = []
624
+ if (!Array.isArray(rc.completedSteps)) rc.completedSteps = []
625
+ if (!Array.isArray(rc.blockers)) rc.blockers = []
626
+ if (typeof rc.parentContext !== 'string' && rc.parentContext !== null) rc.parentContext = null
627
+ if (typeof rc.updatedAt !== 'number') rc.updatedAt = Date.now()
628
+ if (typeof rc.version !== 'number') rc.version = 0
629
+ }
630
+ }
533
631
  return session
534
632
  }
@@ -100,6 +100,9 @@ export function withTransaction<T>(fn: () => T): T {
100
100
  return wrapped()
101
101
  }
102
102
 
103
+ /** Internal: raw database handle for specialized repositories (e.g. message-repository). */
104
+ export function getDb(): InstanceType<typeof Database> { return db }
105
+
103
106
  type StoredObject = Record<string, unknown>
104
107
  type StoredSessionRecord = Session
105
108
  type StoredAgentRecord = Agent
@@ -157,6 +160,8 @@ const COLLECTIONS = [
157
160
  'provider_health',
158
161
  'swarm_snapshots',
159
162
  'main_loop_states',
163
+ 'working_states',
164
+ 'daemon_status',
160
165
  ] as const
161
166
 
162
167
  export type StorageCollection = (typeof COLLECTIONS)[number]
@@ -180,6 +185,14 @@ db.exec(`CREATE TABLE IF NOT EXISTS runtime_locks (
180
185
  updated_at INTEGER NOT NULL
181
186
  )`)
182
187
 
188
+ // Relational message storage — messages extracted from session blobs (Phase 1)
189
+ db.exec(`CREATE TABLE IF NOT EXISTS session_messages (
190
+ session_id TEXT NOT NULL,
191
+ seq INTEGER NOT NULL,
192
+ data TEXT NOT NULL,
193
+ PRIMARY KEY (session_id, seq)
194
+ ) WITHOUT ROWID`)
195
+
183
196
  // --- Internal normalize helper that binds the loadItem dependency ---
184
197
  function normalize(table: string, value: unknown): NormalizationResult {
185
198
  return normalizeStoredRecord(table, value, loadCollectionItem)
@@ -1594,6 +1607,12 @@ export const loadPersistedMainLoopState = mainLoopStatesStore.loadItem
1594
1607
  export const upsertPersistedMainLoopState = mainLoopStatesStore.upsert
1595
1608
  export const deletePersistedMainLoopState = mainLoopStatesStore.deleteItem
1596
1609
 
1610
+ // --- Working States ---
1611
+ const workingStatesStore = createCollectionStore('working_states')
1612
+ export const loadPersistedWorkingState = workingStatesStore.loadItem
1613
+ export const upsertPersistedWorkingState = workingStatesStore.upsert
1614
+ export const deletePersistedWorkingState = workingStatesStore.deleteItem
1615
+
1597
1616
  export function getSessionMessages(sessionId: string): Message[] {
1598
1617
  const session = loadSession(sessionId)
1599
1618
  return Array.isArray(session?.messages) ? session.messages : []
@@ -1,6 +1,6 @@
1
1
  import type { Session } from '@/types'
2
2
  import { getProvider, streamChatWithFailover } from '@/lib/providers'
3
- import { decryptKey, loadCredentials } from './storage'
3
+ import { requireCredentialSecret, resolveCredentialSecret } from './credentials/credential-service'
4
4
  import { extractDocumentArtifact, type DocumentArtifact } from './document-utils'
5
5
 
6
6
  type JsonSchemaLike = Record<string, unknown>
@@ -31,21 +31,10 @@ function resolveApiKey(session: ExtractionSession): string | null {
31
31
  if (!provider) throw new Error(`Unknown provider: ${session.provider}`)
32
32
  if (provider.requiresApiKey) {
33
33
  if (!session.credentialId) throw new Error('No API key configured for this session')
34
- const creds = loadCredentials()
35
- const cred = creds[session.credentialId]
36
- if (!cred?.encryptedKey) throw new Error('API key not found. Please add one in Settings.')
37
- return decryptKey(cred.encryptedKey)
34
+ return requireCredentialSecret(session.credentialId, 'API key not found. Please add one in Settings.')
38
35
  }
39
36
  if (provider.optionalApiKey && session.credentialId) {
40
- const creds = loadCredentials()
41
- const cred = creds[session.credentialId]
42
- if (cred?.encryptedKey) {
43
- try {
44
- return decryptKey(cred.encryptedKey)
45
- } catch {
46
- return null
47
- }
48
- }
37
+ return resolveCredentialSecret(session.credentialId)
49
38
  }
50
39
  return null
51
40
  }