@swarmclawai/swarmclaw 0.7.7 → 0.8.0

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 (281) hide show
  1. package/README.md +12 -14
  2. package/next.config.ts +13 -2
  3. package/package.json +4 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +9 -0
  5. package/src/app/api/agents/route.ts +4 -0
  6. package/src/app/api/agents/thread-route.test.ts +133 -0
  7. package/src/app/api/approvals/route.test.ts +148 -0
  8. package/src/app/api/canvas/[sessionId]/route.ts +3 -1
  9. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
  10. package/src/app/api/chats/[id]/devserver/route.ts +48 -7
  11. package/src/app/api/chats/[id]/messages/route.ts +42 -18
  12. package/src/app/api/chats/[id]/route.ts +1 -1
  13. package/src/app/api/chats/[id]/stop/route.ts +5 -4
  14. package/src/app/api/chats/route.ts +23 -2
  15. package/src/app/api/clawhub/install/route.ts +28 -8
  16. package/src/app/api/connectors/[id]/route.ts +46 -3
  17. package/src/app/api/connectors/route.ts +12 -8
  18. package/src/app/api/external-agents/route.test.ts +165 -0
  19. package/src/app/api/gateways/[id]/health/route.ts +27 -12
  20. package/src/app/api/gateways/[id]/route.ts +2 -0
  21. package/src/app/api/gateways/health-route.test.ts +135 -0
  22. package/src/app/api/gateways/route.ts +2 -0
  23. package/src/app/api/mcp-servers/route.test.ts +130 -0
  24. package/src/app/api/openclaw/deploy/route.ts +38 -5
  25. package/src/app/api/plugins/install/route.ts +46 -6
  26. package/src/app/api/plugins/marketplace/route.ts +48 -15
  27. package/src/app/api/preview-server/route.ts +26 -11
  28. package/src/app/api/projects/[id]/route.ts +6 -2
  29. package/src/app/api/projects/route.ts +4 -3
  30. package/src/app/api/schedules/[id]/run/route.ts +4 -0
  31. package/src/app/api/schedules/route.test.ts +86 -0
  32. package/src/app/api/schedules/route.ts +6 -1
  33. package/src/app/api/secrets/[id]/route.ts +1 -0
  34. package/src/app/api/secrets/route.ts +2 -1
  35. package/src/app/api/settings/route.ts +2 -0
  36. package/src/app/api/setup/check-provider/route.test.ts +19 -0
  37. package/src/app/api/setup/check-provider/route.ts +40 -10
  38. package/src/app/api/skills/[id]/route.ts +12 -0
  39. package/src/app/api/skills/import/route.ts +14 -12
  40. package/src/app/api/skills/route.ts +13 -1
  41. package/src/app/api/tasks/[id]/route.ts +10 -1
  42. package/src/app/api/tasks/import/github/route.test.ts +65 -0
  43. package/src/app/api/tasks/import/github/route.ts +337 -0
  44. package/src/app/api/wallets/[id]/approve/route.ts +17 -3
  45. package/src/app/api/wallets/[id]/route.ts +79 -33
  46. package/src/app/api/wallets/[id]/send/route.ts +19 -33
  47. package/src/app/api/wallets/route.ts +78 -61
  48. package/src/app/api/webhooks/[id]/route.ts +33 -6
  49. package/src/app/api/webhooks/route.test.ts +272 -0
  50. package/src/cli/index.js +1 -0
  51. package/src/cli/spec.js +1 -0
  52. package/src/components/agents/agent-card.tsx +9 -2
  53. package/src/components/agents/agent-chat-list.tsx +18 -2
  54. package/src/components/agents/agent-list.tsx +1 -0
  55. package/src/components/agents/agent-sheet.tsx +257 -38
  56. package/src/components/agents/inspector-panel.tsx +41 -0
  57. package/src/components/canvas/canvas-panel.tsx +236 -65
  58. package/src/components/chat/chat-area.tsx +36 -19
  59. package/src/components/chat/chat-card.tsx +36 -13
  60. package/src/components/chat/chat-header.tsx +48 -16
  61. package/src/components/chat/chat-list.tsx +28 -4
  62. package/src/components/chat/checkpoint-timeline.tsx +50 -34
  63. package/src/components/chat/delegation-banner.test.ts +14 -1
  64. package/src/components/chat/delegation-banner.tsx +1 -1
  65. package/src/components/chat/message-bubble.tsx +208 -145
  66. package/src/components/chat/message-list.tsx +48 -19
  67. package/src/components/chatrooms/chatroom-message.tsx +2 -2
  68. package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
  69. package/src/components/connectors/connector-health.tsx +1 -1
  70. package/src/components/connectors/connector-list.tsx +7 -2
  71. package/src/components/connectors/connector-sheet.tsx +337 -148
  72. package/src/components/gateways/gateway-sheet.tsx +2 -2
  73. package/src/components/layout/app-layout.tsx +40 -23
  74. package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
  75. package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
  76. package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
  77. package/src/components/plugins/plugin-list.tsx +45 -9
  78. package/src/components/plugins/plugin-sheet.tsx +55 -7
  79. package/src/components/projects/project-detail.tsx +217 -0
  80. package/src/components/projects/project-sheet.tsx +176 -4
  81. package/src/components/providers/provider-list.tsx +2 -1
  82. package/src/components/providers/provider-sheet.tsx +21 -2
  83. package/src/components/schedules/schedule-card.tsx +25 -1
  84. package/src/components/schedules/schedule-sheet.tsx +44 -2
  85. package/src/components/secrets/secret-sheet.tsx +21 -2
  86. package/src/components/shared/agent-switch-dialog.tsx +12 -1
  87. package/src/components/shared/bottom-sheet.tsx +13 -3
  88. package/src/components/shared/command-palette.tsx +8 -1
  89. package/src/components/shared/confirm-dialog.tsx +19 -4
  90. package/src/components/shared/connector-platform-icon.test.ts +28 -0
  91. package/src/components/shared/connector-platform-icon.tsx +39 -6
  92. package/src/components/shared/settings/plugin-manager.tsx +29 -6
  93. package/src/components/shared/settings/section-capability-policy.tsx +45 -3
  94. package/src/components/shared/settings/section-voice.tsx +11 -3
  95. package/src/components/skills/skill-list.tsx +25 -0
  96. package/src/components/skills/skill-sheet.tsx +84 -12
  97. package/src/components/tasks/approvals-panel.tsx +289 -34
  98. package/src/components/tasks/task-board.tsx +410 -25
  99. package/src/components/tasks/task-card.tsx +66 -8
  100. package/src/components/tasks/task-sheet.tsx +16 -4
  101. package/src/components/ui/dialog.tsx +2 -2
  102. package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
  103. package/src/components/wallets/wallet-panel.tsx +435 -90
  104. package/src/components/wallets/wallet-section.tsx +198 -48
  105. package/src/components/webhooks/webhook-sheet.tsx +22 -2
  106. package/src/lib/approval-display.ts +20 -0
  107. package/src/lib/canvas-content.ts +198 -0
  108. package/src/lib/chat-artifact-summary.ts +165 -0
  109. package/src/lib/chat-display.test.ts +91 -0
  110. package/src/lib/chat-display.ts +58 -0
  111. package/src/lib/chat-streaming-state.test.ts +47 -1
  112. package/src/lib/chat-streaming-state.ts +42 -0
  113. package/src/lib/ollama-model.ts +10 -0
  114. package/src/lib/openclaw-endpoint.test.ts +8 -0
  115. package/src/lib/openclaw-endpoint.ts +6 -1
  116. package/src/lib/plugin-install-cors.ts +46 -0
  117. package/src/lib/plugin-sources.test.ts +43 -0
  118. package/src/lib/plugin-sources.ts +77 -0
  119. package/src/lib/providers/ollama.ts +16 -6
  120. package/src/lib/providers/openclaw.test.ts +54 -0
  121. package/src/lib/providers/openclaw.ts +127 -11
  122. package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
  123. package/src/lib/schedule-dedupe.test.ts +66 -1
  124. package/src/lib/schedule-dedupe.ts +169 -12
  125. package/src/lib/schedule-origin.test.ts +20 -0
  126. package/src/lib/schedule-origin.ts +15 -0
  127. package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
  128. package/src/lib/server/agent-availability.ts +16 -0
  129. package/src/lib/server/agent-runtime-config.ts +12 -4
  130. package/src/lib/server/agent-thread-session.test.ts +51 -0
  131. package/src/lib/server/agent-thread-session.ts +7 -0
  132. package/src/lib/server/approval-match.ts +205 -0
  133. package/src/lib/server/approvals-auto-approve.test.ts +538 -1
  134. package/src/lib/server/approvals.ts +214 -1
  135. package/src/lib/server/assistant-control.test.ts +29 -0
  136. package/src/lib/server/assistant-control.ts +23 -0
  137. package/src/lib/server/build-llm.test.ts +79 -0
  138. package/src/lib/server/build-llm.ts +14 -4
  139. package/src/lib/server/canvas-content.test.ts +32 -0
  140. package/src/lib/server/canvas-content.ts +6 -0
  141. package/src/lib/server/capability-router.test.ts +33 -0
  142. package/src/lib/server/capability-router.ts +80 -19
  143. package/src/lib/server/chat-execution-advanced.test.ts +651 -0
  144. package/src/lib/server/chat-execution-disabled.test.ts +94 -0
  145. package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
  146. package/src/lib/server/chat-execution.ts +378 -73
  147. package/src/lib/server/clawhub-client.test.ts +14 -8
  148. package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
  149. package/src/lib/server/connectors/manager.test.ts +1147 -0
  150. package/src/lib/server/connectors/manager.ts +461 -137
  151. package/src/lib/server/connectors/pairing.ts +26 -5
  152. package/src/lib/server/connectors/types.ts +2 -0
  153. package/src/lib/server/connectors/whatsapp.test.ts +134 -0
  154. package/src/lib/server/connectors/whatsapp.ts +271 -47
  155. package/src/lib/server/context-manager.ts +6 -1
  156. package/src/lib/server/daemon-state.ts +84 -47
  157. package/src/lib/server/data-dir.test.ts +37 -0
  158. package/src/lib/server/data-dir.ts +20 -1
  159. package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
  160. package/src/lib/server/devserver-launch.test.ts +60 -0
  161. package/src/lib/server/devserver-launch.ts +85 -0
  162. package/src/lib/server/elevenlabs.test.ts +247 -1
  163. package/src/lib/server/elevenlabs.ts +147 -43
  164. package/src/lib/server/ethereum.ts +590 -0
  165. package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
  166. package/src/lib/server/eval/agent-regression.test.ts +18 -1
  167. package/src/lib/server/eval/agent-regression.ts +383 -11
  168. package/src/lib/server/evm-swap.ts +475 -0
  169. package/src/lib/server/execution-log.ts +1 -0
  170. package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
  171. package/src/lib/server/heartbeat-service.ts +20 -11
  172. package/src/lib/server/heartbeat-wake.test.ts +112 -0
  173. package/src/lib/server/heartbeat-wake.ts +338 -57
  174. package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
  175. package/src/lib/server/main-agent-loop.test.ts +260 -0
  176. package/src/lib/server/main-agent-loop.ts +559 -14
  177. package/src/lib/server/mcp-client.test.ts +16 -0
  178. package/src/lib/server/mcp-client.ts +25 -0
  179. package/src/lib/server/memory-integration.test.ts +719 -0
  180. package/src/lib/server/memory-policy.test.ts +43 -0
  181. package/src/lib/server/memory-policy.ts +132 -0
  182. package/src/lib/server/memory-tiers.test.ts +60 -0
  183. package/src/lib/server/memory-tiers.ts +16 -0
  184. package/src/lib/server/ollama-runtime.ts +58 -0
  185. package/src/lib/server/openclaw-deploy.test.ts +109 -1
  186. package/src/lib/server/openclaw-deploy.ts +557 -81
  187. package/src/lib/server/openclaw-gateway.test.ts +131 -0
  188. package/src/lib/server/openclaw-gateway.ts +10 -4
  189. package/src/lib/server/openclaw-health.test.ts +35 -0
  190. package/src/lib/server/openclaw-health.ts +215 -47
  191. package/src/lib/server/orchestrator-lg.ts +3 -2
  192. package/src/lib/server/orchestrator.ts +2 -0
  193. package/src/lib/server/plugins-advanced.test.ts +351 -0
  194. package/src/lib/server/plugins.ts +211 -6
  195. package/src/lib/server/project-context.ts +162 -0
  196. package/src/lib/server/project-utils.ts +150 -0
  197. package/src/lib/server/queue-advanced.test.ts +528 -0
  198. package/src/lib/server/queue-followups.test.ts +409 -2
  199. package/src/lib/server/queue-reconcile.test.ts +128 -0
  200. package/src/lib/server/queue.ts +527 -68
  201. package/src/lib/server/scheduler.ts +29 -1
  202. package/src/lib/server/session-note.test.ts +36 -0
  203. package/src/lib/server/session-note.ts +42 -0
  204. package/src/lib/server/session-run-manager.ts +83 -4
  205. package/src/lib/server/session-tools/canvas.ts +14 -12
  206. package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
  207. package/src/lib/server/session-tools/connector.test.ts +138 -0
  208. package/src/lib/server/session-tools/connector.ts +366 -54
  209. package/src/lib/server/session-tools/context.ts +17 -3
  210. package/src/lib/server/session-tools/crud.ts +484 -84
  211. package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
  212. package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
  213. package/src/lib/server/session-tools/delegate.ts +102 -10
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
  215. package/src/lib/server/session-tools/discovery.ts +80 -12
  216. package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
  217. package/src/lib/server/session-tools/file.ts +43 -4
  218. package/src/lib/server/session-tools/human-loop.ts +35 -5
  219. package/src/lib/server/session-tools/index.ts +44 -9
  220. package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
  221. package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
  222. package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
  223. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
  224. package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
  225. package/src/lib/server/session-tools/memory.test.ts +93 -0
  226. package/src/lib/server/session-tools/memory.ts +554 -75
  227. package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
  228. package/src/lib/server/session-tools/platform-access.test.ts +58 -0
  229. package/src/lib/server/session-tools/platform.ts +60 -19
  230. package/src/lib/server/session-tools/plugin-creator.ts +57 -1
  231. package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
  232. package/src/lib/server/session-tools/schedule.ts +6 -1
  233. package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
  234. package/src/lib/server/session-tools/shell.ts +22 -3
  235. package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
  236. package/src/lib/server/session-tools/wallet.ts +1374 -139
  237. package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
  238. package/src/lib/server/session-tools/web.ts +621 -70
  239. package/src/lib/server/skill-discovery.ts +128 -0
  240. package/src/lib/server/skill-eligibility.test.ts +84 -0
  241. package/src/lib/server/skill-eligibility.ts +95 -0
  242. package/src/lib/server/skill-prompt-budget.test.ts +102 -0
  243. package/src/lib/server/skill-prompt-budget.ts +125 -0
  244. package/src/lib/server/skills-normalize.test.ts +54 -0
  245. package/src/lib/server/skills-normalize.ts +372 -26
  246. package/src/lib/server/solana.ts +214 -29
  247. package/src/lib/server/storage.ts +65 -36
  248. package/src/lib/server/stream-agent-chat.test.ts +437 -2
  249. package/src/lib/server/stream-agent-chat.ts +957 -79
  250. package/src/lib/server/system-events.ts +1 -1
  251. package/src/lib/server/tool-aliases.ts +2 -0
  252. package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
  253. package/src/lib/server/tool-capability-policy.test.ts +24 -0
  254. package/src/lib/server/tool-capability-policy.ts +29 -1
  255. package/src/lib/server/tool-loop-detection.test.ts +105 -0
  256. package/src/lib/server/tool-loop-detection.ts +260 -0
  257. package/src/lib/server/tool-planning.test.ts +44 -0
  258. package/src/lib/server/tool-planning.ts +271 -0
  259. package/src/lib/server/wallet-execution.test.ts +198 -0
  260. package/src/lib/server/wallet-portfolio.test.ts +98 -0
  261. package/src/lib/server/wallet-portfolio.ts +724 -0
  262. package/src/lib/server/wallet-service.test.ts +57 -0
  263. package/src/lib/server/wallet-service.ts +213 -0
  264. package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
  265. package/src/lib/server/watch-jobs.ts +17 -2
  266. package/src/lib/server/workspace-context.ts +111 -0
  267. package/src/lib/skill-save-payload.test.ts +39 -0
  268. package/src/lib/skill-save-payload.ts +37 -0
  269. package/src/lib/tasks.ts +28 -0
  270. package/src/lib/tool-definitions.ts +2 -1
  271. package/src/lib/tool-event-summary.test.ts +30 -0
  272. package/src/lib/tool-event-summary.ts +37 -0
  273. package/src/lib/validation/schemas.ts +1 -0
  274. package/src/lib/wallet-transactions.test.ts +75 -0
  275. package/src/lib/wallet-transactions.ts +43 -0
  276. package/src/lib/wallet.test.ts +17 -0
  277. package/src/lib/wallet.ts +183 -0
  278. package/src/proxy.test.ts +31 -0
  279. package/src/proxy.ts +34 -2
  280. package/src/stores/use-chat-store.ts +15 -1
  281. package/src/types/index.ts +249 -14
@@ -11,6 +11,12 @@ import {
11
11
  startConnector,
12
12
  getConnectorStatus,
13
13
  checkConnectorHealth,
14
+ createConnectorReconnectState,
15
+ advanceConnectorReconnectState,
16
+ clearReconnectState,
17
+ getAllReconnectStates,
18
+ getReconnectState,
19
+ setReconnectState,
14
20
  } from './connectors/manager'
15
21
  import { startHeartbeatService, stopHeartbeatService, getHeartbeatServiceStatus } from './heartbeat-service'
16
22
  import { hasOpenClawAgents, ensureGatewayConnected, disconnectGateway, getGateway } from './openclaw-gateway'
@@ -34,6 +40,7 @@ const QUEUE_CHECK_INTERVAL = 30_000 // 30 seconds
34
40
  const BROWSER_SWEEP_INTERVAL = 60_000 // 60 seconds
35
41
  const BROWSER_MAX_AGE = 10 * 60 * 1000 // 10 minutes idle = orphaned
36
42
  const HEALTH_CHECK_INTERVAL = 120_000 // 2 minutes
43
+ const CONNECTOR_HEALTH_CHECK_INTERVAL = 5_000 // 5 seconds
37
44
  const MEMORY_CONSOLIDATION_INTERVAL = 6 * 3600_000 // 6 hours
38
45
  const MEMORY_CONSOLIDATION_INITIAL_DELAY = 60_000 // 1 minute after daemon start
39
46
  const STALE_MULTIPLIER = 4 // session is stale after N × heartbeat interval
@@ -87,12 +94,12 @@ const ds: {
87
94
  queueIntervalId: ReturnType<typeof setInterval> | null
88
95
  browserSweepId: ReturnType<typeof setInterval> | null
89
96
  healthIntervalId: ReturnType<typeof setInterval> | null
97
+ connectorHealthIntervalId: ReturnType<typeof setInterval> | null
90
98
  memoryConsolidationTimeoutId: ReturnType<typeof setTimeout> | null
91
99
  memoryConsolidationIntervalId: ReturnType<typeof setInterval> | null
92
100
  evalSchedulerIntervalId: ReturnType<typeof setInterval> | null
93
101
  /** Session IDs we've already alerted as stale (alert-once semantics). */
94
102
  staleSessionIds: Set<string>
95
- connectorRestartState: Map<string, { lastAttemptAt: number; failCount: number; wakeAttempts: number }>
96
103
  /** OpenClaw gateway agent IDs currently considered down. */
97
104
  openclawDownAgentIds: Set<string>
98
105
  /** Per-agent auto-repair state for OpenClaw gateways. */
@@ -107,11 +114,11 @@ const ds: {
107
114
  queueIntervalId: null,
108
115
  browserSweepId: null,
109
116
  healthIntervalId: null,
117
+ connectorHealthIntervalId: null,
110
118
  memoryConsolidationTimeoutId: null,
111
119
  memoryConsolidationIntervalId: null,
112
120
  evalSchedulerIntervalId: null,
113
121
  staleSessionIds: new Set<string>(),
114
- connectorRestartState: new Map<string, { lastAttemptAt: number; failCount: number; wakeAttempts: number }>(),
115
122
  openclawDownAgentIds: new Set<string>(),
116
123
  openclawRepairState: new Map<string, { attempts: number; lastAttemptAt: number; cooldownUntil: number }>(),
117
124
  lastIntegrityCheckAt: null,
@@ -123,7 +130,6 @@ const ds: {
123
130
 
124
131
  // Backfill fields for hot-reloaded daemon state objects from older code versions.
125
132
  if (!ds.staleSessionIds) ds.staleSessionIds = new Set<string>()
126
- if (!ds.connectorRestartState) ds.connectorRestartState = new Map<string, { lastAttemptAt: number; failCount: number; wakeAttempts: number }>()
127
133
  if (!ds.openclawDownAgentIds) ds.openclawDownAgentIds = new Set<string>()
128
134
  if (!ds.openclawRepairState) ds.openclawRepairState = new Map<string, { attempts: number; lastAttemptAt: number; cooldownUntil: number }>()
129
135
  if (ds.lastIntegrityCheckAt === undefined) ds.lastIntegrityCheckAt = null
@@ -132,6 +138,7 @@ if (ds.lastIntegrityDriftCount === undefined) ds.lastIntegrityDriftCount = 0
132
138
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
139
  if ((ds as any).issueLastAlertAt) delete (ds as any).issueLastAlertAt
134
140
  if (ds.healthIntervalId === undefined) ds.healthIntervalId = null
141
+ if (ds.connectorHealthIntervalId === undefined) ds.connectorHealthIntervalId = null
135
142
  if (ds.manualStopRequested === undefined) ds.manualStopRequested = false
136
143
  if (ds.memoryConsolidationTimeoutId === undefined) ds.memoryConsolidationTimeoutId = null
137
144
  if (ds.memoryConsolidationIntervalId === undefined) ds.memoryConsolidationIntervalId = null
@@ -156,6 +163,7 @@ export function startDaemon(options?: { source?: string; manualStart?: boolean }
156
163
  startQueueProcessor()
157
164
  startBrowserSweep()
158
165
  startHealthMonitor()
166
+ startConnectorHealthMonitor()
159
167
  startHeartbeatService()
160
168
  startMemoryConsolidation()
161
169
  startEvalScheduler()
@@ -173,6 +181,7 @@ export function startDaemon(options?: { source?: string; manualStart?: boolean }
173
181
  startQueueProcessor()
174
182
  startBrowserSweep()
175
183
  startHealthMonitor()
184
+ startConnectorHealthMonitor()
176
185
  startHeartbeatService()
177
186
  startMemoryConsolidation()
178
187
  startEvalScheduler()
@@ -201,6 +210,7 @@ export function stopDaemon(options?: { source?: string; manualStop?: boolean })
201
210
  stopQueueProcessor()
202
211
  stopBrowserSweep()
203
212
  stopHealthMonitor()
213
+ stopConnectorHealthMonitor()
204
214
  stopHeartbeatService()
205
215
  stopMemoryConsolidation()
206
216
  stopEvalScheduler()
@@ -278,7 +288,8 @@ async function sendHealthAlert(text: string) {
278
288
  }
279
289
 
280
290
  async function runConnectorHealthChecks(now: number) {
281
- // First, check isAlive() on running instances and attempt reconnection for dead ones
291
+ // First, collapse dead runtime instances into persisted error state so the
292
+ // daemon can own the restart cadence and backoff policy.
282
293
  try {
283
294
  await checkConnectorHealth()
284
295
  } catch (err: unknown) {
@@ -289,48 +300,30 @@ async function runConnectorHealthChecks(now: number) {
289
300
  for (const connector of Object.values(connectors) as Record<string, unknown>[]) {
290
301
  if (!connector?.id || typeof connector.id !== 'string') continue
291
302
  if (connector.isEnabled !== true) {
292
- ds.connectorRestartState.delete(connector.id)
303
+ clearReconnectState(connector.id)
293
304
  continue
294
305
  }
295
306
 
296
307
  const runtimeStatus = getConnectorStatus(connector.id)
297
308
  if (runtimeStatus === 'running') {
298
- ds.connectorRestartState.delete(connector.id)
309
+ clearReconnectState(connector.id)
299
310
  continue
300
311
  }
301
312
 
302
- const current = ds.connectorRestartState.get(connector.id) || { lastAttemptAt: 0, failCount: 0, wakeAttempts: 0 }
303
- // Backfill wakeAttempts for state objects created before this field existed
304
- if (typeof current.wakeAttempts !== 'number') current.wakeAttempts = 0
305
-
306
- // Cap wake attempts — stop retrying after MAX_WAKE_ATTEMPTS consecutive failures
307
- if (current.wakeAttempts >= MAX_WAKE_ATTEMPTS) {
308
- console.warn(`[health] Connector "${connector.name}" exceeded ${MAX_WAKE_ATTEMPTS} wake attempts — giving up`)
309
- connector.status = 'error'
310
- connector.lastError = `Auto-restart gave up after ${MAX_WAKE_ATTEMPTS} consecutive failures`
311
- connector.updatedAt = Date.now()
312
- connectors[connector.id] = connector
313
- saveConnectors(connectors)
314
- ds.connectorRestartState.delete(connector.id)
315
- createNotification({
316
- type: 'error',
317
- title: `Connector "${connector.name}" failed`,
318
- message: `Auto-restart gave up after ${MAX_WAKE_ATTEMPTS} consecutive failures.`,
319
- dedupKey: `connector-gave-up:${connector.id}`,
320
- entityType: 'connector',
321
- entityId: connector.id,
322
- })
313
+ const current = getReconnectState(connector.id)
314
+ ?? createConnectorReconnectState(
315
+ { error: typeof connector.lastError === 'string' ? connector.lastError : '' },
316
+ { initialBackoffMs: CONNECTOR_RESTART_BASE_MS },
317
+ )
318
+
319
+ if (current.exhausted) {
323
320
  continue
324
321
  }
325
322
 
326
- const backoffMs = Math.min(
327
- CONNECTOR_RESTART_MAX_MS,
328
- CONNECTOR_RESTART_BASE_MS * (2 ** Math.min(6, current.failCount)),
329
- )
330
- if ((now - current.lastAttemptAt) < backoffMs) continue
323
+ if (current.nextRetryAt > now) continue
331
324
 
332
325
  // Notify on first detection of a down connector
333
- if (current.wakeAttempts === 0) {
326
+ if (current.attempts === 0) {
334
327
  createNotification({
335
328
  type: 'warning',
336
329
  title: `Connector "${connector.name}" is down`,
@@ -341,24 +334,43 @@ async function runConnectorHealthChecks(now: number) {
341
334
  })
342
335
  }
343
336
 
344
- current.lastAttemptAt = now
345
- ds.connectorRestartState.set(connector.id, current)
346
337
  try {
347
338
  await startConnector(connector.id)
348
- ds.connectorRestartState.delete(connector.id)
339
+ clearReconnectState(connector.id)
349
340
  await sendHealthAlert(`Connector "${connector.name}" (${connector.platform}) was down and has been auto-restarted.`)
350
341
  } catch (err: unknown) {
351
- current.failCount += 1
352
- current.wakeAttempts += 1
353
- ds.connectorRestartState.set(connector.id, current)
354
342
  const message = err instanceof Error ? err.message : String(err)
355
- console.warn(`[health] Connector auto-restart failed for ${connector.name} (attempt ${current.wakeAttempts}/${MAX_WAKE_ATTEMPTS}): ${message}`)
343
+ const next = advanceConnectorReconnectState(current, message, now, {
344
+ initialBackoffMs: CONNECTOR_RESTART_BASE_MS,
345
+ maxBackoffMs: CONNECTOR_RESTART_MAX_MS,
346
+ maxAttempts: MAX_WAKE_ATTEMPTS,
347
+ })
348
+ setReconnectState(connector.id, next)
349
+ if (next.exhausted) {
350
+ console.warn(`[health] Connector "${connector.name}" exceeded ${MAX_WAKE_ATTEMPTS} auto-restart attempts — giving up until the server restarts or the user retries manually`)
351
+ connector.status = 'error'
352
+ connector.lastError = `Auto-restart gave up after ${MAX_WAKE_ATTEMPTS} attempts: ${message}`
353
+ connector.updatedAt = Date.now()
354
+ connectors[connector.id] = connector
355
+ saveConnectors(connectors)
356
+ notify('connectors')
357
+ createNotification({
358
+ type: 'error',
359
+ title: `Connector "${connector.name}" failed`,
360
+ message: `Auto-restart gave up after ${MAX_WAKE_ATTEMPTS} attempts.`,
361
+ dedupKey: `connector-gave-up:${connector.id}`,
362
+ entityType: 'connector',
363
+ entityId: connector.id,
364
+ })
365
+ } else {
366
+ console.warn(`[health] Connector auto-restart failed for ${connector.name} (attempt ${next.attempts}/${MAX_WAKE_ATTEMPTS}): ${message}`)
367
+ }
356
368
  }
357
369
  }
358
370
 
359
371
  // Purge restart state for connectors that no longer exist in storage
360
- for (const id of ds.connectorRestartState.keys()) {
361
- if (!connectors[id]) ds.connectorRestartState.delete(id)
372
+ for (const id of Object.keys(getAllReconnectStates())) {
373
+ if (!connectors[id] || connectors[id]?.isEnabled !== true) clearReconnectState(id)
362
374
  }
363
375
  }
364
376
 
@@ -429,7 +441,7 @@ async function processWebhookRetries() {
429
441
  agentId: agent.id,
430
442
  parentSessionId: null,
431
443
  plugins: agent.plugins || agent.tools || [],
432
- heartbeatEnabled: (agent.heartbeatEnabled as boolean | undefined) ?? true,
444
+ heartbeatEnabled: (agent.heartbeatEnabled as boolean | undefined) ?? false,
433
445
  heartbeatIntervalSec: (agent.heartbeatIntervalSec as number | null | undefined) ?? null,
434
446
  }
435
447
  sessions[session.id as string] = session
@@ -776,8 +788,6 @@ async function runHealthChecks() {
776
788
 
777
789
  if (sessionsDirty) saveSessions(sessions)
778
790
 
779
- await runConnectorHealthChecks(now)
780
-
781
791
  // Provider reachability checks
782
792
  try {
783
793
  await runProviderHealthChecks()
@@ -851,6 +861,26 @@ function stopHealthMonitor() {
851
861
  }
852
862
  }
853
863
 
864
+ function startConnectorHealthMonitor() {
865
+ if (ds.connectorHealthIntervalId) return
866
+
867
+ const tick = () => {
868
+ runConnectorHealthChecks(Date.now()).catch((err) => {
869
+ console.error('[daemon] Connector health tick failed:', err instanceof Error ? err.message : String(err))
870
+ })
871
+ }
872
+
873
+ tick()
874
+ ds.connectorHealthIntervalId = setInterval(tick, CONNECTOR_HEALTH_CHECK_INTERVAL)
875
+ }
876
+
877
+ function stopConnectorHealthMonitor() {
878
+ if (ds.connectorHealthIntervalId) {
879
+ clearInterval(ds.connectorHealthIntervalId)
880
+ ds.connectorHealthIntervalId = null
881
+ }
882
+ }
883
+
854
884
  function runConsolidationTick() {
855
885
  import('./memory-consolidation').then(({ runDailyConsolidation }) =>
856
886
  runDailyConsolidation().then((stats) => {
@@ -951,12 +981,16 @@ function stopEvalScheduler() {
951
981
  }
952
982
 
953
983
  export async function runDaemonHealthCheckNow() {
954
- await runHealthChecks()
984
+ await Promise.all([
985
+ runHealthChecks(),
986
+ runConnectorHealthChecks(Date.now()),
987
+ ])
955
988
  }
956
989
 
957
990
  export function getDaemonStatus() {
958
991
  const queue = loadQueue()
959
992
  const schedules = loadSchedules()
993
+ const reconnectStates = Object.values(getAllReconnectStates())
960
994
 
961
995
  // Find next scheduled task
962
996
  let nextScheduled: number | null = null
@@ -985,9 +1019,12 @@ export function getDaemonStatus() {
985
1019
  heartbeat: getHeartbeatServiceStatus(),
986
1020
  health: {
987
1021
  monitorActive: !!ds.healthIntervalId,
1022
+ connectorMonitorActive: !!ds.connectorHealthIntervalId,
988
1023
  staleSessions: ds.staleSessionIds.size,
989
- connectorsInBackoff: ds.connectorRestartState.size,
1024
+ connectorsInBackoff: reconnectStates.filter((state) => !state.exhausted).length,
1025
+ connectorsExhausted: reconnectStates.filter((state) => state.exhausted).length,
990
1026
  checkIntervalSec: Math.trunc(HEALTH_CHECK_INTERVAL / 1000),
1027
+ connectorCheckIntervalSec: Math.trunc(CONNECTOR_HEALTH_CHECK_INTERVAL / 1000),
991
1028
  integrity: {
992
1029
  enabled: loadSettings().integrityMonitorEnabled !== false,
993
1030
  lastCheckedAt: ds.lastIntegrityCheckAt,
@@ -53,4 +53,41 @@ describe('data-dir resolution', () => {
53
53
  fs.rmSync(tempDir, { recursive: true, force: true })
54
54
  }
55
55
  })
56
+
57
+ it('uses isolated temp dirs during build bootstrap when DATA_DIR is unset', () => {
58
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-data-dir-build-'))
59
+ const fakeHome = path.join(tempDir, 'home')
60
+
61
+ try {
62
+ const env = { ...process.env, HOME: fakeHome, npm_lifecycle_event: 'build:ci' }
63
+ delete env.DATA_DIR
64
+ delete env.WORKSPACE_DIR
65
+ delete env.BROWSER_PROFILES_DIR
66
+
67
+ const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', `
68
+ const modNs = await import('./src/lib/server/data-dir.ts')
69
+ const mod = modNs.default || modNs['module.exports'] || modNs
70
+ console.log(JSON.stringify({
71
+ isBuildBootstrap: mod.IS_BUILD_BOOTSTRAP,
72
+ dataDir: mod.DATA_DIR,
73
+ workspaceDir: mod.WORKSPACE_DIR,
74
+ browserProfilesDir: mod.BROWSER_PROFILES_DIR,
75
+ }))
76
+ `], {
77
+ cwd: repoRoot,
78
+ env,
79
+ encoding: 'utf-8',
80
+ })
81
+
82
+ assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
83
+ const payload = extractLastJson(result.stdout || '')
84
+ const expectedDataDir = path.join(os.tmpdir(), 'swarmclaw-build-data')
85
+ assert.equal(payload.isBuildBootstrap, true)
86
+ assert.equal(payload.dataDir, expectedDataDir)
87
+ assert.equal(payload.workspaceDir, path.join(expectedDataDir, 'workspace'))
88
+ assert.equal(payload.browserProfilesDir, path.join(expectedDataDir, 'browser-profiles'))
89
+ } finally {
90
+ fs.rmSync(tempDir, { recursive: true, force: true })
91
+ }
92
+ })
56
93
  })
@@ -2,7 +2,24 @@ import path from 'path'
2
2
  import os from 'os'
3
3
  import fs from 'fs'
4
4
 
5
- export const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), 'data')
5
+ function isBuildBootstrapEnv(env: NodeJS.ProcessEnv = process.env, argv: string[] = process.argv): boolean {
6
+ if (env.SWARMCLAW_BUILD_MODE === '1') return true
7
+ if (env.NEXT_PHASE === 'phase-production-build') return true
8
+ const lifecycle = env.npm_lifecycle_event?.trim().toLowerCase()
9
+ if (lifecycle === 'build' || lifecycle === 'build:ci' || lifecycle?.startsWith('build:')) return true
10
+ return argv.some((arg) => /\bnext(?:[\\/](?:dist[\\/]bin[\\/])?next)?\b/.test(arg))
11
+ && argv.some((arg) => /\bbuild\b/.test(arg))
12
+ }
13
+
14
+ export const IS_BUILD_BOOTSTRAP = isBuildBootstrapEnv()
15
+
16
+ function resolveDataDir(): string {
17
+ if (process.env.DATA_DIR) return process.env.DATA_DIR
18
+ if (IS_BUILD_BOOTSTRAP) return path.join(os.tmpdir(), 'swarmclaw-build-data')
19
+ return path.join(process.cwd(), 'data')
20
+ }
21
+
22
+ export const DATA_DIR = resolveDataDir()
6
23
 
7
24
  function supportsChildWrites(dir: string): boolean {
8
25
  try {
@@ -19,6 +36,7 @@ function supportsChildWrites(dir: string): boolean {
19
36
  // when agents create/modify files. Falls back to data/workspace for Docker/CI.
20
37
  function resolveWorkspaceDir(): string {
21
38
  if (process.env.WORKSPACE_DIR) return process.env.WORKSPACE_DIR
39
+ if (IS_BUILD_BOOTSTRAP) return path.join(DATA_DIR, 'workspace')
22
40
  const external = path.join(os.homedir(), '.swarmclaw', 'workspace')
23
41
  if (supportsChildWrites(external)) {
24
42
  return external
@@ -30,6 +48,7 @@ export const WORKSPACE_DIR = resolveWorkspaceDir()
30
48
 
31
49
  function resolveBrowserProfilesDir(): string {
32
50
  if (process.env.BROWSER_PROFILES_DIR) return process.env.BROWSER_PROFILES_DIR
51
+ if (IS_BUILD_BOOTSTRAP) return path.join(DATA_DIR, 'browser-profiles')
33
52
  const external = path.join(os.homedir(), '.swarmclaw', 'browser-profiles')
34
53
  if (supportsChildWrites(external)) {
35
54
  return external