@swarmclawai/swarmclaw 1.2.6 → 1.2.9

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 (269) hide show
  1. package/README.md +54 -23
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/scripts/easy-setup.mjs +1 -1
  5. package/scripts/postinstall.mjs +1 -1
  6. package/skills/swarmclaw.md +115 -0
  7. package/skills/tools/browser.md +131 -0
  8. package/skills/tools/execute.md +98 -0
  9. package/skills/tools/files.md +98 -0
  10. package/skills/tools/memory.md +104 -0
  11. package/skills/tools/platform.md +144 -0
  12. package/skills/tools/skills.md +83 -0
  13. package/src/app/agents/[id]/page.tsx +1 -18
  14. package/src/app/api/agents/thread-route.test.ts +0 -1
  15. package/src/app/api/approvals/route.test.ts +6 -22
  16. package/src/app/api/chats/[id]/messages/route.ts +23 -19
  17. package/src/app/api/chats/messages-route.test.ts +105 -51
  18. package/src/app/api/connectors/route.ts +2 -2
  19. package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
  20. package/src/app/api/openclaw/deploy/route.ts +2 -0
  21. package/src/app/api/portability/export/route.ts +8 -0
  22. package/src/app/api/portability/import/route.test.ts +80 -0
  23. package/src/app/api/portability/import/route.ts +28 -0
  24. package/src/app/api/settings/route.ts +0 -2
  25. package/src/app/api/setup/doctor/route.ts +4 -4
  26. package/src/app/api/wallets/[id]/route.ts +15 -157
  27. package/src/app/api/wallets/generate/route.ts +22 -0
  28. package/src/app/api/wallets/route.test.ts +147 -0
  29. package/src/app/api/wallets/route.ts +13 -95
  30. package/src/app/autonomy/page.tsx +2 -57
  31. package/src/app/protocols/page.tsx +2 -21
  32. package/src/app/settings/page.tsx +0 -9
  33. package/src/app/wallets/page.tsx +105 -5
  34. package/src/cli/index.js +21 -33
  35. package/src/cli/spec.js +19 -30
  36. package/src/components/agents/agent-chat-list.tsx +23 -1
  37. package/src/components/agents/agent-sheet.tsx +2 -40
  38. package/src/components/agents/inspector-panel.tsx +165 -131
  39. package/src/components/chat/chat-area.tsx +38 -9
  40. package/src/components/chat/chat-card.tsx +0 -31
  41. package/src/components/chat/message-bubble.tsx +1 -108
  42. package/src/components/chat/message-list.tsx +33 -19
  43. package/src/components/connectors/connector-sheet.tsx +25 -1
  44. package/src/components/gateways/gateway-sheet.tsx +5 -2
  45. package/src/components/layout/sidebar-rail.tsx +6 -10
  46. package/src/components/projects/project-detail.tsx +3 -35
  47. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  48. package/src/components/projects/tabs/work-tab.tsx +7 -77
  49. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  50. package/src/components/shared/connector-platform-icon.tsx +1 -0
  51. package/src/components/tasks/task-card.tsx +4 -34
  52. package/src/components/tasks/task-sheet.tsx +6 -36
  53. package/src/components/wallets/wallet-list.tsx +150 -0
  54. package/src/lib/agent-execute-defaults.test.ts +24 -0
  55. package/src/lib/agent-execute-defaults.ts +62 -0
  56. package/src/lib/app/navigation.test.ts +0 -13
  57. package/src/lib/app/navigation.ts +2 -7
  58. package/src/lib/app/view-constants.ts +14 -19
  59. package/src/lib/chat/queued-message-queue.test.ts +134 -1
  60. package/src/lib/chat/queued-message-queue.ts +77 -2
  61. package/src/lib/server/agents/agent-service.ts +5 -0
  62. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  63. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  64. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  65. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  66. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  67. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  68. package/src/lib/server/approval-match.ts +0 -85
  69. package/src/lib/server/approvals.test.ts +6 -6
  70. package/src/lib/server/approvals.ts +0 -6
  71. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  72. package/src/lib/server/builtin-extensions.ts +1 -2
  73. package/src/lib/server/capability-router.test.ts +0 -2
  74. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
  75. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
  76. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  77. package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
  78. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  79. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  80. package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
  81. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
  82. package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
  83. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  84. package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
  85. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  86. package/src/lib/server/chat-execution/message-classifier.ts +11 -16
  87. package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
  88. package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
  89. package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
  90. package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
  91. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  92. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  93. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  94. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
  95. package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
  96. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  97. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  98. package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
  99. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  100. package/src/lib/server/chats/chat-session-service.ts +3 -5
  101. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  102. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  103. package/src/lib/server/connectors/connector-service.ts +39 -9
  104. package/src/lib/server/connectors/discord.ts +2 -2
  105. package/src/lib/server/connectors/matrix.ts +3 -2
  106. package/src/lib/server/connectors/signal.ts +5 -4
  107. package/src/lib/server/connectors/slack.ts +10 -9
  108. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  109. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  110. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  111. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  112. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  113. package/src/lib/server/connectors/swarmdock.ts +255 -0
  114. package/src/lib/server/connectors/teams.ts +3 -2
  115. package/src/lib/server/connectors/telegram.ts +4 -4
  116. package/src/lib/server/connectors/whatsapp.ts +2 -2
  117. package/src/lib/server/daemon/controller.ts +7 -0
  118. package/src/lib/server/execution-brief.test.ts +2 -25
  119. package/src/lib/server/execution-brief.ts +12 -35
  120. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  121. package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
  122. package/src/lib/server/messages/message-repository.test.ts +70 -0
  123. package/src/lib/server/messages/message-repository.ts +11 -6
  124. package/src/lib/server/openclaw/deploy.ts +32 -2
  125. package/src/lib/server/persistence/storage-context.ts +0 -5
  126. package/src/lib/server/plugins-advanced.test.ts +1 -2
  127. package/src/lib/server/portability/export.ts +109 -0
  128. package/src/lib/server/portability/import.ts +159 -0
  129. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  130. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  131. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  132. package/src/lib/server/protocols/protocol-service.ts +0 -1
  133. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  134. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  135. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  136. package/src/lib/server/protocols/protocol-types.ts +0 -2
  137. package/src/lib/server/provider-health.ts +1 -10
  138. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  139. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  140. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  141. package/src/lib/server/runtime/process-manager.ts +13 -9
  142. package/src/lib/server/runtime/queue/core.ts +11 -33
  143. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  144. package/src/lib/server/runtime/scheduler.ts +0 -13
  145. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  146. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  147. package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
  148. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  149. package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
  150. package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
  151. package/src/lib/server/sandbox/session-runtime.ts +40 -28
  152. package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
  153. package/src/lib/server/session-tools/context.ts +1 -1
  154. package/src/lib/server/session-tools/credential-env.ts +109 -0
  155. package/src/lib/server/session-tools/crud.ts +3 -17
  156. package/src/lib/server/session-tools/delegate.ts +0 -4
  157. package/src/lib/server/session-tools/edit_file.ts +3 -2
  158. package/src/lib/server/session-tools/execute.test.ts +58 -0
  159. package/src/lib/server/session-tools/execute.ts +334 -0
  160. package/src/lib/server/session-tools/files-tool.ts +635 -0
  161. package/src/lib/server/session-tools/index.ts +14 -8
  162. package/src/lib/server/session-tools/memory-tool.ts +242 -0
  163. package/src/lib/server/session-tools/memory.ts +1 -1
  164. package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
  165. package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
  166. package/src/lib/server/session-tools/platform-tool.ts +617 -0
  167. package/src/lib/server/session-tools/session-info.ts +3 -2
  168. package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
  169. package/src/lib/server/session-tools/shell.ts +7 -122
  170. package/src/lib/server/session-tools/skills-tool.ts +396 -0
  171. package/src/lib/server/session-tools/team-context.ts +0 -3
  172. package/src/lib/server/session-tools/web.ts +2 -2
  173. package/src/lib/server/storage-normalization.ts +10 -0
  174. package/src/lib/server/storage.ts +18 -45
  175. package/src/lib/server/tasks/task-checkout.ts +59 -0
  176. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  177. package/src/lib/server/tasks/task-route-service.ts +4 -26
  178. package/src/lib/server/tasks/task-service.ts +0 -7
  179. package/src/lib/server/tool-aliases.ts +2 -2
  180. package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
  181. package/src/lib/server/tool-capability-policy.test.ts +2 -1
  182. package/src/lib/server/tool-capability-policy.ts +60 -35
  183. package/src/lib/server/tool-planning.ts +11 -12
  184. package/src/lib/server/universal-tool-access.ts +0 -1
  185. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  186. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  187. package/src/lib/server/wallets/wallet-service.ts +119 -0
  188. package/src/lib/server/working-state/extraction.ts +8 -42
  189. package/src/lib/server/working-state/normalization.ts +10 -103
  190. package/src/lib/server/working-state/service.ts +12 -21
  191. package/src/lib/setup-defaults.ts +5 -0
  192. package/src/lib/strip-internal-metadata.test.ts +1 -1
  193. package/src/lib/strip-internal-metadata.ts +1 -1
  194. package/src/lib/tool-definitions.ts +1 -1
  195. package/src/lib/validation/schemas.test.ts +16 -0
  196. package/src/lib/validation/schemas.ts +49 -2
  197. package/src/stores/slices/data-slice.ts +5 -1
  198. package/src/stores/slices/ui-slice.ts +0 -4
  199. package/src/stores/use-chat-store.test.ts +231 -0
  200. package/src/stores/use-chat-store.ts +62 -13
  201. package/src/types/agent.ts +264 -0
  202. package/src/types/app-settings.ts +173 -0
  203. package/src/types/approval.ts +25 -0
  204. package/src/types/connector.ts +188 -0
  205. package/src/types/extension.ts +386 -0
  206. package/src/types/index.ts +16 -3555
  207. package/src/types/message.ts +56 -0
  208. package/src/types/misc.ts +737 -0
  209. package/src/types/protocol.ts +420 -0
  210. package/src/types/provider.ts +52 -0
  211. package/src/types/run.ts +180 -0
  212. package/src/types/schedule.ts +59 -0
  213. package/src/types/session.ts +215 -0
  214. package/src/types/skill.ts +157 -0
  215. package/src/types/swarmdock.ts +29 -0
  216. package/src/types/task.ts +144 -0
  217. package/src/types/working-state.ts +204 -0
  218. package/src/views/settings/section-heartbeat.tsx +2 -2
  219. package/src/views/settings/section-runtime-loop.tsx +0 -14
  220. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  221. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  222. package/src/app/api/missions/[id]/events/route.ts +0 -14
  223. package/src/app/api/missions/[id]/route.ts +0 -10
  224. package/src/app/api/missions/route.test.ts +0 -244
  225. package/src/app/api/missions/route.ts +0 -57
  226. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  227. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  228. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  229. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  230. package/src/app/missions/[id]/page.tsx +0 -3
  231. package/src/app/missions/page.tsx +0 -685
  232. package/src/components/canvas/canvas-panel.tsx +0 -267
  233. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  234. package/src/components/wallets/wallet-panel.tsx +0 -1010
  235. package/src/components/wallets/wallet-section.tsx +0 -260
  236. package/src/features/missions/queries.ts +0 -23
  237. package/src/lib/canvas-content.test.ts +0 -360
  238. package/src/lib/canvas-content.ts +0 -198
  239. package/src/lib/server/canvas-content.test.ts +0 -32
  240. package/src/lib/server/canvas-content.ts +0 -6
  241. package/src/lib/server/ethereum.ts +0 -591
  242. package/src/lib/server/evm-swap.ts +0 -476
  243. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  244. package/src/lib/server/missions/mission-intent.ts +0 -569
  245. package/src/lib/server/missions/mission-repository.ts +0 -74
  246. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  247. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  248. package/src/lib/server/missions/mission-service/context.ts +0 -4
  249. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  250. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  251. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  252. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  253. package/src/lib/server/missions/mission-service.test.ts +0 -888
  254. package/src/lib/server/missions/mission-service.ts +0 -6
  255. package/src/lib/server/session-tools/canvas.ts +0 -105
  256. package/src/lib/server/session-tools/sandbox.ts +0 -281
  257. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  258. package/src/lib/server/session-tools/wallet.ts +0 -1287
  259. package/src/lib/server/solana.ts +0 -327
  260. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  261. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  262. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  263. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  264. package/src/lib/server/wallet/wallet-service.ts +0 -225
  265. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  266. package/src/lib/wallet/wallet-transactions.ts +0 -43
  267. package/src/lib/wallet/wallet.test.ts +0 -333
  268. package/src/lib/wallet/wallet.ts +0 -183
  269. package/src/views/settings/section-wallets.tsx +0 -35
@@ -1,888 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, it } from 'node:test'
3
- import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
4
-
5
- describe('mission-service', () => {
6
- it('creates and then completes a mission from model-backed turn decisions', () => {
7
- const output = runWithTempDataDir<{
8
- missionId: string | null
9
- sessionMissionId: string | null
10
- missionStatus: string | null
11
- missionPhase: string | null
12
- eventTypes: string[]
13
- }>(`
14
- const storageMod = await import('@/lib/server/storage')
15
- const missionMod = await import('@/lib/server/missions/mission-service')
16
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
17
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
18
-
19
- storage.saveSessions({
20
- sessionA: {
21
- id: 'sessionA',
22
- name: 'Release Chat',
23
- cwd: process.env.WORKSPACE_DIR,
24
- user: 'tester',
25
- provider: 'ollama',
26
- model: 'test-model',
27
- claudeSessionId: null,
28
- messages: [
29
- { role: 'user', text: 'Can you prep the release and update the docs?', time: 1 },
30
- ],
31
- createdAt: 1,
32
- lastActiveAt: 1,
33
- agentId: 'agentA',
34
- },
35
- })
36
-
37
- storage.saveAgents({
38
- agentA: {
39
- id: 'agentA',
40
- name: 'Agent A',
41
- provider: 'ollama',
42
- model: 'test-model',
43
- systemPrompt: 'test',
44
- },
45
- })
46
-
47
- const session = storage.loadSessions().sessionA
48
- const mission = await missions.resolveMissionForTurn({
49
- session,
50
- message: 'Please prep the release and update the docs.',
51
- source: 'chat',
52
- internal: false,
53
- runId: 'run-1',
54
- generateText: async () => JSON.stringify({
55
- action: 'create_new',
56
- confidence: 0.96,
57
- objective: 'Prepare the next release',
58
- successCriteria: ['README updated', 'release validated'],
59
- currentStep: 'Audit release requirements',
60
- plannerSummary: 'Track the release prep as a durable mission.',
61
- }),
62
- })
63
-
64
- const updated = await missions.applyMissionOutcomeForTurn({
65
- session,
66
- missionId: mission?.id || '',
67
- source: 'chat',
68
- runId: 'run-1',
69
- message: 'Please prep the release and update the docs.',
70
- assistantText: 'I updated the release checklist and verified the remaining steps are complete.',
71
- toolEvents: [],
72
- generateText: async () => JSON.stringify({
73
- verdict: 'completed',
74
- confidence: 0.88,
75
- phase: 'completed',
76
- verifierSummary: 'The release prep work is complete.',
77
- }),
78
- })
79
-
80
- const persistedSession = storage.loadSessions().sessionA
81
- const events = missions.listMissionEventsForMission(mission?.id || '')
82
-
83
- console.log(JSON.stringify({
84
- missionId: mission?.id || null,
85
- sessionMissionId: persistedSession.missionId || null,
86
- missionStatus: updated?.status || null,
87
- missionPhase: updated?.phase || null,
88
- eventTypes: events.map((event) => event.type),
89
- }))
90
- `, { prefix: 'swarmclaw-mission-service-' })
91
-
92
- assert.ok(output.missionId)
93
- assert.equal(output.sessionMissionId, output.missionId)
94
- assert.equal(output.missionStatus, 'completed')
95
- assert.equal(output.missionPhase, 'completed')
96
- assert.deepEqual(output.eventTypes, ['created', 'run_result', 'completed'])
97
- })
98
-
99
- it('reuses the current mission and records waiting state when verification says to pause', () => {
100
- const output = runWithTempDataDir<{
101
- missionId: string | null
102
- sameMission: boolean
103
- status: string | null
104
- waitKind: string | null
105
- waitReason: string | null
106
- }>(`
107
- const storageMod = await import('@/lib/server/storage')
108
- const missionMod = await import('@/lib/server/missions/mission-service')
109
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
110
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
111
-
112
- storage.saveSessions({
113
- sessionA: {
114
- id: 'sessionA',
115
- name: 'Main Chat',
116
- cwd: process.env.WORKSPACE_DIR,
117
- user: 'tester',
118
- provider: 'ollama',
119
- model: 'test-model',
120
- claudeSessionId: null,
121
- messages: [],
122
- createdAt: 1,
123
- lastActiveAt: 1,
124
- agentId: 'agentA',
125
- },
126
- })
127
-
128
- storage.saveAgents({
129
- agentA: {
130
- id: 'agentA',
131
- name: 'Agent A',
132
- provider: 'ollama',
133
- model: 'test-model',
134
- systemPrompt: 'test',
135
- },
136
- })
137
-
138
- const session = storage.loadSessions().sessionA
139
- const created = await missions.resolveMissionForTurn({
140
- session,
141
- message: 'Build the release dashboard and keep iterating on it.',
142
- source: 'chat',
143
- internal: false,
144
- runId: 'run-1',
145
- generateText: async () => JSON.stringify({
146
- action: 'create_new',
147
- confidence: 0.95,
148
- objective: 'Build the release dashboard',
149
- successCriteria: ['dashboard exists'],
150
- currentStep: 'Create the first dashboard draft',
151
- }),
152
- })
153
-
154
- const attached = await missions.resolveMissionForTurn({
155
- session: storage.loadSessions().sessionA,
156
- message: 'Now add a blocker summary section too.',
157
- source: 'chat',
158
- internal: false,
159
- runId: 'run-2',
160
- generateText: async () => JSON.stringify({
161
- action: 'attach_current',
162
- confidence: 0.89,
163
- currentStep: 'Add a blocker summary section',
164
- }),
165
- })
166
-
167
- const updated = await missions.applyMissionOutcomeForTurn({
168
- session: storage.loadSessions().sessionA,
169
- missionId: attached?.id || '',
170
- source: 'chat',
171
- runId: 'run-2',
172
- message: 'Now add a blocker summary section too.',
173
- assistantText: 'I need a design approval before I can finish the dashboard changes.',
174
- toolEvents: [],
175
- generateText: async () => JSON.stringify({
176
- verdict: 'waiting',
177
- confidence: 0.83,
178
- phase: 'waiting',
179
- currentStep: 'Wait for design approval',
180
- verifierSummary: 'The dashboard mission is waiting on a design approval.',
181
- waitKind: 'approval',
182
- waitReason: 'Design approval is still pending.',
183
- }),
184
- })
185
-
186
- console.log(JSON.stringify({
187
- missionId: created?.id || null,
188
- sameMission: created?.id === attached?.id,
189
- status: updated?.status || null,
190
- waitKind: updated?.waitState?.kind || null,
191
- waitReason: updated?.waitState?.reason || null,
192
- }))
193
- `, { prefix: 'swarmclaw-mission-service-' })
194
-
195
- assert.ok(output.missionId)
196
- assert.equal(output.sameMission, true)
197
- assert.equal(output.status, 'waiting')
198
- assert.equal(output.waitKind, 'approval')
199
- assert.equal(output.waitReason, 'Design approval is still pending.')
200
- })
201
-
202
- it('leaves unrelated one-shot turns missionless when classification says none', () => {
203
- const output = runWithTempDataDir<{
204
- resolvedMissionId: string | null
205
- sessionMissionId: string | null
206
- }>(`
207
- const storageMod = await import('@/lib/server/storage')
208
- const missionMod = await import('@/lib/server/missions/mission-service')
209
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
210
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
211
-
212
- storage.saveSessions({
213
- sessionA: {
214
- id: 'sessionA',
215
- name: 'Main Chat',
216
- cwd: process.env.WORKSPACE_DIR,
217
- user: 'tester',
218
- provider: 'ollama',
219
- model: 'test-model',
220
- claudeSessionId: null,
221
- messages: [],
222
- createdAt: 1,
223
- lastActiveAt: 1,
224
- agentId: 'agentA',
225
- missionId: 'missionA',
226
- },
227
- })
228
-
229
- storage.saveAgents({
230
- agentA: {
231
- id: 'agentA',
232
- name: 'Agent A',
233
- provider: 'ollama',
234
- model: 'test-model',
235
- systemPrompt: 'test',
236
- },
237
- })
238
-
239
- storage.saveMissions({
240
- missionA: {
241
- id: 'missionA',
242
- source: 'chat',
243
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
244
- objective: 'Long-running dashboard mission',
245
- status: 'active',
246
- phase: 'planning',
247
- sessionId: 'sessionA',
248
- agentId: 'agentA',
249
- taskIds: [],
250
- childMissionIds: [],
251
- dependencyMissionIds: [],
252
- dependencyTaskIds: [],
253
- currentStep: 'Wait for the next dashboard task',
254
- createdAt: 1,
255
- updatedAt: 1,
256
- },
257
- })
258
-
259
- const session = storage.loadSessions().sessionA
260
- const resolved = await missions.resolveMissionForTurn({
261
- session,
262
- message: 'Thanks',
263
- source: 'chat',
264
- internal: false,
265
- runId: 'run-1',
266
- generateText: async () => JSON.stringify({
267
- action: 'none',
268
- confidence: 0.98,
269
- }),
270
- })
271
-
272
- const persistedSession = storage.loadSessions().sessionA
273
- console.log(JSON.stringify({
274
- resolvedMissionId: resolved?.id || null,
275
- sessionMissionId: persistedSession.missionId || null,
276
- }))
277
- `, { prefix: 'swarmclaw-mission-service-' })
278
-
279
- assert.equal(output.resolvedMissionId, null)
280
- assert.equal(output.sessionMissionId, 'missionA')
281
- })
282
-
283
- it('dispatches linked backlog tasks from a mission tick', () => {
284
- const output = runWithTempDataDir<{
285
- missionId: string | null
286
- missionPhase: string | null
287
- taskStatus: string | null
288
- plannerDecision: string | null
289
- }>(`
290
- const storageMod = await import('@/lib/server/storage')
291
- const missionMod = await import('@/lib/server/missions/mission-service')
292
- const queueMod = await import('@/lib/server/runtime/queue')
293
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
294
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
295
- const queue = queueMod.default || queueMod['module.exports'] || queueMod
296
-
297
- storage.saveAgents({
298
- agentA: {
299
- id: 'agentA',
300
- name: 'Agent A',
301
- provider: 'ollama',
302
- model: 'test-model',
303
- systemPrompt: 'test',
304
- },
305
- })
306
-
307
- storage.saveTasks({
308
- taskA: {
309
- id: 'taskA',
310
- title: 'Generate release notes',
311
- description: 'Create the release notes artifact.',
312
- status: 'backlog',
313
- agentId: 'agentA',
314
- createdAt: 1,
315
- updatedAt: 1,
316
- },
317
- })
318
-
319
- const mission = missions.ensureMissionForTask(storage.loadTasks().taskA, { source: 'task' })
320
- const updated = await missions.runMissionTick(mission?.id || '', 'test', {
321
- generateText: async () => JSON.stringify({
322
- decision: 'dispatch_task',
323
- confidence: 0.96,
324
- summary: 'Queue the linked release notes task.',
325
- taskId: 'taskA',
326
- }),
327
- })
328
- const task = storage.loadTasks().taskA
329
-
330
- console.log(JSON.stringify({
331
- missionId: mission?.id || null,
332
- missionPhase: updated?.phase || null,
333
- taskStatus: task?.status || null,
334
- plannerDecision: updated?.plannerState?.lastDecision || null,
335
- }))
336
- `, { prefix: 'swarmclaw-mission-service-' })
337
-
338
- assert.ok(output.missionId)
339
- assert.equal(output.missionPhase, 'dispatching')
340
- assert.equal(output.taskStatus, 'queued')
341
- assert.equal(output.plannerDecision, 'dispatch_task')
342
- })
343
-
344
- it('marks task-backed missions completed when all linked tasks are complete', () => {
345
- const output = runWithTempDataDir<{
346
- missionStatus: string | null
347
- missionPhase: string | null
348
- lastVerdict: string | null
349
- eventTypes: string[]
350
- }>(`
351
- const storageMod = await import('@/lib/server/storage')
352
- const missionMod = await import('@/lib/server/missions/mission-service')
353
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
354
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
355
-
356
- storage.saveAgents({
357
- agentA: {
358
- id: 'agentA',
359
- name: 'Agent A',
360
- provider: 'ollama',
361
- model: 'test-model',
362
- systemPrompt: 'test',
363
- },
364
- })
365
-
366
- storage.saveTasks({
367
- taskA: {
368
- id: 'taskA',
369
- title: 'Verify the release checklist',
370
- description: 'Confirm the release checklist is complete.',
371
- status: 'completed',
372
- agentId: 'agentA',
373
- createdAt: 1,
374
- updatedAt: 2,
375
- completedAt: 2,
376
- },
377
- })
378
-
379
- const mission = missions.ensureMissionForTask(storage.loadTasks().taskA, { source: 'task' })
380
- const updated = await missions.runMissionTick(mission?.id || '', 'test')
381
- const events = missions.listMissionEventsForMission(mission?.id || '')
382
-
383
- console.log(JSON.stringify({
384
- missionStatus: updated?.status || null,
385
- missionPhase: updated?.phase || null,
386
- lastVerdict: updated?.verificationState?.lastVerdict || null,
387
- eventTypes: events.map((event) => event.type),
388
- }))
389
- `, { prefix: 'swarmclaw-mission-service-' })
390
-
391
- assert.equal(output.missionStatus, 'completed')
392
- assert.equal(output.missionPhase, 'completed')
393
- assert.equal(output.lastVerdict, 'completed')
394
- assert.deepEqual(output.eventTypes, ['created', 'task_linked', 'planner_decision', 'verifier_decision', 'completed'])
395
- })
396
-
397
- it('uses the structured planner to queue a mission follow-up turn', () => {
398
- const output = runWithTempDataDir<{
399
- missionPhase: string | null
400
- plannerDecision: string | null
401
- queuedCount: number
402
- queuedMissionId: string | null
403
- queuedText: string | null
404
- }>(`
405
- const storageMod = await import('@/lib/server/storage')
406
- const missionMod = await import('@/lib/server/missions/mission-service')
407
- const runsMod = await import('@/lib/server/runtime/session-run-manager')
408
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
409
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
410
- const runs = runsMod.default || runsMod['module.exports'] || runsMod
411
-
412
- storage.saveAgents({
413
- agentA: {
414
- id: 'agentA',
415
- name: 'Agent A',
416
- provider: 'ollama',
417
- model: 'test-model',
418
- systemPrompt: 'test',
419
- },
420
- })
421
-
422
- storage.saveSessions({
423
- sessionA: {
424
- id: 'sessionA',
425
- name: 'Mission Chat',
426
- cwd: process.env.WORKSPACE_DIR,
427
- user: 'tester',
428
- provider: 'ollama',
429
- model: 'test-model',
430
- claudeSessionId: null,
431
- messages: [],
432
- createdAt: 1,
433
- lastActiveAt: 1,
434
- agentId: 'agentA',
435
- },
436
- })
437
-
438
- storage.saveMissions({
439
- missionA: {
440
- id: 'missionA',
441
- source: 'schedule',
442
- sourceRef: { kind: 'schedule', scheduleId: 'sch-1', recurring: true },
443
- objective: 'Prepare the release handoff',
444
- status: 'active',
445
- phase: 'planning',
446
- sessionId: 'sessionA',
447
- agentId: 'agentA',
448
- taskIds: [],
449
- childMissionIds: [],
450
- dependencyMissionIds: [],
451
- dependencyTaskIds: [],
452
- currentStep: 'Summarize the remaining blockers',
453
- plannerSummary: 'Use the structured planner.',
454
- verifierSummary: null,
455
- blockerSummary: null,
456
- waitState: null,
457
- verificationState: { candidate: false },
458
- createdAt: 1,
459
- updatedAt: 1,
460
- },
461
- })
462
-
463
- const updated = await missions.runMissionTick('missionA', 'test', {
464
- generateText: async () => JSON.stringify({
465
- decision: 'dispatch_session_turn',
466
- confidence: 0.91,
467
- summary: 'Queue the next durable follow-up turn.',
468
- currentStep: 'Summarize the remaining blockers',
469
- sessionMessage: 'Continue the mission and summarize the remaining release blockers.',
470
- }),
471
- })
472
- const runList = runs.listRuns({ limit: 20 }).filter((run) => run.missionId === 'missionA')
473
-
474
- console.log(JSON.stringify({
475
- missionPhase: updated?.phase || null,
476
- plannerDecision: updated?.plannerState?.lastDecision || null,
477
- queuedCount: runList.filter((run) => run.status === 'queued' || run.status === 'running').length,
478
- queuedMissionId: runList[0]?.missionId || null,
479
- queuedText: runList[0]?.messagePreview || null,
480
- }))
481
- `, { prefix: 'swarmclaw-mission-service-' })
482
-
483
- assert.equal(output.missionPhase, 'dispatching')
484
- assert.equal(output.plannerDecision, 'dispatch_session_turn')
485
- assert.equal(output.queuedCount, 1)
486
- assert.equal(output.queuedMissionId, 'missionA')
487
- assert.match(output.queuedText || '', /summarize the remaining release blockers/i)
488
- })
489
-
490
- it('requests mission ticks when an approval resolves', () => {
491
- const output = runWithTempDataDir<{
492
- resumedCount: number
493
- missionStatus: string | null
494
- missionPhase: string | null
495
- eventTypes: string[]
496
- }>(`
497
- const storageMod = await import('@/lib/server/storage')
498
- const missionMod = await import('@/lib/server/missions/mission-service')
499
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
500
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
501
-
502
- storage.upsertApproval('approvalA', {
503
- id: 'approvalA',
504
- category: 'human_loop',
505
- title: 'Resume release mission',
506
- description: '',
507
- data: {},
508
- status: 'approved',
509
- createdAt: 1,
510
- updatedAt: 2,
511
- sessionId: 'sessionA',
512
- })
513
-
514
- storage.saveMissions({
515
- missionA: {
516
- id: 'missionA',
517
- source: 'chat',
518
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
519
- objective: 'Resume the release mission',
520
- status: 'waiting',
521
- phase: 'waiting',
522
- sessionId: 'sessionA',
523
- waitState: { kind: 'approval', reason: 'Waiting on approval.', approvalId: 'approvalA' },
524
- taskIds: [],
525
- createdAt: 1,
526
- updatedAt: 1,
527
- },
528
- })
529
-
530
- const resumed = missions.requestMissionTicksForApprovalDecision({
531
- approvalId: 'approvalA',
532
- status: 'approved',
533
- sessionId: 'sessionA',
534
- })
535
- const mission = missions.loadMissionById('missionA')
536
- const events = missions.listMissionEventsForMission('missionA')
537
-
538
- console.log(JSON.stringify({
539
- resumedCount: resumed.length,
540
- missionStatus: mission?.status || null,
541
- missionPhase: mission?.phase || null,
542
- eventTypes: events.map((event) => event.type),
543
- }))
544
- `, { prefix: 'swarmclaw-mission-service-' })
545
-
546
- assert.equal(output.resumedCount, 1)
547
- assert.equal(output.missionStatus, 'active')
548
- assert.equal(output.missionPhase, 'planning')
549
- assert.ok(output.eventTypes.includes('source_triggered'))
550
- })
551
-
552
- it('requests mission ticks when a human reply arrives', () => {
553
- const output = runWithTempDataDir<{
554
- resumedCount: number
555
- missionStatus: string | null
556
- missionPhase: string | null
557
- }>(`
558
- const storageMod = await import('@/lib/server/storage')
559
- const missionMod = await import('@/lib/server/missions/mission-service')
560
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
561
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
562
-
563
- storage.saveMissions({
564
- missionA: {
565
- id: 'missionA',
566
- source: 'connector',
567
- sourceRef: { kind: 'connector', sessionId: 'sessionA', connectorId: 'con-1', channelId: 'chan-1' },
568
- objective: 'Wait for the operator reply',
569
- status: 'waiting',
570
- phase: 'waiting',
571
- sessionId: 'sessionA',
572
- waitState: { kind: 'human_reply', reason: 'Waiting for operator reply.' },
573
- taskIds: [],
574
- createdAt: 1,
575
- updatedAt: 1,
576
- },
577
- })
578
-
579
- const resumed = missions.requestMissionTicksForHumanReply({
580
- sessionId: 'sessionA',
581
- correlationId: 'corr-1',
582
- payload: 'The operator replied with the final answer.',
583
- })
584
- const mission = missions.loadMissionById('missionA')
585
-
586
- console.log(JSON.stringify({
587
- resumedCount: resumed.length,
588
- missionStatus: mission?.status || null,
589
- missionPhase: mission?.phase || null,
590
- }))
591
- `, { prefix: 'swarmclaw-mission-service-' })
592
-
593
- assert.equal(output.resumedCount, 1)
594
- assert.equal(output.missionStatus, 'active')
595
- assert.equal(output.missionPhase, 'planning')
596
- })
597
-
598
- it('does not wake non-human waiting missions on unrelated replies', () => {
599
- const output = runWithTempDataDir<{
600
- resumedCount: number
601
- missionStatus: string | null
602
- missionPhase: string | null
603
- eventTypes: string[]
604
- }>(`
605
- const storageMod = await import('@/lib/server/storage')
606
- const missionMod = await import('@/lib/server/missions/mission-service')
607
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
608
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
609
-
610
- storage.saveMissions({
611
- missionA: {
612
- id: 'missionA',
613
- source: 'chat',
614
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
615
- objective: 'Wait for approval before shipping',
616
- status: 'waiting',
617
- phase: 'waiting',
618
- sessionId: 'sessionA',
619
- waitState: { kind: 'approval', reason: 'Waiting for approval.' },
620
- taskIds: [],
621
- createdAt: 1,
622
- updatedAt: 1,
623
- },
624
- })
625
-
626
- const resumed = missions.requestMissionTicksForHumanReply({
627
- sessionId: 'sessionA',
628
- correlationId: 'corr-1',
629
- payload: 'Here is an unrelated follow-up.',
630
- })
631
- const mission = missions.loadMissionById('missionA')
632
- const events = missions.listMissionEventsForMission('missionA')
633
-
634
- console.log(JSON.stringify({
635
- resumedCount: resumed.length,
636
- missionStatus: mission?.status || null,
637
- missionPhase: mission?.phase || null,
638
- eventTypes: events.map((event) => event.type),
639
- }))
640
- `, { prefix: 'swarmclaw-mission-service-' })
641
-
642
- assert.equal(output.resumedCount, 0)
643
- assert.equal(output.missionStatus, 'waiting')
644
- assert.equal(output.missionPhase, 'waiting')
645
- assert.equal(output.eventTypes.includes('source_triggered'), false)
646
- })
647
-
648
- it('closes mission outcomes that only wait for a human reply when mission human loop is disabled', () => {
649
- const output = runWithTempDataDir<{
650
- missionStatus: string | null
651
- missionPhase: string | null
652
- verifierSummary: string | null
653
- eventTypes: string[]
654
- }>(`
655
- const storageMod = await import('@/lib/server/storage')
656
- const missionMod = await import('@/lib/server/missions/mission-service')
657
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
658
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
659
-
660
- storage.saveSettings({ missionHumanLoopEnabled: false })
661
- storage.saveSessions({
662
- sessionA: {
663
- id: 'sessionA',
664
- name: 'Mission Chat',
665
- cwd: process.env.WORKSPACE_DIR,
666
- user: 'tester',
667
- provider: 'ollama',
668
- model: 'test-model',
669
- claudeSessionId: null,
670
- messages: [],
671
- createdAt: 1,
672
- lastActiveAt: 1,
673
- agentId: 'agentA',
674
- },
675
- })
676
- storage.saveAgents({
677
- agentA: {
678
- id: 'agentA',
679
- name: 'Agent A',
680
- provider: 'ollama',
681
- model: 'test-model',
682
- systemPrompt: 'test',
683
- },
684
- })
685
- storage.saveMissions({
686
- missionA: {
687
- id: 'missionA',
688
- source: 'chat',
689
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
690
- objective: 'Create one small file',
691
- status: 'active',
692
- phase: 'executing',
693
- sessionId: 'sessionA',
694
- agentId: 'agentA',
695
- taskIds: [],
696
- childMissionIds: [],
697
- dependencyMissionIds: [],
698
- dependencyTaskIds: [],
699
- currentStep: 'Write the file',
700
- createdAt: 1,
701
- updatedAt: 1,
702
- },
703
- })
704
-
705
- const updated = await missions.applyMissionOutcomeForTurn({
706
- session: storage.loadSessions().sessionA,
707
- missionId: 'missionA',
708
- source: 'chat',
709
- runId: 'run-1',
710
- message: 'Create mission.txt with start in it.',
711
- assistantText: 'Done. Let me know what you want next.',
712
- toolEvents: [],
713
- generateText: async () => JSON.stringify({
714
- verdict: 'waiting',
715
- confidence: 0.91,
716
- phase: 'waiting',
717
- waitKind: 'human_reply',
718
- waitReason: 'Waiting for the user to say what to do next.',
719
- verifierSummary: 'The file is done and the mission is waiting for the next instruction.',
720
- }),
721
- })
722
- const events = missions.listMissionEventsForMission('missionA')
723
-
724
- console.log(JSON.stringify({
725
- missionStatus: updated?.status || null,
726
- missionPhase: updated?.phase || null,
727
- verifierSummary: updated?.verifierSummary || null,
728
- eventTypes: events.map((event) => event.type),
729
- }))
730
- `, { prefix: 'swarmclaw-mission-service-' })
731
-
732
- assert.equal(output.missionStatus, 'completed')
733
- assert.equal(output.missionPhase, 'completed')
734
- assert.match(String(output.verifierSummary || ''), /human-loop waits are disabled/i)
735
- assert.ok(output.eventTypes.includes('completed'))
736
- assert.equal(output.eventTypes.includes('waiting'), false)
737
- })
738
-
739
- it('does not leave planner ticks waiting for a human reply when mission human loop is disabled', () => {
740
- const output = runWithTempDataDir<{
741
- missionStatus: string | null
742
- missionPhase: string | null
743
- plannerDecision: string | null
744
- lastVerdict: string | null
745
- }>(`
746
- const storageMod = await import('@/lib/server/storage')
747
- const missionMod = await import('@/lib/server/missions/mission-service')
748
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
749
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
750
-
751
- storage.saveSettings({ missionHumanLoopEnabled: false })
752
- storage.saveMissions({
753
- missionA: {
754
- id: 'missionA',
755
- source: 'manual',
756
- sourceRef: { kind: 'manual' },
757
- objective: 'Finish the small file task',
758
- status: 'active',
759
- phase: 'planning',
760
- taskIds: [],
761
- childMissionIds: [],
762
- dependencyMissionIds: [],
763
- dependencyTaskIds: [],
764
- currentStep: 'Close out the task',
765
- createdAt: 1,
766
- updatedAt: 1,
767
- },
768
- })
769
-
770
- const updated = await missions.runMissionTick('missionA', 'test', {
771
- generateText: async () => JSON.stringify({
772
- decision: 'wait',
773
- confidence: 0.88,
774
- summary: 'Waiting for the user to say what to do next.',
775
- waitKind: 'human_reply',
776
- waitReason: 'Waiting for the next instruction.',
777
- }),
778
- })
779
-
780
- console.log(JSON.stringify({
781
- missionStatus: updated?.status || null,
782
- missionPhase: updated?.phase || null,
783
- plannerDecision: updated?.plannerState?.lastDecision || null,
784
- lastVerdict: updated?.verificationState?.lastVerdict || null,
785
- }))
786
- `, { prefix: 'swarmclaw-mission-service-' })
787
-
788
- assert.equal(output.missionStatus, 'completed')
789
- assert.equal(output.missionPhase, 'completed')
790
- assert.equal(output.plannerDecision, 'verify_now')
791
- assert.equal(output.lastVerdict, 'completed')
792
- })
793
-
794
- it('requests mission ticks when provider recovery clears a provider wait', () => {
795
- const output = runWithTempDataDir<{
796
- resumedCount: number
797
- missionStatus: string | null
798
- missionPhase: string | null
799
- }>(`
800
- const missionMod = await import('@/lib/server/missions/mission-service')
801
- const storageMod = await import('@/lib/server/storage')
802
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
803
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
804
-
805
- storage.saveMissions({
806
- missionA: {
807
- id: 'missionA',
808
- source: 'chat',
809
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
810
- objective: 'Wait for provider recovery',
811
- status: 'waiting',
812
- phase: 'waiting',
813
- sessionId: 'sessionA',
814
- waitState: { kind: 'provider', reason: 'Provider connection failed.', providerKey: 'ollama' },
815
- taskIds: [],
816
- createdAt: 1,
817
- updatedAt: 1,
818
- },
819
- })
820
-
821
- const resumed = missions.requestMissionTicksForProviderRecovery('ollama')
822
- const mission = missions.loadMissionById('missionA')
823
-
824
- console.log(JSON.stringify({
825
- resumedCount: resumed.length,
826
- missionStatus: mission?.status || null,
827
- missionPhase: mission?.phase || null,
828
- }))
829
- `, { prefix: 'swarmclaw-mission-service-' })
830
-
831
- assert.equal(output.resumedCount, 1)
832
- assert.equal(output.missionStatus, 'active')
833
- assert.equal(output.missionPhase, 'planning')
834
- })
835
-
836
- it('reconciles stale executing missions on startup', () => {
837
- const output = runWithTempDataDir<{
838
- beforeStatus: string | null
839
- beforePhase: string | null
840
- missionStatus: string | null
841
- missionPhase: string | null
842
- eventTypes: string[]
843
- }>(`
844
- const storageMod = await import('@/lib/server/storage')
845
- const missionMod = await import('@/lib/server/missions/mission-service')
846
- const storage = storageMod.default || storageMod['module.exports'] || storageMod
847
- const missions = missionMod.default || missionMod['module.exports'] || missionMod
848
-
849
- storage.saveMissions({
850
- missionA: {
851
- id: 'missionA',
852
- source: 'chat',
853
- sourceRef: { kind: 'chat', sessionId: 'sessionA' },
854
- objective: 'Recover after restart',
855
- status: 'active',
856
- phase: 'executing',
857
- sessionId: 'sessionA',
858
- taskIds: [],
859
- controllerState: {
860
- activeRunId: 'run-stale',
861
- currentTaskId: 'task-stale',
862
- },
863
- createdAt: 1,
864
- updatedAt: 1,
865
- },
866
- })
867
-
868
- const before = missions.loadMissionById('missionA')
869
- missions.runMissionControllerStartupRecovery()
870
- const mission = missions.loadMissionById('missionA')
871
- const events = missions.listMissionEventsForMission('missionA')
872
-
873
- console.log(JSON.stringify({
874
- beforeStatus: before?.status || null,
875
- beforePhase: before?.phase || null,
876
- missionStatus: mission?.status || null,
877
- missionPhase: mission?.phase || null,
878
- eventTypes: events.map((event) => event.type),
879
- }))
880
- `, { prefix: 'swarmclaw-mission-service-' })
881
-
882
- assert.equal(output.beforeStatus, 'active')
883
- assert.equal(output.beforePhase, 'executing')
884
- assert.equal(output.missionStatus, 'active')
885
- assert.equal(output.missionPhase, 'planning')
886
- assert.ok(output.eventTypes.includes('interrupted'))
887
- })
888
- })