@swarmclawai/swarmclaw 1.2.0 → 1.2.2

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 (241) hide show
  1. package/README.md +19 -0
  2. package/package.json +5 -2
  3. package/skills/coding-agent/SKILL.md +111 -0
  4. package/skills/github/SKILL.md +140 -0
  5. package/skills/nano-banana-pro/SKILL.md +62 -0
  6. package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
  7. package/skills/nano-pdf/SKILL.md +53 -0
  8. package/skills/openai-image-gen/SKILL.md +78 -0
  9. package/skills/openai-image-gen/scripts/gen.py +328 -0
  10. package/skills/resourceful-problem-solving/SKILL.md +49 -0
  11. package/skills/skill-creator/SKILL.md +147 -0
  12. package/skills/skill-creator/scripts/init_skill.py +378 -0
  13. package/skills/skill-creator/scripts/quick_validate.py +159 -0
  14. package/skills/summarize/SKILL.md +77 -0
  15. package/src/app/api/auth/route.ts +20 -5
  16. package/src/app/api/chats/[id]/deploy/route.ts +11 -6
  17. package/src/app/api/chats/[id]/devserver/route.ts +17 -20
  18. package/src/app/api/chats/[id]/messages/route.ts +15 -11
  19. package/src/app/api/chats/[id]/route.ts +9 -10
  20. package/src/app/api/chats/[id]/stop/route.ts +5 -7
  21. package/src/app/api/chats/messages-route.test.ts +8 -6
  22. package/src/app/api/chats/route.ts +9 -10
  23. package/src/app/api/credentials/[id]/route.ts +4 -1
  24. package/src/app/api/extensions/marketplace/route.ts +5 -2
  25. package/src/app/api/ip/route.ts +2 -2
  26. package/src/app/api/memory/maintenance/route.ts +5 -2
  27. package/src/app/api/preview-server/route.ts +15 -12
  28. package/src/app/api/projects/[id]/route.ts +7 -46
  29. package/src/app/api/system/status/route.ts +11 -0
  30. package/src/app/api/upload/route.ts +4 -1
  31. package/src/cli/index.js +7 -0
  32. package/src/cli/spec.js +1 -0
  33. package/src/components/agents/agent-files-editor.tsx +44 -32
  34. package/src/components/agents/personality-builder.tsx +13 -7
  35. package/src/components/agents/trash-list.tsx +1 -1
  36. package/src/components/chat/chat-area.tsx +45 -23
  37. package/src/components/chat/message-bubble.test.ts +35 -0
  38. package/src/components/chat/message-bubble.tsx +20 -9
  39. package/src/components/chat/message-list.tsx +62 -42
  40. package/src/components/chat/swarm-status-card.tsx +10 -3
  41. package/src/components/input/chat-input.tsx +34 -14
  42. package/src/components/layout/daemon-indicator.tsx +7 -8
  43. package/src/components/layout/update-banner.tsx +8 -13
  44. package/src/components/logs/log-list.tsx +1 -1
  45. package/src/components/memory/memory-card.tsx +3 -1
  46. package/src/components/org-chart/org-chart-view.tsx +4 -0
  47. package/src/components/projects/project-list.tsx +4 -2
  48. package/src/components/projects/tabs/overview-tab.tsx +3 -2
  49. package/src/components/secrets/secret-sheet.tsx +1 -1
  50. package/src/components/secrets/secrets-list.tsx +1 -1
  51. package/src/components/shared/agent-switch-dialog.tsx +12 -6
  52. package/src/components/shared/dir-browser.tsx +22 -18
  53. package/src/components/skills/skill-sheet.tsx +2 -3
  54. package/src/components/tasks/task-list.tsx +1 -1
  55. package/src/components/tasks/task-sheet.tsx +1 -1
  56. package/src/hooks/use-openclaw-gateway.ts +46 -27
  57. package/src/instrumentation.ts +10 -7
  58. package/src/lib/chat/assistant-render-id.ts +3 -0
  59. package/src/lib/chat/chat-streaming-state.test.ts +42 -3
  60. package/src/lib/chat/chat-streaming-state.ts +20 -8
  61. package/src/lib/chat/chat.ts +18 -2
  62. package/src/lib/chat/queued-message-queue.test.ts +23 -1
  63. package/src/lib/chat/queued-message-queue.ts +11 -2
  64. package/src/lib/providers/anthropic.ts +6 -3
  65. package/src/lib/providers/claude-cli.ts +9 -3
  66. package/src/lib/providers/cli-utils.test.ts +124 -0
  67. package/src/lib/providers/cli-utils.ts +15 -0
  68. package/src/lib/providers/codex-cli.ts +9 -3
  69. package/src/lib/providers/gemini-cli.ts +6 -2
  70. package/src/lib/providers/index.ts +4 -1
  71. package/src/lib/providers/ollama.ts +5 -2
  72. package/src/lib/providers/openai.ts +8 -5
  73. package/src/lib/providers/opencode-cli.ts +6 -2
  74. package/src/lib/server/activity/activity-log.ts +21 -0
  75. package/src/lib/server/agents/agent-availability.test.ts +10 -5
  76. package/src/lib/server/agents/agent-cascade.ts +79 -59
  77. package/src/lib/server/agents/agent-registry.ts +23 -4
  78. package/src/lib/server/agents/agent-repository.ts +90 -0
  79. package/src/lib/server/agents/delegation-job-repository.ts +53 -0
  80. package/src/lib/server/agents/delegation-jobs.ts +11 -4
  81. package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
  82. package/src/lib/server/agents/guardian.ts +2 -2
  83. package/src/lib/server/agents/main-agent-loop.ts +14 -6
  84. package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
  85. package/src/lib/server/agents/subagent-runtime.ts +9 -6
  86. package/src/lib/server/agents/subagent-swarm.ts +3 -2
  87. package/src/lib/server/agents/task-session.ts +3 -4
  88. package/src/lib/server/approvals/approval-repository.ts +30 -0
  89. package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
  90. package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
  91. package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
  92. package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
  93. package/src/lib/server/chat-execution/chat-execution.ts +84 -1914
  94. package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
  95. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
  96. package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
  97. package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
  98. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
  99. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
  100. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
  101. package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
  102. package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
  103. package/src/lib/server/chat-execution/message-classifier.ts +5 -2
  104. package/src/lib/server/chat-execution/post-stream-finalization.ts +5 -2
  105. package/src/lib/server/chat-execution/prompt-builder.ts +22 -1
  106. package/src/lib/server/chat-execution/prompt-sections.ts +55 -13
  107. package/src/lib/server/chat-execution/response-completeness.ts +5 -2
  108. package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
  109. package/src/lib/server/chat-execution/stream-agent-chat.ts +58 -25
  110. package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
  111. package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
  112. package/src/lib/server/connectors/bluebubbles.ts +7 -4
  113. package/src/lib/server/connectors/connector-inbound.ts +16 -13
  114. package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
  115. package/src/lib/server/connectors/connector-outbound.ts +6 -3
  116. package/src/lib/server/connectors/connector-repository.ts +58 -0
  117. package/src/lib/server/connectors/discord.ts +10 -7
  118. package/src/lib/server/connectors/email.ts +17 -14
  119. package/src/lib/server/connectors/googlechat.ts +7 -4
  120. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
  121. package/src/lib/server/connectors/matrix.ts +6 -3
  122. package/src/lib/server/connectors/openclaw.ts +20 -17
  123. package/src/lib/server/connectors/outbox.ts +4 -1
  124. package/src/lib/server/connectors/runtime-state.test.ts +117 -0
  125. package/src/lib/server/connectors/runtime-state.ts +19 -0
  126. package/src/lib/server/connectors/session-consolidation.ts +5 -2
  127. package/src/lib/server/connectors/signal.ts +9 -6
  128. package/src/lib/server/connectors/slack.ts +13 -10
  129. package/src/lib/server/connectors/teams.ts +8 -5
  130. package/src/lib/server/connectors/telegram.ts +15 -12
  131. package/src/lib/server/connectors/whatsapp.ts +32 -29
  132. package/src/lib/server/credentials/credential-repository.ts +7 -0
  133. package/src/lib/server/embeddings.ts +4 -1
  134. package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
  135. package/src/lib/server/link-understanding.ts +4 -1
  136. package/src/lib/server/memory/memory-abstract.test.ts +59 -0
  137. package/src/lib/server/memory/memory-abstract.ts +59 -0
  138. package/src/lib/server/memory/memory-db.ts +40 -14
  139. package/src/lib/server/missions/mission-repository.ts +74 -0
  140. package/src/lib/server/missions/mission-service/actions.ts +6 -0
  141. package/src/lib/server/missions/mission-service/bindings.ts +9 -0
  142. package/src/lib/server/missions/mission-service/context.ts +4 -0
  143. package/src/lib/server/missions/mission-service/core.ts +2269 -0
  144. package/src/lib/server/missions/mission-service/queries.ts +12 -0
  145. package/src/lib/server/missions/mission-service/recovery.ts +5 -0
  146. package/src/lib/server/missions/mission-service/ticks.ts +9 -0
  147. package/src/lib/server/missions/mission-service.test.ts +9 -2
  148. package/src/lib/server/missions/mission-service.ts +6 -2263
  149. package/src/lib/server/openclaw/gateway.ts +8 -5
  150. package/src/lib/server/persistence/repository-utils.ts +154 -0
  151. package/src/lib/server/persistence/storage-context.ts +51 -0
  152. package/src/lib/server/persistence/transaction.ts +1 -0
  153. package/src/lib/server/project-utils.ts +13 -0
  154. package/src/lib/server/projects/project-repository.ts +36 -0
  155. package/src/lib/server/projects/project-service.ts +79 -0
  156. package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
  157. package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
  158. package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
  159. package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
  160. package/src/lib/server/provider-health.ts +18 -0
  161. package/src/lib/server/query-expansion.ts +4 -1
  162. package/src/lib/server/runtime/alert-dispatch.ts +8 -7
  163. package/src/lib/server/runtime/daemon-policy.ts +1 -1
  164. package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
  165. package/src/lib/server/runtime/daemon-state/health.ts +6 -0
  166. package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
  167. package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
  168. package/src/lib/server/runtime/daemon-state.test.ts +48 -0
  169. package/src/lib/server/runtime/daemon-state.ts +3 -1331
  170. package/src/lib/server/runtime/estop-repository.ts +4 -0
  171. package/src/lib/server/runtime/estop.ts +3 -1
  172. package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
  173. package/src/lib/server/runtime/heartbeat-service.ts +78 -34
  174. package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
  175. package/src/lib/server/runtime/idle-window.ts +6 -3
  176. package/src/lib/server/runtime/network.ts +11 -0
  177. package/src/lib/server/runtime/orchestrator-events.ts +2 -2
  178. package/src/lib/server/runtime/perf.ts +4 -1
  179. package/src/lib/server/runtime/process-manager.ts +7 -4
  180. package/src/lib/server/runtime/queue/claims.ts +4 -0
  181. package/src/lib/server/runtime/queue/core.ts +2079 -0
  182. package/src/lib/server/runtime/queue/execution.ts +7 -0
  183. package/src/lib/server/runtime/queue/followups.ts +4 -0
  184. package/src/lib/server/runtime/queue/queries.ts +12 -0
  185. package/src/lib/server/runtime/queue/recovery.ts +7 -0
  186. package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
  187. package/src/lib/server/runtime/queue-repository.ts +17 -0
  188. package/src/lib/server/runtime/queue.ts +5 -2058
  189. package/src/lib/server/runtime/run-ledger.ts +6 -5
  190. package/src/lib/server/runtime/run-repository.ts +73 -0
  191. package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
  192. package/src/lib/server/runtime/runtime-settings.ts +1 -1
  193. package/src/lib/server/runtime/runtime-state.ts +99 -0
  194. package/src/lib/server/runtime/scheduler.ts +13 -8
  195. package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
  196. package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
  197. package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
  198. package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
  199. package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
  200. package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
  201. package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
  202. package/src/lib/server/runtime/session-run-manager.ts +72 -1374
  203. package/src/lib/server/runtime/watch-job-repository.ts +35 -0
  204. package/src/lib/server/runtime/watch-jobs.ts +3 -1
  205. package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
  206. package/src/lib/server/sandbox/novnc-auth.ts +10 -0
  207. package/src/lib/server/schedules/schedule-repository.ts +42 -0
  208. package/src/lib/server/session-tools/context.ts +14 -0
  209. package/src/lib/server/session-tools/discovery.ts +9 -6
  210. package/src/lib/server/session-tools/index.ts +3 -1
  211. package/src/lib/server/session-tools/platform.ts +1 -1
  212. package/src/lib/server/session-tools/subagent.ts +23 -2
  213. package/src/lib/server/session-tools/wallet.ts +4 -1
  214. package/src/lib/server/sessions/session-repository.ts +85 -0
  215. package/src/lib/server/settings/settings-repository.ts +25 -0
  216. package/src/lib/server/skills/clawhub-client.ts +4 -1
  217. package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
  218. package/src/lib/server/skills/skill-discovery.test.ts +2 -2
  219. package/src/lib/server/skills/skill-discovery.ts +2 -2
  220. package/src/lib/server/skills/skill-eligibility.ts +6 -0
  221. package/src/lib/server/skills/skill-repository.ts +14 -0
  222. package/src/lib/server/solana.ts +6 -0
  223. package/src/lib/server/storage-auth.ts +5 -5
  224. package/src/lib/server/storage-normalization.ts +4 -0
  225. package/src/lib/server/storage.ts +32 -32
  226. package/src/lib/server/tasks/task-followups.ts +4 -1
  227. package/src/lib/server/tasks/task-repository.ts +54 -0
  228. package/src/lib/server/tool-loop-detection.ts +8 -3
  229. package/src/lib/server/tool-planning.ts +226 -0
  230. package/src/lib/server/tool-retry.ts +4 -3
  231. package/src/lib/server/usage/usage-repository.ts +30 -0
  232. package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
  233. package/src/lib/server/webhooks/webhook-repository.ts +10 -0
  234. package/src/lib/server/ws-hub.ts +5 -2
  235. package/src/lib/strip-internal-metadata.test.ts +78 -37
  236. package/src/lib/strip-internal-metadata.ts +20 -6
  237. package/src/stores/use-approval-store.ts +7 -1
  238. package/src/stores/use-chat-store.test.ts +54 -0
  239. package/src/stores/use-chat-store.ts +26 -6
  240. package/src/types/index.ts +6 -0
  241. /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
@@ -1,7 +1,8 @@
1
1
  import { genId } from '@/lib/id'
2
2
  import type { RunEventRecord, SessionRunRecord, SessionRunStatus, SSEEvent } from '@/types'
3
3
  import {
4
- deleteStoredItem,
4
+ deleteRuntimeRun,
5
+ deleteRuntimeRunEvent,
5
6
  loadRuntimeRun,
6
7
  loadRuntimeRunEvents,
7
8
  loadRuntimeRunEventsByRunId,
@@ -9,7 +10,7 @@ import {
9
10
  patchRuntimeRun,
10
11
  upsertRuntimeRun,
11
12
  upsertRuntimeRunEvent,
12
- } from '@/lib/server/storage'
13
+ } from '@/lib/server/runtime/run-repository'
13
14
 
14
15
  const MAX_SUMMARY_CHARS = 240
15
16
  const RESTART_RECOVERABLE_SOURCES = new Set([
@@ -137,7 +138,7 @@ export function pruneOldRuns(): { prunedRuns: number; prunedEvents: number } {
137
138
  // Non-terminal (running/queued) — only prune if stuck for much longer
138
139
  if (deadline - endTs < ORPHANED_RUN_RETENTION_MS) continue
139
140
  }
140
- deleteStoredItem('runtime_runs', id)
141
+ deleteRuntimeRun(id)
141
142
  prunedRunIds.add(id)
142
143
  prunedRuns++
143
144
  }
@@ -146,7 +147,7 @@ export function pruneOldRuns(): { prunedRuns: number; prunedEvents: number } {
146
147
  const events = loadRuntimeRunEvents()
147
148
  for (const [id, event] of Object.entries(events)) {
148
149
  if (prunedRunIds.has(event.runId)) {
149
- deleteStoredItem('runtime_run_events', id)
150
+ deleteRuntimeRunEvent(id)
150
151
  prunedEvents++
151
152
  continue
152
153
  }
@@ -154,7 +155,7 @@ export function pruneOldRuns(): { prunedRuns: number; prunedEvents: number } {
154
155
  const parentRun = runs[event.runId]
155
156
  if (!parentRun || !TERMINAL_STATUSES.has(parentRun.status)) continue
156
157
  if (deadline - event.timestamp < RUN_EVENT_RETENTION_MS) continue
157
- deleteStoredItem('runtime_run_events', id)
158
+ deleteRuntimeRunEvent(id)
158
159
  prunedEvents++
159
160
  }
160
161
 
@@ -0,0 +1,73 @@
1
+ import type { RunEventRecord, SessionRunRecord } from '@/types'
2
+
3
+ import {
4
+ deleteStoredItem,
5
+ loadRuntimeRun as loadStoredRuntimeRun,
6
+ loadRuntimeRunEvents as loadStoredRuntimeRunEvents,
7
+ loadRuntimeRunEventsByRunId as loadStoredRuntimeRunEventsByRunId,
8
+ loadRuntimeRuns as loadStoredRuntimeRuns,
9
+ patchRuntimeRun as patchStoredRuntimeRun,
10
+ saveRuntimeRunEvents as saveStoredRuntimeRunEvents,
11
+ saveRuntimeRuns as saveStoredRuntimeRuns,
12
+ upsertRuntimeRun as upsertStoredRuntimeRun,
13
+ upsertRuntimeRunEvent as upsertStoredRuntimeRunEvent,
14
+ } from '@/lib/server/storage'
15
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
16
+
17
+ export const runRepository = createRecordRepository<SessionRunRecord>(
18
+ 'runtime-runs',
19
+ {
20
+ get(id) {
21
+ return loadStoredRuntimeRun(id) as SessionRunRecord | null
22
+ },
23
+ list() {
24
+ return loadStoredRuntimeRuns() as Record<string, SessionRunRecord>
25
+ },
26
+ upsert(id, value) {
27
+ upsertStoredRuntimeRun(id, value as SessionRunRecord)
28
+ },
29
+ replace(data) {
30
+ saveStoredRuntimeRuns(data as Record<string, SessionRunRecord>)
31
+ },
32
+ patch(id, updater) {
33
+ return patchStoredRuntimeRun(id, updater as (current: SessionRunRecord | null) => SessionRunRecord | null) as SessionRunRecord | null
34
+ },
35
+ delete(id) {
36
+ deleteStoredItem('runtime_runs', id)
37
+ },
38
+ },
39
+ )
40
+
41
+ export const runEventRepository = createRecordRepository<RunEventRecord>(
42
+ 'runtime-run-events',
43
+ {
44
+ get(id) {
45
+ return (loadStoredRuntimeRunEvents() as Record<string, RunEventRecord>)[id] || null
46
+ },
47
+ list() {
48
+ return loadStoredRuntimeRunEvents() as Record<string, RunEventRecord>
49
+ },
50
+ upsert(id, value) {
51
+ upsertStoredRuntimeRunEvent(id, value as RunEventRecord)
52
+ },
53
+ replace(data) {
54
+ saveStoredRuntimeRunEvents(data as Record<string, RunEventRecord>)
55
+ },
56
+ delete(id) {
57
+ deleteStoredItem('runtime_run_events', id)
58
+ },
59
+ },
60
+ )
61
+
62
+ export const loadRuntimeRuns = () => runRepository.list()
63
+ export const saveRuntimeRuns = (items: Record<string, SessionRunRecord | Record<string, unknown>>) => runRepository.replace(items as Record<string, SessionRunRecord>)
64
+ export const loadRuntimeRun = (id: string) => runRepository.get(id)
65
+ export const upsertRuntimeRun = (id: string, value: SessionRunRecord | Record<string, unknown>) => runRepository.upsert(id, value as SessionRunRecord)
66
+ export const patchRuntimeRun = (id: string, updater: (current: SessionRunRecord | null) => SessionRunRecord | null) => runRepository.patch(id, updater)
67
+
68
+ export const loadRuntimeRunEvents = () => runEventRepository.list()
69
+ export const saveRuntimeRunEvents = (items: Record<string, RunEventRecord | Record<string, unknown>>) => runEventRepository.replace(items as Record<string, RunEventRecord>)
70
+ export const upsertRuntimeRunEvent = (id: string, value: RunEventRecord | Record<string, unknown>) => runEventRepository.upsert(id, value as RunEventRecord)
71
+ export const loadRuntimeRunEventsByRunId = (runId: string) => loadStoredRuntimeRunEventsByRunId(runId)
72
+ export const deleteRuntimeRun = (id: string) => runRepository.delete(id)
73
+ export const deleteRuntimeRunEvent = (id: string) => runEventRepository.delete(id)
@@ -0,0 +1,8 @@
1
+ export {
2
+ isRuntimeLockActive,
3
+ pruneExpiredLocks,
4
+ readRuntimeLock,
5
+ releaseRuntimeLock,
6
+ renewRuntimeLock,
7
+ tryAcquireRuntimeLock,
8
+ } from '@/lib/server/storage'
@@ -2,7 +2,7 @@ import type { LoopMode } from '@/types'
2
2
  import {
3
3
  normalizeRuntimeSettingFields,
4
4
  } from '@/lib/runtime/runtime-loop'
5
- import { loadSettings } from '@/lib/server/storage'
5
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
6
6
 
7
7
  export interface RuntimeSettings {
8
8
  loopMode: LoopMode
@@ -0,0 +1,99 @@
1
+ import type { ChildProcess } from 'node:child_process'
2
+
3
+ import { hmrSingleton } from '@/lib/shared-utils'
4
+
5
+ export type ActiveSessionProcess = {
6
+ runId?: string | null
7
+ source?: string
8
+ kill: (signal?: NodeJS.Signals | number) => boolean | void
9
+ }
10
+
11
+ export interface DevServerRuntime {
12
+ proc: ChildProcess
13
+ url: string
14
+ }
15
+
16
+ interface RuntimeStateRegistry {
17
+ activeSessionProcesses: Map<string, ActiveSessionProcess>
18
+ devServers: Map<string, DevServerRuntime>
19
+ }
20
+
21
+ const state = hmrSingleton<RuntimeStateRegistry>('__swarmclaw_runtime_state__', () => ({
22
+ activeSessionProcesses: new Map<string, ActiveSessionProcess>(),
23
+ devServers: new Map<string, DevServerRuntime>(),
24
+ }))
25
+
26
+ if (!state.activeSessionProcesses) state.activeSessionProcesses = new Map<string, ActiveSessionProcess>()
27
+ if (!state.devServers) state.devServers = new Map<string, DevServerRuntime>()
28
+
29
+ export const activeSessionProcesses = state.activeSessionProcesses
30
+ export const devServers = state.devServers
31
+
32
+ export function getActiveSessionProcess(sessionId: string): ActiveSessionProcess | undefined {
33
+ return state.activeSessionProcesses.get(sessionId)
34
+ }
35
+
36
+ export function hasActiveSessionProcess(sessionId: string): boolean {
37
+ return state.activeSessionProcesses.has(sessionId)
38
+ }
39
+
40
+ export function registerActiveSessionProcess(sessionId: string, process: ActiveSessionProcess): void {
41
+ state.activeSessionProcesses.set(sessionId, process)
42
+ }
43
+
44
+ export function stopActiveSessionProcess(sessionId: string, signal?: NodeJS.Signals | number): boolean {
45
+ const process = state.activeSessionProcesses.get(sessionId)
46
+ if (!process) return false
47
+ try {
48
+ process.kill(signal)
49
+ } catch {
50
+ // Ignore process teardown errors during cleanup.
51
+ }
52
+ state.activeSessionProcesses.delete(sessionId)
53
+ return true
54
+ }
55
+
56
+ export function clearActiveSessionProcess(sessionId: string): void {
57
+ state.activeSessionProcesses.delete(sessionId)
58
+ }
59
+
60
+ export function getDevServer(sessionId: string): DevServerRuntime | undefined {
61
+ return state.devServers.get(sessionId)
62
+ }
63
+
64
+ export function hasDevServer(sessionId: string): boolean {
65
+ return state.devServers.has(sessionId)
66
+ }
67
+
68
+ export function registerDevServer(sessionId: string, runtime: DevServerRuntime): void {
69
+ state.devServers.set(sessionId, runtime)
70
+ }
71
+
72
+ export function updateDevServerUrl(sessionId: string, url: string): void {
73
+ const runtime = state.devServers.get(sessionId)
74
+ if (!runtime) return
75
+ runtime.url = url
76
+ }
77
+
78
+ export function stopDevServer(sessionId: string): boolean {
79
+ const runtime = state.devServers.get(sessionId)
80
+ if (!runtime) return false
81
+ try {
82
+ runtime.proc.kill('SIGTERM')
83
+ } catch {
84
+ // Ignore process teardown errors during cleanup.
85
+ }
86
+ if (typeof runtime.proc.pid === 'number') {
87
+ try {
88
+ process.kill(-runtime.proc.pid, 'SIGTERM')
89
+ } catch {
90
+ // Ignore process-group teardown errors when the child is already gone.
91
+ }
92
+ }
93
+ state.devServers.delete(sessionId)
94
+ return true
95
+ }
96
+
97
+ export function clearDevServer(sessionId: string): void {
98
+ state.devServers.delete(sessionId)
99
+ }
@@ -1,4 +1,6 @@
1
- import { loadSchedules, loadAgents, loadTasks, upsertSchedule, upsertSchedules, upsertTask } from '@/lib/server/storage'
1
+ import { listAgents } from '@/lib/server/agents/agent-repository'
2
+ import { loadSchedules, upsertSchedule, upsertSchedules } from '@/lib/server/schedules/schedule-repository'
3
+ import { loadTasks, upsertTask } from '@/lib/server/tasks/task-repository'
2
4
  import { enqueueTask } from '@/lib/server/runtime/queue'
3
5
  import { CronExpressionParser } from 'cron-parser'
4
6
  import { pushMainLoopEventToMainSessions } from '@/lib/server/agents/main-agent-loop'
@@ -11,8 +13,11 @@ import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-sessi
11
13
  import { ensureMissionForTask, noteScheduleMissionTriggered } from '@/lib/server/missions/mission-service'
12
14
  import { hasActiveProtocolRunForSchedule, launchProtocolRunForSchedule } from '@/lib/server/protocols/protocol-service'
13
15
  import { hmrSingleton } from '@/lib/shared-utils'
16
+ import { log } from '@/lib/server/logger'
14
17
  import type { Schedule } from '@/types'
15
18
 
19
+ const TAG = 'scheduler'
20
+
16
21
  const TICK_INTERVAL = 60_000 // 60 seconds
17
22
  const schedulerState = hmrSingleton('__swarmclaw_scheduler_state__', () => ({
18
23
  intervalId: null as ReturnType<typeof setInterval> | null,
@@ -45,7 +50,7 @@ function shouldLaunchScheduleProtocol(schedule: Schedule): boolean {
45
50
 
46
51
  export function startScheduler() {
47
52
  if (schedulerState.intervalId) return
48
- console.log('[scheduler] Starting scheduler engine (60s tick)')
53
+ log.info(TAG, 'Starting scheduler engine (60s tick)')
49
54
 
50
55
  // Compute initial nextRunAt for cron schedules missing it
51
56
  computeNextRuns()
@@ -57,7 +62,7 @@ export function stopScheduler() {
57
62
  if (schedulerState.intervalId) {
58
63
  clearInterval(schedulerState.intervalId)
59
64
  schedulerState.intervalId = null
60
- console.log('[scheduler] Stopped scheduler engine')
65
+ log.info(TAG, 'Stopped scheduler engine')
61
66
  }
62
67
  }
63
68
 
@@ -75,7 +80,7 @@ function computeNextRuns() {
75
80
  schedule.nextRunAt = interval.next().getTime()
76
81
  changedEntries.push([schedule.id, schedule])
77
82
  } catch (err) {
78
- console.error(`[scheduler] Invalid cron for ${schedule.id}:`, err)
83
+ log.error(TAG, `Invalid cron for ${schedule.id}:`, err)
79
84
  schedule.status = 'failed'
80
85
  changedEntries.push([schedule.id, schedule])
81
86
  }
@@ -87,7 +92,7 @@ function computeNextRuns() {
87
92
  async function tick(now = Date.now()) {
88
93
  await processDueWatchJobs(now)
89
94
  const schedules = loadSchedules()
90
- const agents = loadAgents()
95
+ const agents = listAgents()
91
96
  const tasks = loadTasks()
92
97
  const inFlightScheduleKeys = new Set<string>(
93
98
  Object.values(tasks as Record<string, ScheduleTaskLike>)
@@ -133,7 +138,7 @@ async function tick(now = Date.now()) {
133
138
 
134
139
  const agent = agents[schedule.agentId]
135
140
  if (!agent) {
136
- console.error(`[scheduler] Agent ${schedule.agentId} not found for schedule ${schedule.id}`)
141
+ log.error(TAG, `Agent ${schedule.agentId} not found for schedule ${schedule.id}`)
137
142
  schedule.status = 'failed'
138
143
  upsertSchedule(schedule.id, schedule)
139
144
  pushMainLoopEventToMainSessions({
@@ -143,7 +148,7 @@ async function tick(now = Date.now()) {
143
148
  continue
144
149
  }
145
150
  if (isAgentDisabled(agent)) {
146
- console.warn(`[scheduler] Skipping schedule "${schedule.name}" (${schedule.id}) because agent ${schedule.agentId} is disabled`)
151
+ log.warn(TAG, `Skipping schedule "${schedule.name}" (${schedule.id}) because agent ${schedule.agentId} is disabled`)
147
152
  advanceSchedule(schedule)
148
153
  upsertSchedule(schedule.id, schedule)
149
154
  pushMainLoopEventToMainSessions({
@@ -153,7 +158,7 @@ async function tick(now = Date.now()) {
153
158
  continue
154
159
  }
155
160
 
156
- console.log(`[scheduler] Firing schedule "${schedule.name}" (${schedule.id})`)
161
+ log.info(TAG, `Firing schedule "${schedule.name}" (${schedule.id})`)
157
162
  schedule.lastRunAt = now
158
163
  schedule.runNumber = (schedule.runNumber || 0) + 1
159
164
  // Compute next run
@@ -0,0 +1,157 @@
1
+ import { isInternalHeartbeatRun } from '@/lib/server/runtime/heartbeat-source'
2
+
3
+ import {
4
+ abortSessionRuntime,
5
+ decrementNonHeartbeatWork,
6
+ emitRunMeta,
7
+ now,
8
+ reconcileSessionActivityLease,
9
+ state,
10
+ syncRunRecord,
11
+ } from './state'
12
+ import type { SessionRunQueueEntry } from './types'
13
+
14
+ export function cancelPendingForSession(sessionId: string, reason: string): number {
15
+ let cancelled = 0
16
+ for (const [key, queue] of state.queueByExecution.entries()) {
17
+ if (!queue.length) continue
18
+ const keep: SessionRunQueueEntry[] = []
19
+ for (const entry of queue) {
20
+ if (entry.run.sessionId !== sessionId) {
21
+ keep.push(entry)
22
+ continue
23
+ }
24
+ entry.run.status = 'cancelled'
25
+ entry.run.endedAt = now()
26
+ entry.run.error = reason
27
+ syncRunRecord(entry.run)
28
+ emitRunMeta(entry, 'cancelled', { reason })
29
+ entry.reject(new Error(reason))
30
+ decrementNonHeartbeatWork(entry)
31
+ cancelled += 1
32
+ }
33
+ if (keep.length > 0) state.queueByExecution.set(key, keep)
34
+ else state.queueByExecution.delete(key)
35
+ }
36
+ reconcileSessionActivityLease(sessionId)
37
+ return cancelled
38
+ }
39
+
40
+ function cancelQueuedEntries(
41
+ matcher: (entry: SessionRunQueueEntry) => boolean,
42
+ reason: string,
43
+ ): { cancelled: number; sessionIds: Set<string> } {
44
+ let cancelled = 0
45
+ const sessionIds = new Set<string>()
46
+ for (const [key, queue] of state.queueByExecution.entries()) {
47
+ if (!queue.length) continue
48
+ const keep: SessionRunQueueEntry[] = []
49
+ for (const entry of queue) {
50
+ if (!matcher(entry)) {
51
+ keep.push(entry)
52
+ continue
53
+ }
54
+ entry.run.status = 'cancelled'
55
+ entry.run.endedAt = now()
56
+ entry.run.error = reason
57
+ syncRunRecord(entry.run)
58
+ emitRunMeta(entry, 'cancelled', { reason })
59
+ entry.reject(new Error(reason))
60
+ decrementNonHeartbeatWork(entry)
61
+ sessionIds.add(entry.run.sessionId)
62
+ cancelled += 1
63
+ }
64
+ if (keep.length > 0) state.queueByExecution.set(key, keep)
65
+ else state.queueByExecution.delete(key)
66
+ }
67
+ for (const sessionId of sessionIds) reconcileSessionActivityLease(sessionId)
68
+ return { cancelled, sessionIds }
69
+ }
70
+
71
+ export function cancelAllHeartbeatRuns(reason = 'Heartbeat disabled globally'): { cancelledQueued: number; abortedRunning: number } {
72
+ let cancelledQueued = 0
73
+ let abortedRunning = 0
74
+
75
+ for (const [key, queue] of state.queueByExecution.entries()) {
76
+ if (!queue.length) continue
77
+ const keep: SessionRunQueueEntry[] = []
78
+ for (const entry of queue) {
79
+ const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
80
+ if (!isHeartbeat) {
81
+ keep.push(entry)
82
+ continue
83
+ }
84
+ entry.run.status = 'cancelled'
85
+ entry.run.endedAt = now()
86
+ entry.run.error = reason
87
+ syncRunRecord(entry.run)
88
+ emitRunMeta(entry, 'cancelled', { reason })
89
+ entry.reject(new Error(reason))
90
+ cancelledQueued += 1
91
+ }
92
+ if (keep.length > 0) state.queueByExecution.set(key, keep)
93
+ else state.queueByExecution.delete(key)
94
+ }
95
+
96
+ for (const entry of state.runningByExecution.values()) {
97
+ const isHeartbeat = isInternalHeartbeatRun(entry.run.internal, entry.run.source)
98
+ if (!isHeartbeat) continue
99
+ abortedRunning += 1
100
+ abortSessionRuntime(entry, reason)
101
+ }
102
+
103
+ return { cancelledQueued, abortedRunning }
104
+ }
105
+
106
+ export function cancelAllRuns(reason = 'Cancelled'): { cancelledQueued: number; abortedRunning: number } {
107
+ let cancelledQueued = 0
108
+ let abortedRunning = 0
109
+
110
+ for (const [key, queue] of state.queueByExecution.entries()) {
111
+ if (!queue.length) continue
112
+ for (const entry of queue) {
113
+ entry.run.status = 'cancelled'
114
+ entry.run.endedAt = now()
115
+ entry.run.error = reason
116
+ syncRunRecord(entry.run)
117
+ emitRunMeta(entry, 'cancelled', { reason })
118
+ entry.reject(new Error(reason))
119
+ cancelledQueued += 1
120
+ }
121
+ state.queueByExecution.delete(key)
122
+ }
123
+
124
+ for (const entry of state.runningByExecution.values()) {
125
+ abortedRunning += 1
126
+ abortSessionRuntime(entry, reason)
127
+ }
128
+ state.runningByExecution.clear()
129
+ state.nonHeartbeatWorkCount.clear()
130
+
131
+ return { cancelledQueued, abortedRunning }
132
+ }
133
+
134
+ export function cancelQueuedRunById(runId: string, reason = 'Removed from queue'): boolean {
135
+ const result = cancelQueuedEntries((entry) => entry.run.id === runId, reason)
136
+ return result.cancelled > 0
137
+ }
138
+
139
+ export function cancelQueuedRunsForSession(sessionId: string, reason = 'Cleared queued messages'): number {
140
+ const result = cancelQueuedEntries((entry) => entry.run.sessionId === sessionId, reason)
141
+ return result.cancelled
142
+ }
143
+
144
+ export function cancelSessionRuns(sessionId: string, reason = 'Cancelled'): { cancelledQueued: number; cancelledRunning: boolean } {
145
+ const running = Array.from(state.runningByExecution.values())
146
+ .find((entry) => entry.run.sessionId === sessionId)
147
+ let cancelledRunning = false
148
+ if (running) {
149
+ cancelledRunning = true
150
+ abortSessionRuntime(running, reason)
151
+ state.runningByExecution.delete(running.executionKey)
152
+ decrementNonHeartbeatWork(running)
153
+ }
154
+ const cancelledQueued = cancelPendingForSession(sessionId, reason)
155
+ reconcileSessionActivityLease(sessionId)
156
+ return { cancelledQueued, cancelledRunning }
157
+ }