@swarmclawai/swarmclaw 0.7.2 → 0.7.4

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 (274) hide show
  1. package/README.md +116 -50
  2. package/bin/package-manager.js +157 -0
  3. package/bin/package-manager.test.js +90 -0
  4. package/bin/server-cmd.js +38 -7
  5. package/bin/swarmclaw.js +54 -4
  6. package/bin/update-cmd.js +48 -10
  7. package/bin/update-cmd.test.js +55 -0
  8. package/package.json +8 -3
  9. package/scripts/postinstall.mjs +26 -0
  10. package/src/app/api/agents/[id]/route.ts +43 -0
  11. package/src/app/api/agents/[id]/thread/route.ts +39 -8
  12. package/src/app/api/agents/route.ts +35 -2
  13. package/src/app/api/auth/route.ts +77 -8
  14. package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
  15. package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
  16. package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
  17. package/src/app/api/chatrooms/[id]/route.ts +6 -0
  18. package/src/app/api/chats/[id]/browser/route.ts +5 -1
  19. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  20. package/src/app/api/chats/[id]/messages/route.ts +19 -13
  21. package/src/app/api/chats/[id]/route.ts +30 -0
  22. package/src/app/api/chats/[id]/stop/route.ts +6 -1
  23. package/src/app/api/chats/heartbeat/route.ts +2 -1
  24. package/src/app/api/chats/route.ts +23 -1
  25. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  26. package/src/app/api/connectors/doctor/route.ts +13 -0
  27. package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
  28. package/src/app/api/external-agents/[id]/route.ts +31 -0
  29. package/src/app/api/external-agents/register/route.ts +3 -0
  30. package/src/app/api/external-agents/route.ts +66 -0
  31. package/src/app/api/files/open/route.ts +16 -14
  32. package/src/app/api/gateways/[id]/health/route.ts +28 -0
  33. package/src/app/api/gateways/[id]/route.ts +79 -0
  34. package/src/app/api/gateways/route.ts +57 -0
  35. package/src/app/api/memory/maintenance/route.ts +11 -1
  36. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  37. package/src/app/api/openclaw/gateway/route.ts +10 -7
  38. package/src/app/api/openclaw/skills/route.ts +12 -4
  39. package/src/app/api/plugins/dependencies/route.ts +24 -0
  40. package/src/app/api/plugins/install/route.ts +15 -92
  41. package/src/app/api/plugins/route.ts +3 -26
  42. package/src/app/api/plugins/settings/route.ts +17 -12
  43. package/src/app/api/plugins/ui/route.ts +1 -0
  44. package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
  45. package/src/app/api/schedules/[id]/route.ts +38 -9
  46. package/src/app/api/schedules/route.ts +51 -28
  47. package/src/app/api/settings/route.ts +55 -17
  48. package/src/app/api/setup/doctor/route.ts +6 -4
  49. package/src/app/api/tasks/[id]/route.ts +16 -6
  50. package/src/app/api/tasks/bulk/route.ts +3 -3
  51. package/src/app/api/tasks/route.ts +9 -4
  52. package/src/app/api/webhooks/[id]/route.ts +8 -1
  53. package/src/app/page.tsx +135 -17
  54. package/src/cli/binary.test.js +142 -0
  55. package/src/cli/index.js +38 -11
  56. package/src/cli/index.test.js +195 -0
  57. package/src/cli/index.ts +21 -12
  58. package/src/cli/server-cmd.test.js +59 -0
  59. package/src/cli/spec.js +20 -2
  60. package/src/components/agents/agent-card.tsx +15 -12
  61. package/src/components/agents/agent-chat-list.tsx +101 -1
  62. package/src/components/agents/agent-list.tsx +46 -9
  63. package/src/components/agents/agent-sheet.tsx +456 -23
  64. package/src/components/agents/inspector-panel.tsx +110 -49
  65. package/src/components/agents/sandbox-env-panel.tsx +4 -1
  66. package/src/components/auth/access-key-gate.tsx +36 -97
  67. package/src/components/auth/setup-wizard.tsx +970 -275
  68. package/src/components/chat/chat-area.tsx +70 -27
  69. package/src/components/chat/chat-card.tsx +6 -21
  70. package/src/components/chat/chat-header.tsx +263 -366
  71. package/src/components/chat/chat-list.tsx +62 -26
  72. package/src/components/chat/checkpoint-timeline.tsx +1 -1
  73. package/src/components/chat/message-list.tsx +145 -19
  74. package/src/components/chatrooms/chatroom-input.tsx +96 -33
  75. package/src/components/chatrooms/chatroom-list.tsx +141 -72
  76. package/src/components/chatrooms/chatroom-message.tsx +7 -6
  77. package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
  78. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
  79. package/src/components/chatrooms/chatroom-view.tsx +422 -209
  80. package/src/components/chatrooms/reaction-picker.tsx +38 -33
  81. package/src/components/connectors/connector-list.tsx +265 -127
  82. package/src/components/connectors/connector-sheet.tsx +217 -0
  83. package/src/components/gateways/gateway-sheet.tsx +567 -0
  84. package/src/components/home/home-view.tsx +128 -4
  85. package/src/components/input/chat-input.tsx +135 -86
  86. package/src/components/layout/app-layout.tsx +385 -194
  87. package/src/components/layout/mobile-header.tsx +26 -8
  88. package/src/components/memory/memory-browser.tsx +71 -6
  89. package/src/components/memory/memory-card.tsx +18 -0
  90. package/src/components/memory/memory-detail.tsx +58 -31
  91. package/src/components/memory/memory-sheet.tsx +32 -4
  92. package/src/components/plugins/plugin-list.tsx +15 -3
  93. package/src/components/plugins/plugin-sheet.tsx +118 -9
  94. package/src/components/projects/project-detail.tsx +189 -1
  95. package/src/components/providers/provider-list.tsx +158 -2
  96. package/src/components/providers/provider-sheet.tsx +81 -70
  97. package/src/components/shared/agent-picker-list.tsx +2 -2
  98. package/src/components/shared/bottom-sheet.tsx +31 -15
  99. package/src/components/shared/command-palette.tsx +111 -24
  100. package/src/components/shared/confirm-dialog.tsx +45 -30
  101. package/src/components/shared/model-combobox.tsx +90 -8
  102. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  103. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  104. package/src/components/shared/settings/section-heartbeat.tsx +88 -6
  105. package/src/components/shared/settings/section-orchestrator.tsx +6 -3
  106. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  107. package/src/components/shared/settings/section-secrets.tsx +6 -6
  108. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  109. package/src/components/shared/settings/section-voice.tsx +5 -1
  110. package/src/components/shared/settings/section-web-search.tsx +10 -2
  111. package/src/components/shared/settings/settings-page.tsx +248 -47
  112. package/src/components/tasks/approvals-panel.tsx +211 -18
  113. package/src/components/tasks/task-board.tsx +242 -46
  114. package/src/components/ui/dialog.tsx +2 -2
  115. package/src/components/usage/metrics-dashboard.tsx +74 -1
  116. package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
  117. package/src/components/wallets/wallet-panel.tsx +17 -5
  118. package/src/components/webhooks/webhook-sheet.tsx +7 -7
  119. package/src/lib/auth.ts +17 -0
  120. package/src/lib/chat-streaming-state.test.ts +108 -0
  121. package/src/lib/chat-streaming-state.ts +108 -0
  122. package/src/lib/heartbeat-defaults.ts +48 -0
  123. package/src/lib/memory-presentation.ts +59 -0
  124. package/src/lib/openclaw-agent-id.test.ts +14 -0
  125. package/src/lib/openclaw-agent-id.ts +31 -0
  126. package/src/lib/provider-model-discovery-client.ts +29 -0
  127. package/src/lib/providers/index.ts +12 -5
  128. package/src/lib/runtime-loop.ts +105 -3
  129. package/src/lib/safe-storage.ts +6 -1
  130. package/src/lib/server/agent-assignment.test.ts +112 -0
  131. package/src/lib/server/agent-assignment.ts +169 -0
  132. package/src/lib/server/agent-runtime-config.test.ts +141 -0
  133. package/src/lib/server/agent-runtime-config.ts +277 -0
  134. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  135. package/src/lib/server/approvals-auto-approve.test.ts +264 -0
  136. package/src/lib/server/approvals.ts +483 -75
  137. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  138. package/src/lib/server/browser-state.test.ts +118 -0
  139. package/src/lib/server/browser-state.ts +123 -0
  140. package/src/lib/server/build-llm.test.ts +44 -0
  141. package/src/lib/server/build-llm.ts +11 -4
  142. package/src/lib/server/builtin-plugins.ts +34 -0
  143. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  144. package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
  145. package/src/lib/server/chat-execution.ts +402 -125
  146. package/src/lib/server/chatroom-health.test.ts +26 -0
  147. package/src/lib/server/chatroom-health.ts +2 -3
  148. package/src/lib/server/chatroom-helpers.test.ts +74 -2
  149. package/src/lib/server/chatroom-helpers.ts +144 -11
  150. package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
  151. package/src/lib/server/connectors/discord.ts +175 -11
  152. package/src/lib/server/connectors/doctor.test.ts +80 -0
  153. package/src/lib/server/connectors/doctor.ts +116 -0
  154. package/src/lib/server/connectors/manager.ts +994 -130
  155. package/src/lib/server/connectors/policy.test.ts +222 -0
  156. package/src/lib/server/connectors/policy.ts +452 -0
  157. package/src/lib/server/connectors/slack.ts +189 -10
  158. package/src/lib/server/connectors/telegram.ts +65 -15
  159. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  160. package/src/lib/server/connectors/thread-context.ts +72 -0
  161. package/src/lib/server/connectors/types.ts +41 -11
  162. package/src/lib/server/daemon-state.ts +62 -3
  163. package/src/lib/server/data-dir.ts +13 -0
  164. package/src/lib/server/delegation-jobs.test.ts +140 -0
  165. package/src/lib/server/delegation-jobs.ts +248 -0
  166. package/src/lib/server/document-utils.test.ts +47 -0
  167. package/src/lib/server/document-utils.ts +397 -0
  168. package/src/lib/server/eval/agent-regression.test.ts +47 -0
  169. package/src/lib/server/eval/agent-regression.ts +1742 -0
  170. package/src/lib/server/eval/runner.ts +11 -1
  171. package/src/lib/server/eval/store.ts +2 -1
  172. package/src/lib/server/heartbeat-service.ts +23 -43
  173. package/src/lib/server/heartbeat-source.test.ts +22 -0
  174. package/src/lib/server/heartbeat-source.ts +7 -0
  175. package/src/lib/server/identity-continuity.test.ts +77 -0
  176. package/src/lib/server/identity-continuity.ts +127 -0
  177. package/src/lib/server/mailbox-utils.ts +347 -0
  178. package/src/lib/server/main-agent-loop.ts +31 -964
  179. package/src/lib/server/memory-db.ts +4 -6
  180. package/src/lib/server/memory-tiers.ts +40 -0
  181. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  182. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  183. package/src/lib/server/openclaw-exec-config.ts +6 -5
  184. package/src/lib/server/openclaw-gateway.ts +123 -36
  185. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  186. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  187. package/src/lib/server/openclaw-sync.ts +3 -2
  188. package/src/lib/server/orchestrator-lg.ts +18 -8
  189. package/src/lib/server/orchestrator.ts +5 -4
  190. package/src/lib/server/playwright-proxy.mjs +27 -3
  191. package/src/lib/server/plugins.test.ts +215 -0
  192. package/src/lib/server/plugins.ts +832 -69
  193. package/src/lib/server/provider-health.ts +33 -3
  194. package/src/lib/server/provider-model-discovery.ts +481 -0
  195. package/src/lib/server/queue.ts +4 -21
  196. package/src/lib/server/runtime-settings.test.ts +119 -0
  197. package/src/lib/server/runtime-settings.ts +12 -92
  198. package/src/lib/server/schedule-normalization.ts +187 -0
  199. package/src/lib/server/scheduler.ts +2 -0
  200. package/src/lib/server/session-archive-memory.test.ts +85 -0
  201. package/src/lib/server/session-archive-memory.ts +230 -0
  202. package/src/lib/server/session-mailbox.ts +8 -18
  203. package/src/lib/server/session-reset-policy.test.ts +99 -0
  204. package/src/lib/server/session-reset-policy.ts +311 -0
  205. package/src/lib/server/session-run-manager.ts +33 -80
  206. package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
  207. package/src/lib/server/session-tools/calendar.ts +2 -12
  208. package/src/lib/server/session-tools/connector.ts +109 -8
  209. package/src/lib/server/session-tools/context.ts +14 -2
  210. package/src/lib/server/session-tools/crawl.ts +447 -0
  211. package/src/lib/server/session-tools/crud.ts +96 -34
  212. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  213. package/src/lib/server/session-tools/delegate.ts +406 -20
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
  215. package/src/lib/server/session-tools/discovery.ts +40 -12
  216. package/src/lib/server/session-tools/document.ts +283 -0
  217. package/src/lib/server/session-tools/email.ts +1 -3
  218. package/src/lib/server/session-tools/extract.ts +137 -0
  219. package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
  220. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  221. package/src/lib/server/session-tools/file.ts +243 -24
  222. package/src/lib/server/session-tools/http.ts +9 -3
  223. package/src/lib/server/session-tools/human-loop.ts +227 -0
  224. package/src/lib/server/session-tools/image-gen.ts +1 -3
  225. package/src/lib/server/session-tools/index.ts +87 -2
  226. package/src/lib/server/session-tools/mailbox.ts +276 -0
  227. package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
  228. package/src/lib/server/session-tools/memory.ts +35 -3
  229. package/src/lib/server/session-tools/monitor.ts +162 -12
  230. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  231. package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
  232. package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
  233. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  234. package/src/lib/server/session-tools/platform.ts +142 -4
  235. package/src/lib/server/session-tools/plugin-creator.ts +95 -25
  236. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  237. package/src/lib/server/session-tools/replicate.ts +1 -3
  238. package/src/lib/server/session-tools/sandbox.ts +51 -92
  239. package/src/lib/server/session-tools/schedule.ts +20 -10
  240. package/src/lib/server/session-tools/session-info.ts +58 -4
  241. package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
  242. package/src/lib/server/session-tools/shell.ts +2 -2
  243. package/src/lib/server/session-tools/subagent.ts +195 -27
  244. package/src/lib/server/session-tools/table.ts +587 -0
  245. package/src/lib/server/session-tools/wallet.ts +13 -10
  246. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  247. package/src/lib/server/session-tools/web.ts +947 -108
  248. package/src/lib/server/storage.ts +255 -10
  249. package/src/lib/server/stream-agent-chat.test.ts +61 -0
  250. package/src/lib/server/stream-agent-chat.ts +185 -25
  251. package/src/lib/server/structured-extract.test.ts +72 -0
  252. package/src/lib/server/structured-extract.ts +373 -0
  253. package/src/lib/server/task-mention.test.ts +16 -2
  254. package/src/lib/server/task-mention.ts +61 -11
  255. package/src/lib/server/tool-aliases.ts +80 -12
  256. package/src/lib/server/tool-capability-policy.ts +7 -1
  257. package/src/lib/server/tool-retry.ts +2 -0
  258. package/src/lib/server/watch-jobs.test.ts +173 -0
  259. package/src/lib/server/watch-jobs.ts +532 -0
  260. package/src/lib/server/ws-hub.ts +5 -3
  261. package/src/lib/setup-defaults.ts +352 -11
  262. package/src/lib/tool-definitions.ts +3 -4
  263. package/src/lib/validation/schemas.test.ts +26 -0
  264. package/src/lib/validation/schemas.ts +62 -1
  265. package/src/lib/ws-client.ts +14 -12
  266. package/src/proxy.ts +5 -5
  267. package/src/stores/use-app-store.ts +43 -7
  268. package/src/stores/use-chat-store.ts +31 -2
  269. package/src/stores/use-chatroom-store.ts +153 -26
  270. package/src/types/index.ts +470 -44
  271. package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
  272. package/src/components/chat/new-chat-sheet.tsx +0 -253
  273. package/src/lib/server/main-session.ts +0 -17
  274. package/src/lib/server/session-run-manager.test.ts +0 -26
package/src/app/page.tsx CHANGED
@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { initAudioContext } from '@/lib/tts'
6
6
  import { getStoredAccessKey, clearStoredAccessKey, api } from '@/lib/api-client'
7
+ import { safeStorageGet, safeStorageRemove, safeStorageSet } from '@/lib/safe-storage'
7
8
  import { connectWs, disconnectWs } from '@/lib/ws-client'
8
9
  import { fetchWithTimeout } from '@/lib/fetch-timeout'
9
10
  import { useWs } from '@/hooks/use-ws'
@@ -12,10 +13,17 @@ import { UserPicker } from '@/components/auth/user-picker'
12
13
  import { SetupWizard } from '@/components/auth/setup-wizard'
13
14
  import { AppLayout } from '@/components/layout/app-layout'
14
15
  import { useViewRouter } from '@/hooks/use-view-router'
16
+ import type { Agent } from '@/types'
15
17
 
16
18
  const AUTH_CHECK_TIMEOUT_MS = 8_000
19
+ const POST_AUTH_BOOTSTRAP_TIMEOUT_MS = 8_000
17
20
 
18
- function FullScreenLoader() {
21
+ function FullScreenLoader(props: {
22
+ stage?: string | null
23
+ stalled?: boolean
24
+ onReload?: () => void
25
+ onReset?: () => void
26
+ }) {
19
27
  return (
20
28
  <div className="h-full flex flex-col items-center justify-center bg-bg overflow-hidden select-none">
21
29
  {/* Animated orbital ring */}
@@ -106,6 +114,42 @@ function FullScreenLoader() {
106
114
  />
107
115
  </div>
108
116
 
117
+ {props.stage ? (
118
+ <p
119
+ className="mt-4 text-[12px] text-text-3"
120
+ style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}
121
+ >
122
+ {props.stage}
123
+ </p>
124
+ ) : null}
125
+
126
+ {props.stalled ? (
127
+ <div
128
+ className="mt-6 max-w-[360px] px-4 text-center"
129
+ style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.5s both' }}
130
+ >
131
+ <p className="text-[12px] text-text-2">
132
+ Startup is taking longer than expected. This usually means the browser kept stale local state while the dev server restarted.
133
+ </p>
134
+ <div className="mt-4 flex items-center justify-center gap-3">
135
+ <button
136
+ type="button"
137
+ onClick={props.onReload}
138
+ className="px-4 py-2 rounded-[12px] border border-white/[0.08] bg-surface text-[12px] text-text-2 transition-colors hover:bg-surface-2"
139
+ >
140
+ Reload
141
+ </button>
142
+ <button
143
+ type="button"
144
+ onClick={props.onReset}
145
+ className="px-4 py-2 rounded-[12px] border border-white/[0.08] bg-transparent text-[12px] text-text-3 transition-colors hover:bg-white/[0.04]"
146
+ >
147
+ Reset Local Session
148
+ </button>
149
+ </div>
150
+ </div>
151
+ ) : null}
152
+
109
153
  {/* Loading animation keyframes */}
110
154
  <style>{`
111
155
  @keyframes sc-orbit {
@@ -150,16 +194,24 @@ export default function Home() {
150
194
 
151
195
  const [authChecked, setAuthChecked] = useState(false)
152
196
  const [authenticated, setAuthenticated] = useState(false)
197
+ const [bootTimedOut, setBootTimedOut] = useState(false)
153
198
  const [setupDone, setSetupDone] = useState<boolean | null>(() => {
154
- if (typeof window !== 'undefined' && localStorage.getItem('sc_setup_done') === '1') return true
199
+ if (safeStorageGet('sc_setup_done') === '1') return true
155
200
  return null
156
201
  })
157
202
 
158
203
  const checkAuth = useCallback(async () => {
159
204
  const key = getStoredAccessKey()
160
205
  if (!key) {
161
- setAuthChecked(true)
162
- setAuthenticated(false)
206
+ try {
207
+ const res = await fetchWithTimeout('/api/auth', {}, AUTH_CHECK_TIMEOUT_MS)
208
+ const data = await res.json().catch(() => ({}))
209
+ setAuthenticated(data?.authenticated === true)
210
+ } catch {
211
+ setAuthenticated(false)
212
+ } finally {
213
+ setAuthChecked(true)
214
+ }
163
215
  return
164
216
  }
165
217
 
@@ -176,7 +228,8 @@ export default function Home() {
176
228
  setAuthenticated(false)
177
229
  }
178
230
  } catch {
179
- setAuthenticated(true)
231
+ clearStoredAccessKey()
232
+ setAuthenticated(false)
180
233
  } finally {
181
234
  setAuthChecked(true)
182
235
  }
@@ -186,7 +239,10 @@ export default function Home() {
186
239
  const syncUserFromServer = useCallback(async () => {
187
240
  if (currentUser) return // already have a name locally
188
241
  try {
189
- const settings = await api<{ userName?: string }>('GET', '/settings')
242
+ const settings = await api<{ userName?: string }>('GET', '/settings', undefined, {
243
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
244
+ retries: 0,
245
+ })
190
246
  if (settings.userName) {
191
247
  setUser(settings.userName)
192
248
  }
@@ -222,11 +278,14 @@ export default function Home() {
222
278
  let cancelled = false
223
279
  ;(async () => {
224
280
  try {
225
- const state = useAppStore.getState()
226
- await state.loadAgents()
281
+ const agents = await api<Record<string, Agent>>('GET', '/agents', undefined, {
282
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
283
+ retries: 0,
284
+ })
227
285
  if (cancelled) return
286
+ useAppStore.setState({ agents })
228
287
 
229
- const { agents, currentAgentId, appSettings } = useAppStore.getState()
288
+ const { currentAgentId, appSettings } = useAppStore.getState()
230
289
  // Priority: persisted agent > settings default > first agent
231
290
  const targetId = (currentAgentId && agents[currentAgentId])
232
291
  ? currentAgentId
@@ -249,14 +308,23 @@ export default function Home() {
249
308
  let cancelled = false
250
309
  ;(async () => {
251
310
  try {
252
- const [settings, creds] = await Promise.all([
253
- api<{ setupCompleted?: boolean }>('GET', '/settings'),
254
- api<Record<string, unknown>>('GET', '/credentials'),
311
+ const [settingsResult, credsResult] = await Promise.allSettled([
312
+ api<{ setupCompleted?: boolean }>('GET', '/settings', undefined, {
313
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
314
+ retries: 0,
315
+ }),
316
+ api<Record<string, unknown>>('GET', '/credentials', undefined, {
317
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
318
+ retries: 0,
319
+ }),
255
320
  ])
256
321
  if (cancelled) return
322
+ const settings = settingsResult.status === 'fulfilled' ? settingsResult.value : {}
323
+ const creds = credsResult.status === 'fulfilled' ? credsResult.value : {}
324
+ const bothFailed = settingsResult.status === 'rejected' && credsResult.status === 'rejected'
257
325
  const hasCreds = Object.keys(creds).length > 0
258
- const done = settings.setupCompleted === true || hasCreds
259
- if (done) localStorage.setItem('sc_setup_done', '1')
326
+ const done = bothFailed ? true : settings.setupCompleted === true || hasCreds
327
+ if (done) safeStorageSet('sc_setup_done', '1')
260
328
  setSetupDone(done)
261
329
  } catch {
262
330
  if (!cancelled) setSetupDone(true) // on error, skip wizard
@@ -286,10 +354,60 @@ export default function Home() {
286
354
 
287
355
  useViewRouter()
288
356
 
289
- if (!hydrated || !authChecked) return <FullScreenLoader />
357
+ const bootStage = !hydrated
358
+ ? 'Restoring local session'
359
+ : !authChecked
360
+ ? 'Checking access'
361
+ : authenticated && currentUser && setupDone === null
362
+ ? 'Loading setup state'
363
+ : authenticated && currentUser && !agentReady
364
+ ? 'Restoring agent workspace'
365
+ : null
366
+
367
+ useEffect(() => {
368
+ if (!bootStage) {
369
+ setBootTimedOut(false)
370
+ return
371
+ }
372
+ const timer = window.setTimeout(() => setBootTimedOut(true), 15_000)
373
+ return () => window.clearTimeout(timer)
374
+ }, [bootStage])
375
+
376
+ const reloadApp = useCallback(() => {
377
+ window.location.reload()
378
+ }, [])
379
+
380
+ const resetLocalSession = useCallback(() => {
381
+ clearStoredAccessKey()
382
+ disconnectWs()
383
+ safeStorageRemove('sc_user')
384
+ safeStorageRemove('sc_agent')
385
+ safeStorageRemove('sc_setup_done')
386
+ window.location.assign('/')
387
+ }, [])
388
+
389
+ if (!hydrated || !authChecked) {
390
+ return (
391
+ <FullScreenLoader
392
+ stage={bootStage}
393
+ stalled={bootTimedOut}
394
+ onReload={reloadApp}
395
+ onReset={resetLocalSession}
396
+ />
397
+ )
398
+ }
290
399
  if (!authenticated) return <AccessKeyGate onAuthenticated={() => setAuthenticated(true)} />
291
400
  if (!currentUser) return <UserPicker />
292
- if (setupDone === null || !agentReady) return <FullScreenLoader />
293
- if (!setupDone) return <SetupWizard onComplete={() => { localStorage.setItem('sc_setup_done', '1'); setSetupDone(true) }} />
401
+ if (setupDone === null || !agentReady) {
402
+ return (
403
+ <FullScreenLoader
404
+ stage={bootStage}
405
+ stalled={bootTimedOut}
406
+ onReload={reloadApp}
407
+ onReset={resetLocalSession}
408
+ />
409
+ )
410
+ }
411
+ if (!setupDone) return <SetupWizard onComplete={() => { safeStorageSet('sc_setup_done', '1'); setSetupDone(true) }} />
294
412
  return <AppLayout />
295
413
  }
@@ -0,0 +1,142 @@
1
+ 'use strict'
2
+ /* eslint-disable @typescript-eslint/no-require-imports */
3
+
4
+ const test = require('node:test')
5
+ const assert = require('node:assert/strict')
6
+ const fs = require('node:fs')
7
+ const os = require('node:os')
8
+ const path = require('node:path')
9
+ const { spawnSync } = require('node:child_process')
10
+ const { buildLegacyTsCliArgs } = require('../../bin/swarmclaw.js')
11
+
12
+ const CLI_BIN = path.join(__dirname, '..', '..', 'bin', 'swarmclaw.js')
13
+ const PACKAGE_JSON = require('../../package.json')
14
+ const APP_ROOT = path.join(__dirname, '..', '..')
15
+
16
+ function runBinary(args, options = {}) {
17
+ return spawnSync(process.execPath, [CLI_BIN, ...args], {
18
+ cwd: options.cwd || APP_ROOT,
19
+ env: {
20
+ ...process.env,
21
+ ...options.env,
22
+ },
23
+ encoding: 'utf8',
24
+ })
25
+ }
26
+
27
+ function runWithMockedFetch(args, options = {}) {
28
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-binary-fetch-'))
29
+ const capturePath = path.join(tmpDir, 'capture.json')
30
+ const preloadPath = path.join(tmpDir, 'mock-fetch.cjs')
31
+
32
+ fs.writeFileSync(
33
+ preloadPath,
34
+ `
35
+ const fs = require('node:fs')
36
+ globalThis.fetch = async (url, init = {}) => {
37
+ const capture = {
38
+ url: String(url),
39
+ method: init.method || 'GET',
40
+ headers: init.headers || {},
41
+ body: typeof init.body === 'string'
42
+ ? init.body
43
+ : (Buffer.isBuffer(init.body) ? init.body.toString('utf8') : null),
44
+ }
45
+ fs.writeFileSync(process.env.SWARMCLAW_TEST_CAPTURE, JSON.stringify(capture), 'utf8')
46
+ return new Response(JSON.stringify([]), {
47
+ status: 200,
48
+ headers: { 'content-type': 'application/json' },
49
+ })
50
+ }
51
+ `,
52
+ 'utf8',
53
+ )
54
+
55
+ const nodeOptions = [process.env.NODE_OPTIONS, `--require=${preloadPath}`]
56
+ .filter(Boolean)
57
+ .join(' ')
58
+
59
+ const result = runBinary(args, {
60
+ ...options,
61
+ env: {
62
+ ...options.env,
63
+ NODE_OPTIONS: nodeOptions,
64
+ SWARMCLAW_TEST_CAPTURE: capturePath,
65
+ },
66
+ })
67
+
68
+ const capture = fs.existsSync(capturePath)
69
+ ? JSON.parse(fs.readFileSync(capturePath, 'utf8'))
70
+ : null
71
+
72
+ fs.rmSync(tmpDir, { recursive: true, force: true })
73
+ return { result, capture }
74
+ }
75
+
76
+ test('legacy-routed binary commands honor SWARMCLAW_API_KEY', () => {
77
+ const { result, capture } = runWithMockedFetch(
78
+ ['runs', 'list', '--raw', '--url', 'http://localhost:3456'],
79
+ {
80
+ env: {
81
+ SWARMCLAW_API_KEY: 'legacy-api-key',
82
+ SWARMCLAW_ACCESS_KEY: '',
83
+ SC_ACCESS_KEY: '',
84
+ },
85
+ },
86
+ )
87
+
88
+ assert.equal(result.status, 0, result.stderr)
89
+ assert.equal(result.stdout.trim(), '[]')
90
+ assert.equal(capture.headers['X-Access-Key'], 'legacy-api-key')
91
+ })
92
+
93
+ test('legacy-routed binary commands fall back to platform-api-key.txt', () => {
94
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-binary-keyfile-'))
95
+ fs.writeFileSync(path.join(tmpDir, 'platform-api-key.txt'), 'file-fallback-key\n', 'utf8')
96
+
97
+ const { result, capture } = runWithMockedFetch(
98
+ ['runs', 'list', '--raw', '--url', 'http://localhost:3456'],
99
+ {
100
+ cwd: tmpDir,
101
+ env: {
102
+ SWARMCLAW_API_KEY: '',
103
+ SWARMCLAW_ACCESS_KEY: '',
104
+ SC_ACCESS_KEY: '',
105
+ },
106
+ },
107
+ )
108
+
109
+ assert.equal(result.status, 0, result.stderr)
110
+ assert.equal(result.stdout.trim(), '[]')
111
+ assert.equal(capture.headers['X-Access-Key'], 'file-fallback-key')
112
+
113
+ fs.rmSync(tmpDir, { recursive: true, force: true })
114
+ })
115
+
116
+ test('binary server help exits successfully', () => {
117
+ const result = runBinary(['server', '--help'])
118
+ assert.equal(result.status, 0, result.stderr)
119
+ assert.match(result.stdout, /Usage: swarmclaw server/i)
120
+ })
121
+
122
+ test('binary update help exits successfully', () => {
123
+ const result = runBinary(['update', '--help'])
124
+ assert.equal(result.status, 0, result.stderr)
125
+ assert.match(result.stdout, /Usage: swarmclaw update/i)
126
+ })
127
+
128
+ test('binary version output matches package version', () => {
129
+ const result = runBinary(['--version'])
130
+ assert.equal(result.status, 0, result.stderr)
131
+ assert.equal(result.stdout.trim(), `${PACKAGE_JSON.name} ${PACKAGE_JSON.version}`)
132
+ })
133
+
134
+ test('legacy TS launcher falls back to tsx import when strip-types is unavailable', () => {
135
+ const cliPath = path.join(APP_ROOT, 'src', 'cli', 'index.ts')
136
+ const args = buildLegacyTsCliArgs(cliPath, ['runs', 'list'], {
137
+ supportsStripTypes: false,
138
+ hasTsxRuntime: true,
139
+ })
140
+
141
+ assert.deepEqual(args, ['--no-warnings', '--import', 'tsx', cliPath, 'runs', 'list'])
142
+ })
package/src/cli/index.js CHANGED
@@ -120,6 +120,9 @@ const COMMAND_GROUPS = [
120
120
  defaultBody: { action: 'repair' },
121
121
  }),
122
122
  cmd('health', 'GET', '/connectors/:id/health', 'Get connector health status'),
123
+ cmd('doctor', 'GET', '/connectors/:id/doctor', 'Get connector doctor diagnostics'),
124
+ cmd('doctor-preview', 'POST', '/connectors/:id/doctor', 'Preview connector doctor diagnostics with temporary overrides', { expectsJsonBody: true }),
125
+ cmd('doctor-draft', 'POST', '/connectors/doctor', 'Preview connector doctor diagnostics before saving a connector', { expectsJsonBody: true }),
123
126
  ],
124
127
  },
125
128
  {
@@ -178,6 +181,17 @@ const COMMAND_GROUPS = [
178
181
  cmd('suite', 'POST', '/eval/suite', 'Run a full eval suite against an agent', { expectsJsonBody: true }),
179
182
  ],
180
183
  },
184
+ {
185
+ name: 'external-agents',
186
+ description: 'Manage external agent runtimes',
187
+ commands: [
188
+ cmd('list', 'GET', '/external-agents', 'List external agent runtimes'),
189
+ cmd('create', 'POST', '/external-agents', 'Register an external agent runtime', { expectsJsonBody: true }),
190
+ cmd('update', 'PUT', '/external-agents/:id', 'Update an external agent runtime', { expectsJsonBody: true }),
191
+ cmd('delete', 'DELETE', '/external-agents/:id', 'Delete an external agent runtime'),
192
+ cmd('heartbeat', 'POST', '/external-agents/:id/heartbeat', 'Record an external agent heartbeat', { expectsJsonBody: true }),
193
+ ],
194
+ },
181
195
  {
182
196
  name: 'files',
183
197
  description: 'Serve and manage local files',
@@ -186,6 +200,17 @@ const COMMAND_GROUPS = [
186
200
  cmd('open', 'POST', '/files/open', 'Open a local file path via the host default app/browser', { expectsJsonBody: true }),
187
201
  ],
188
202
  },
203
+ {
204
+ name: 'gateways',
205
+ description: 'Manage named OpenClaw gateway profiles',
206
+ commands: [
207
+ cmd('list', 'GET', '/gateways', 'List configured gateway profiles'),
208
+ cmd('create', 'POST', '/gateways', 'Create a gateway profile', { expectsJsonBody: true }),
209
+ cmd('update', 'PUT', '/gateways/:id', 'Update a gateway profile', { expectsJsonBody: true }),
210
+ cmd('delete', 'DELETE', '/gateways/:id', 'Delete a gateway profile'),
211
+ cmd('health', 'GET', '/gateways/:id/health', 'Run a gateway health check'),
212
+ ],
213
+ },
189
214
  {
190
215
  name: 'ip',
191
216
  description: 'Get local IP/port metadata',
@@ -344,6 +369,7 @@ const COMMAND_GROUPS = [
344
369
  cmd('delete', 'DELETE', '/plugins', 'Delete an external plugin (use --query filename=plugin.js)'),
345
370
  cmd('update', 'PATCH', '/plugins', 'Update a plugin (use --query id=plugin.js or --query all=true)'),
346
371
  cmd('install', 'POST', '/plugins/install', 'Install plugin from URL', { expectsJsonBody: true }),
372
+ cmd('install-deps', 'POST', '/plugins/dependencies', 'Install or refresh plugin workspace dependencies', { expectsJsonBody: true }),
347
373
  cmd('marketplace', 'GET', '/plugins/marketplace', 'Get marketplace catalog'),
348
374
  cmd('settings-get', 'GET', '/plugins/settings', 'Get plugin settings (use --query pluginId=plugin_name)'),
349
375
  cmd('settings-set', 'PUT', '/plugins/settings', 'Set plugin settings (use --query pluginId=plugin_name and --data JSON)', { expectsJsonBody: true }),
@@ -360,6 +386,7 @@ const COMMAND_GROUPS = [
360
386
  cmd('update', 'PUT', '/providers/:id', 'Update provider', { expectsJsonBody: true }),
361
387
  cmd('delete', 'DELETE', '/providers/:id', 'Delete provider'),
362
388
  cmd('configs', 'GET', '/providers/configs', 'List saved provider configs'),
389
+ cmd('discover-models', 'GET', '/providers/:id/discover-models', 'Discover provider models via endpoint or credential checks'),
363
390
  cmd('ollama', 'GET', '/providers/ollama', 'List local Ollama models (use --query endpoint=http://localhost:11434)'),
364
391
  cmd('openclaw-health', 'GET', '/providers/openclaw/health', 'Probe OpenClaw endpoint/auth (use --query endpoint= --query credentialId= --query model=)'),
365
392
  cmd('models', 'GET', '/providers/:id/models', 'Get provider model overrides'),
@@ -425,10 +452,6 @@ const COMMAND_GROUPS = [
425
452
  cmd('messages-delete', 'DELETE', '/chats/:id/messages', 'Delete a message from a chat', { expectsJsonBody: true }),
426
453
  cmd('fork', 'POST', '/chats/:id/fork', 'Fork chat from a specific message index', { expectsJsonBody: true }),
427
454
  cmd('edit-resend', 'POST', '/chats/:id/edit-resend', 'Edit and resend from a specific message index', { expectsJsonBody: true }),
428
- cmd('main-loop', 'GET', '/chats/:id/main-loop', 'Get main mission loop state'),
429
- cmd('main-loop-action', 'POST', '/chats/:id/main-loop', 'Control main mission loop (pause/resume/set_goal/set_mode/clear_events/nudge)', {
430
- expectsJsonBody: true,
431
- }),
432
455
  cmd('chat', 'POST', '/chats/:id/chat', 'Send chat message (streaming)', {
433
456
  expectsJsonBody: true,
434
457
  responseType: 'sse',
@@ -673,7 +696,7 @@ function normalizeBaseUrl(raw) {
673
696
 
674
697
  function resolveAccessKey(opts, env, cwd) {
675
698
  if (opts.accessKey) return String(opts.accessKey).trim()
676
- const envKey = env.SWARMCLAW_API_KEY || env.SC_ACCESS_KEY || ''
699
+ const envKey = env.SWARMCLAW_API_KEY || env.SC_ACCESS_KEY || env.SWARMCLAW_ACCESS_KEY || ''
677
700
  if (envKey) return String(envKey).trim()
678
701
 
679
702
  const keyFile = path.join(cwd, 'platform-api-key.txt')
@@ -923,9 +946,11 @@ async function consumeSse(body, stdout, stderr, jsonOutput) {
923
946
  const reader = body.getReader()
924
947
  const decoder = new TextDecoder()
925
948
  let buffer = ''
949
+ const eventBoundary = /\r?\n\r?\n/
926
950
 
927
951
  function flushChunk(rawChunk) {
928
952
  const lines = rawChunk
953
+ .replace(/\r\n/g, '\n')
929
954
  .split('\n')
930
955
  .map((line) => line.trimEnd())
931
956
  .filter(Boolean)
@@ -968,12 +993,14 @@ async function consumeSse(body, stdout, stderr, jsonOutput) {
968
993
  if (done) break
969
994
  buffer += decoder.decode(value, { stream: true })
970
995
 
971
- let splitIndex = buffer.indexOf('\n\n')
972
- while (splitIndex >= 0) {
996
+ let match = eventBoundary.exec(buffer)
997
+ while (match) {
998
+ const splitIndex = match.index
999
+ const delimiterLength = match[0].length
973
1000
  const chunk = buffer.slice(0, splitIndex)
974
- buffer = buffer.slice(splitIndex + 2)
1001
+ buffer = buffer.slice(splitIndex + delimiterLength)
975
1002
  flushChunk(chunk)
976
- splitIndex = buffer.indexOf('\n\n')
1003
+ match = eventBoundary.exec(buffer)
977
1004
  }
978
1005
  }
979
1006
 
@@ -1086,7 +1113,7 @@ function renderGeneralHelp() {
1086
1113
  '',
1087
1114
  'Global options:',
1088
1115
  ' --base-url <url> API base URL (default: http://localhost:3456)',
1089
- ' --access-key <key> Access key override (else SWARMCLAW_API_KEY or platform-api-key.txt)',
1116
+ ' --access-key <key> Access key override (else SWARMCLAW_API_KEY/SWARMCLAW_ACCESS_KEY or platform-api-key.txt)',
1090
1117
  ' --data <json|@file|-> Request JSON body',
1091
1118
  ' --query key=value Query parameter (repeatable)',
1092
1119
  ' --header key=value Extra HTTP header (repeatable)',
@@ -1211,7 +1238,7 @@ async function runCli(argv, deps = {}) {
1211
1238
  }
1212
1239
 
1213
1240
  const accessKey = resolveAccessKey(parsed.opts, env, cwd)
1214
- const baseUrl = parsed.opts.baseUrl || env.SWARMCLAW_BASE_URL || 'http://localhost:3456'
1241
+ const baseUrl = parsed.opts.baseUrl || env.SWARMCLAW_BASE_URL || env.SWARMCLAW_URL || 'http://localhost:3456'
1215
1242
 
1216
1243
  const headerEntries = []
1217
1244
  for (const raw of parsed.opts.headers) {