@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,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { memo, useCallback, useState } from 'react'
3
+ import { memo, useEffect, useState } from 'react'
4
4
  import { AgentAvatar } from '@/components/agents/agent-avatar'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
6
 
@@ -115,7 +115,7 @@ const SwarmMemberCard = memo(function SwarmMemberCard({
115
115
  agents,
116
116
  }: {
117
117
  member: SwarmMemberData
118
- agents: Record<string, any>
118
+ agents: Record<string, { avatarSeed?: string; avatarUrl?: string | null; name?: string }>
119
119
  }) {
120
120
  const [expanded, setExpanded] = useState(false)
121
121
  const cfg = MEMBER_STATUS_CONFIG[member.status]
@@ -206,6 +206,13 @@ const SwarmMemberCard = memo(function SwarmMemberCard({
206
206
  function SwarmSummaryBar({ data }: { data: SwarmStatusData }) {
207
207
  const cfg = SWARM_STATUS_CONFIG[data.status]
208
208
  const isTerminal = data.status === 'completed' || data.status === 'partial' || data.status === 'failed'
209
+ const [now, setNow] = useState(data.completedAt ?? data.createdAt)
210
+
211
+ useEffect(() => {
212
+ if (isTerminal) return
213
+ const id = setInterval(() => setNow(Date.now()), 1000)
214
+ return () => clearInterval(id)
215
+ }, [isTerminal])
209
216
 
210
217
  const formatDuration = (ms: number) => {
211
218
  if (ms < 1000) return `${ms}ms`
@@ -215,7 +222,7 @@ function SwarmSummaryBar({ data }: { data: SwarmStatusData }) {
215
222
 
216
223
  const durationMs = data.completedAt
217
224
  ? data.completedAt - data.createdAt
218
- : Date.now() - data.createdAt
225
+ : now - data.createdAt
219
226
 
220
227
  return (
221
228
  <div
@@ -50,6 +50,10 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
50
50
  const streamPhase = useChatStore((s) => s.streamPhase)
51
51
  const streamToolName = useChatStore((s) => s.streamToolName)
52
52
  const visibleQueuedMessages = listQueuedMessagesForSession(queuedMessages, sessionId)
53
+ const sendingQueuedMessages = visibleQueuedMessages.filter((item) => item.sending)
54
+ const pendingQueuedMessages = visibleQueuedMessages.filter((item) => !item.sending)
55
+ const displayedQueuedMessages = [...sendingQueuedMessages, ...pendingQueuedMessages]
56
+ const nextPendingRunId = pendingQueuedMessages[0]?.runId ?? null
53
57
  const shouldQueue = !!sessionId && (busy || visibleQueuedMessages.length > 0)
54
58
 
55
59
  useEffect(() => {
@@ -178,6 +182,8 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
178
182
  const hasContent = value.trim().length > 0 || pendingFiles.length > 0
179
183
  const queueStatusLabel = !busy
180
184
  ? 'Queue ready'
185
+ : sendingQueuedMessages.length > 0 && pendingQueuedMessages.length === 0
186
+ ? 'Sending now'
181
187
  : streamPhase === 'queued'
182
188
  ? 'Queued'
183
189
  : streamPhase === 'tool' && streamToolName
@@ -191,6 +197,8 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
191
197
  : 'Working'
192
198
  const queueStatusDetail = !busy
193
199
  ? 'Queued messages are ready and will dispatch automatically.'
200
+ : sendingQueuedMessages.length > 0 && pendingQueuedMessages.length === 0
201
+ ? 'The queued message has been accepted and should appear in the transcript shortly.'
194
202
  : 'Queued messages will send automatically when the current turn finishes.'
195
203
 
196
204
  return (
@@ -229,9 +237,16 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
229
237
  <span className={`relative inline-flex h-2.5 w-2.5 rounded-full ${busy ? 'bg-amber-300' : 'bg-white/[0.45]'}`} />
230
238
  </span>
231
239
  <span className="label-mono text-amber-300/80">Message queue</span>
232
- <span className="rounded-pill border border-amber-400/15 bg-amber-400/10 px-2 py-0.5 text-[10px] font-600 text-amber-200">
233
- {visibleQueuedMessages.length}
234
- </span>
240
+ {pendingQueuedMessages.length > 0 && (
241
+ <span className="rounded-pill border border-amber-400/15 bg-amber-400/10 px-2 py-0.5 text-[10px] font-600 text-amber-200">
242
+ {pendingQueuedMessages.length}
243
+ </span>
244
+ )}
245
+ {sendingQueuedMessages.length > 0 && (
246
+ <span className="rounded-pill border border-sky-300/15 bg-sky-300/10 px-2 py-0.5 text-[10px] font-600 text-sky-200">
247
+ {sendingQueuedMessages.length} sending
248
+ </span>
249
+ )}
235
250
  <span className={`rounded-pill border px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] ${
236
251
  busy
237
252
  ? 'border-amber-300/20 bg-amber-300/10 text-amber-100'
@@ -256,7 +271,7 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
256
271
  Stop
257
272
  </button>
258
273
  )}
259
- {sessionId && visibleQueuedMessages.length > 1 && (
274
+ {sessionId && pendingQueuedMessages.length > 0 && (
260
275
  <button
261
276
  type="button"
262
277
  onClick={() => { void clearQueuedMessagesForSession(sessionId) }}
@@ -268,21 +283,26 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
268
283
  </div>
269
284
  </div>
270
285
  <div className="max-h-[184px] space-y-1.5 overflow-y-auto px-2.5 py-2.5">
271
- {visibleQueuedMessages.map((item, index) => (
286
+ {displayedQueuedMessages.map((item, index) => (
272
287
  <div
273
288
  key={item.runId}
274
289
  className={`group flex items-start gap-3 rounded-[12px] border px-3 py-2.5 transition-all ${
275
- index === 0
276
- ? 'border-amber-300/20 bg-amber-300/[0.07]'
277
- : 'border-white/[0.05] bg-white/[0.02]'
290
+ item.sending
291
+ ? 'border-sky-300/15 bg-sky-300/[0.06]'
292
+ : item.runId === nextPendingRunId
293
+ ? 'border-amber-300/20 bg-amber-300/[0.07]'
294
+ : 'border-white/[0.05] bg-white/[0.02]'
278
295
  }`}
279
296
  >
280
297
  <div className={`mt-0.5 flex h-6 min-w-6 items-center justify-center rounded-[8px] px-2 text-[10px] font-700 ${
281
- index === 0
282
- ? 'bg-amber-300/15 text-amber-100'
283
- : 'bg-white/[0.06] text-text-3'
284
- }`}>
285
- {index + 1}
298
+ item.sending
299
+ ? 'bg-sky-300/15 text-sky-100'
300
+ : item.runId === nextPendingRunId
301
+ ? 'bg-amber-300/15 text-amber-100'
302
+ : 'border-white/[0.05] bg-white/[0.02]'
303
+ }`}
304
+ >
305
+ {item.sending ? '>' : pendingQueuedMessages.findIndex((candidate) => candidate.runId === item.runId) + 1}
286
306
  </div>
287
307
  <div className="min-w-0 flex-1">
288
308
  <div className="flex flex-wrap items-center gap-2">
@@ -290,7 +310,7 @@ export function ChatInput({ streaming, busy, onSend, onStop, extensionChatAction
290
310
  <span className="rounded-pill border border-sky-300/15 bg-sky-300/10 px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] text-sky-200 animate-pulse">
291
311
  Sending
292
312
  </span>
293
- ) : index === 0 && (
313
+ ) : item.runId === nextPendingRunId && (
294
314
  <span className="rounded-pill border border-amber-300/15 bg-amber-300/10 px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] text-amber-100">
295
315
  Next
296
316
  </span>
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState } from 'react'
3
+ import { useCallback, useEffect, useState } from 'react'
4
4
  import { api } from '@/lib/app/api-client'
5
5
  import { useWs } from '@/hooks/use-ws'
6
6
  import { StatusDot } from '@/components/ui/status-dot'
@@ -16,14 +16,13 @@ interface DaemonStatus {
16
16
  export function DaemonIndicator() {
17
17
  const [status, setStatus] = useState<DaemonStatus | null>(null)
18
18
 
19
- const fetchStatus = async () => {
20
- try {
21
- const data = await api<DaemonStatus>('GET', '/daemon')
22
- setStatus(data)
23
- } catch { /* ignore */ }
24
- }
19
+ const fetchStatus = useCallback(() => {
20
+ api<DaemonStatus>('GET', '/daemon')
21
+ .then(setStatus)
22
+ .catch(() => {})
23
+ }, [])
25
24
 
26
- useEffect(() => { fetchStatus() }, [])
25
+ useEffect(() => { fetchStatus() }, [fetchStatus])
27
26
  useWs('daemon', fetchStatus, 60_000)
28
27
 
29
28
  const toggle = async () => {
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState, useCallback } from 'react'
3
+ import { useEffect, useState } from 'react'
4
4
 
5
5
  const CHECK_INTERVAL = 5 * 60_000 // 5 minutes
6
6
 
@@ -19,22 +19,17 @@ export function UpdateBanner() {
19
19
  const [dismissed, setDismissed] = useState<string | null>(null)
20
20
  const [errorMsg, setErrorMsg] = useState('')
21
21
 
22
- const checkVersion = useCallback(async () => {
23
- try {
24
- const res = await fetch('/api/version')
25
- if (!res.ok) return
26
- const data: VersionInfo = await res.json()
27
- setVersion(data)
28
- } catch {
29
- // silently fail — no network or server issue
30
- }
31
- }, [])
32
-
33
22
  useEffect(() => {
23
+ const checkVersion = () => {
24
+ fetch('/api/version')
25
+ .then((res) => res.ok ? res.json() as Promise<VersionInfo> : null)
26
+ .then((data) => { if (data) setVersion(data) })
27
+ .catch(() => {})
28
+ }
34
29
  checkVersion()
35
30
  const id = setInterval(checkVersion, CHECK_INTERVAL)
36
31
  return () => clearInterval(id)
37
- }, [checkVersion])
32
+ }, [])
38
33
 
39
34
  const handleUpdate = async () => {
40
35
  setUpdateState('updating')
@@ -67,7 +67,7 @@ export function LogList() {
67
67
  useEffect(() => {
68
68
  fetchLogs()
69
69
  loadAgents()
70
- }, [fetchLogs])
70
+ }, [fetchLogs, loadAgents])
71
71
 
72
72
  useWs('logs', fetchLogs, autoRefresh ? 3000 : undefined)
73
73
 
@@ -1,5 +1,6 @@
1
1
  'use client'
2
2
 
3
+ import { useState } from 'react'
3
4
  import type { MemoryEntry } from '@/types'
4
5
  import { AgentAvatar } from '@/components/agents/agent-avatar'
5
6
  import { deriveMemoryScope, getMemoryScopeLabel, getMemoryTier } from '@/lib/memory-presentation'
@@ -15,6 +16,7 @@ interface Props {
15
16
  }
16
17
 
17
18
  export function MemoryCard({ entry, active, agentName, agentAvatarSeed, agentAvatarUrl, onClick }: Props) {
19
+ const [now] = useState(() => Date.now())
18
20
  const scope = deriveMemoryScope(entry)
19
21
  const tier = getMemoryTier(entry)
20
22
 
@@ -41,7 +43,7 @@ export function MemoryCard({ entry, active, agentName, agentAvatarSeed, agentAva
41
43
  )}
42
44
  <span className="font-display text-[13px] font-600 truncate flex-1 tracking-[-0.01em]">{entry.title}</span>
43
45
  <span className="text-[10px] text-text-3/60 shrink-0 tabular-nums font-mono">
44
- {timeAgoShort(entry.updatedAt || entry.createdAt, Date.now())}
46
+ {timeAgoShort(entry.updatedAt || entry.createdAt, now)}
45
47
  </span>
46
48
  </div>
47
49
  <div className="text-[12px] text-text-2/40 mt-1 line-clamp-3 leading-relaxed">
@@ -21,6 +21,7 @@ import type { ContextAction } from './org-chart-context-menu'
21
21
  import { useOrgChartPanZoom } from './use-org-chart-pan-zoom'
22
22
  import { useOrgChartDrag } from './use-org-chart-drag'
23
23
  import { useNavigate } from '@/lib/app/navigation'
24
+ import { useWs } from '@/hooks/use-ws'
24
25
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
25
26
 
26
27
  const NODE_W = 200
@@ -57,6 +58,9 @@ export function OrgChartView() {
57
58
  // eslint-disable-next-line react-hooks/exhaustive-deps
58
59
  useEffect(() => { loadAgents(); loadSessions() }, [])
59
60
 
61
+ useWs('agents', loadAgents, 60_000)
62
+ useWs('sessions', loadSessions, 30_000)
63
+
60
64
  // Running agents — derived from sessions
61
65
  const runningAgentIds = useMemo(() => {
62
66
  const ids = new Set<string>()
@@ -58,8 +58,10 @@ export function ProjectList() {
58
58
  }
59
59
  for (const t of Object.values(tasks)) {
60
60
  if (t.projectId && map[t.projectId]) {
61
- map[t.projectId].tasks++
62
- if (t.status === 'completed') map[t.projectId].completedTasks++
61
+ if (t.status !== 'cancelled' && t.status !== 'archived') {
62
+ map[t.projectId].tasks++
63
+ if (t.status === 'completed') map[t.projectId].completedTasks++
64
+ }
63
65
  if (t.updatedAt && t.updatedAt > map[t.projectId].lastActivity) {
64
66
  map[t.projectId].lastActivity = t.updatedAt
65
67
  }
@@ -46,8 +46,9 @@ export function OverviewTab({ project, missions }: OverviewTabProps) {
46
46
  [now, projectTasks],
47
47
  )
48
48
 
49
- const completedTasks = projectTasks.filter((t) => t.status === 'completed').length
50
- const totalTasks = projectTasks.length
49
+ const actionableTasks = projectTasks.filter((t) => t.status !== 'cancelled' && t.status !== 'archived')
50
+ const completedTasks = actionableTasks.filter((t) => t.status === 'completed').length
51
+ const totalTasks = actionableTasks.length
51
52
  const progressPct = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0
52
53
 
53
54
  const tasksByStatus = useMemo(() => {
@@ -34,7 +34,7 @@ export function SecretSheet() {
34
34
 
35
35
  useEffect(() => {
36
36
  if (open) loadAgents()
37
- }, [open])
37
+ }, [open, loadAgents])
38
38
 
39
39
  useEffect(() => {
40
40
  if (editing) {
@@ -18,7 +18,7 @@ export function SecretsList({ inSidebar }: Props) {
18
18
 
19
19
  useEffect(() => {
20
20
  loadSecrets()
21
- }, [])
21
+ }, [loadSecrets])
22
22
 
23
23
  const handleDelete = async (e: React.MouseEvent, id: string) => {
24
24
  e.stopPropagation()
@@ -31,13 +31,19 @@ export function AgentSwitchDialog() {
31
31
  return () => window.removeEventListener('keydown', handler)
32
32
  }, [])
33
33
 
34
- // Reset on open
34
+ // Reset on open (render-time state adjustment)
35
+ const [prevOpen, setPrevOpen] = useState(false)
36
+ if (open && !prevOpen) {
37
+ setQuery('')
38
+ setSelectedIdx(0)
39
+ }
40
+ if (open !== prevOpen) {
41
+ setPrevOpen(open)
42
+ }
43
+
44
+ // Focus input after opening
35
45
  useEffect(() => {
36
- if (open) {
37
- setQuery('')
38
- setSelectedIdx(0)
39
- setTimeout(() => inputRef.current?.focus(), 50)
40
- }
46
+ if (open) setTimeout(() => inputRef.current?.focus(), 50)
41
47
  }, [open])
42
48
 
43
49
  const filtered = useMemo(() => {
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState, useCallback, useMemo } from 'react'
3
+ import { useEffect, useState, useMemo } from 'react'
4
4
  import { api } from '@/lib/app/api-client'
5
5
  import { SearchInput } from '@/components/ui/search-input'
6
6
 
@@ -37,26 +37,30 @@ export function DirBrowser({ value, file, onChange, onClear }: DirBrowserProps)
37
37
  const [pathInput, setPathInput] = useState('')
38
38
  const [search, setSearch] = useState('')
39
39
 
40
- const fetchDirs = useCallback(async (dirPath: string) => {
41
- setLoading(true)
42
- try {
43
- const data = await api<DirApiResponse>('GET', `/dirs?path=${encodeURIComponent(dirPath)}`)
44
- setDirs(data.dirs || [])
45
- setCurrentPath(data.currentPath || dirPath)
46
- setParentPath(data.parentPath || null)
47
- setPathInput(data.currentPath || dirPath)
48
- } catch {
49
- setDirs([])
50
- }
51
- setLoading(false)
52
- }, [])
53
-
54
- useEffect(() => {
40
+ // Reset search and mark loading when navigating in browse mode
41
+ const [prevBrowsePath, setPrevBrowsePath] = useState(browsePath)
42
+ const [prevMode, setPrevMode] = useState(mode)
43
+ if (browsePath !== prevBrowsePath || mode !== prevMode) {
44
+ setPrevBrowsePath(browsePath)
45
+ setPrevMode(mode)
55
46
  if (mode === 'browse') {
56
- fetchDirs(browsePath)
57
47
  setSearch('')
48
+ setLoading(true)
58
49
  }
59
- }, [browsePath, mode, fetchDirs])
50
+ }
51
+
52
+ useEffect(() => {
53
+ if (mode !== 'browse') return
54
+ api<DirApiResponse>('GET', `/dirs?path=${encodeURIComponent(browsePath)}`)
55
+ .then((data) => {
56
+ setDirs(data.dirs || [])
57
+ setCurrentPath(data.currentPath || browsePath)
58
+ setParentPath(data.parentPath || null)
59
+ setPathInput(data.currentPath || browsePath)
60
+ })
61
+ .catch(() => { setDirs([]) })
62
+ .finally(() => setLoading(false))
63
+ }, [browsePath, mode])
60
64
 
61
65
  const filteredDirs = useMemo(() => {
62
66
  if (!search) return dirs
@@ -64,8 +64,7 @@ export function SkillSheet() {
64
64
 
65
65
  useEffect(() => {
66
66
  if (open) loadAgents()
67
- // eslint-disable-next-line react-hooks/exhaustive-deps
68
- }, [open])
67
+ }, [open, loadAgents])
69
68
 
70
69
  useEffect(() => {
71
70
  if (open) {
@@ -91,7 +90,7 @@ export function SkillSheet() {
91
90
  setMetadataPreview(null)
92
91
  }
93
92
  }
94
- }, [open, editingId])
93
+ }, [open, editingId, editing])
95
94
 
96
95
  const onClose = () => {
97
96
  setConfirmDelete(false)
@@ -29,7 +29,7 @@ export function TaskList({ inSidebar }: { inSidebar?: boolean }) {
29
29
  const [search, setSearch] = useState('')
30
30
  const [clearing, setClearing] = useState(false)
31
31
 
32
- useEffect(() => { loadTasks() }, [])
32
+ useEffect(() => { loadTasks() }, [loadTasks])
33
33
  useWs('tasks', loadTasks, 5000)
34
34
 
35
35
  const sorted = useMemo(() =>
@@ -151,7 +151,7 @@ export function TaskSheet() {
151
151
  if (open && !editing && !agentId && agentList.length) {
152
152
  setAgentId(agentList[0].id)
153
153
  }
154
- }, [open, editing, agentId, agentList.length, agents])
154
+ }, [open, editing, agentId, agentList])
155
155
 
156
156
  useEffect(() => {
157
157
  if (!editing?.id || !open) {
@@ -8,35 +8,57 @@ import { useWs } from './use-ws'
8
8
  /** Call an OpenClaw gateway RPC method via the proxy route. */
9
9
  export function useOpenClawRpc<T = unknown>(method: string | null, params?: unknown) {
10
10
  const [data, setData] = useState<T | null>(null)
11
- const [loading, setLoading] = useState(false)
11
+ const [loading, setLoading] = useState(!!method)
12
12
  const [error, setError] = useState<string | null>(null)
13
13
  const paramsRef = useRef(params)
14
- paramsRef.current = params
14
+ useEffect(() => { paramsRef.current = params })
15
15
 
16
- const fetch = useCallback(async () => {
16
+ // doFetch only uses async callbacks for setState (no synchronous setState)
17
+ const doFetch = useCallback(() => {
17
18
  if (!method) return
18
- setLoading(true)
19
- setError(null)
20
- try {
21
- const res = await api<{ ok: boolean; result: T; error?: string }>('POST', '/openclaw/gateway', {
22
- method,
23
- params: paramsRef.current,
19
+ api<{ ok: boolean; result: T; error?: string }>('POST', '/openclaw/gateway', {
20
+ method,
21
+ params: paramsRef.current,
22
+ })
23
+ .then((res) => {
24
+ if (res.error) {
25
+ setError(res.error)
26
+ } else {
27
+ setData(res.result)
28
+ }
29
+ })
30
+ .catch((err: unknown) => {
31
+ setError(errorMessage(err))
24
32
  })
25
- if (res.error) {
26
- setError(res.error)
27
- } else {
28
- setData(res.result)
29
- }
30
- } catch (err: unknown) {
31
- setError(errorMessage(err))
32
- } finally {
33
+ .finally(() => {
34
+ setLoading(false)
35
+ })
36
+ }, [method])
37
+
38
+ // Reset loading/error when method changes (render-time state adjustment)
39
+ const [prevMethod, setPrevMethod] = useState(method)
40
+ if (method !== prevMethod) {
41
+ setPrevMethod(method)
42
+ if (method) {
43
+ setLoading(true)
44
+ setError(null)
45
+ } else {
33
46
  setLoading(false)
47
+ setError(null)
48
+ setData(null)
34
49
  }
35
- }, [method])
50
+ }
36
51
 
37
- useEffect(() => { fetch() }, [fetch])
52
+ useEffect(() => { doFetch() }, [doFetch])
38
53
 
39
- return { data, loading, error, refetch: fetch }
54
+ // refetch wraps doFetch with loading/error reset (called from event handlers)
55
+ const refetch = useCallback(() => {
56
+ setLoading(true)
57
+ setError(null)
58
+ doFetch()
59
+ }, [doFetch])
60
+
61
+ return { data, loading, error, refetch }
40
62
  }
41
63
 
42
64
  /** Subscribe to an OpenClaw event topic via the WS hub. */
@@ -48,13 +70,10 @@ export function useOpenClawEvent(topic: string, handler: () => void) {
48
70
  export function useOpenClawConnected() {
49
71
  const [connected, setConnected] = useState(false)
50
72
 
51
- const check = useCallback(async () => {
52
- try {
53
- const res = await api<{ connected: boolean }>('GET', '/openclaw/gateway')
54
- setConnected(res.connected)
55
- } catch {
56
- setConnected(false)
57
- }
73
+ const check = useCallback(() => {
74
+ api<{ connected: boolean }>('GET', '/openclaw/gateway')
75
+ .then((res) => setConnected(res.connected))
76
+ .catch(() => setConnected(false))
58
77
  }, [])
59
78
 
60
79
  useEffect(() => { check() }, [check])
@@ -1,7 +1,10 @@
1
1
  import { hmrSingleton } from '@/lib/shared-utils'
2
2
 
3
+ const TAG = 'instrumentation'
4
+
3
5
  export async function register() {
4
6
  if (process.env.NEXT_RUNTIME === 'nodejs') {
7
+ const { log } = await import('@/lib/server/logger')
5
8
  const isWorkerOnly = process.env.SWARMCLAW_WORKER_ONLY === '1'
6
9
  const { initWsServer, closeWsServer } = await import('./lib/server/ws-hub')
7
10
  const { ensureDaemonStarted } = await import('@/lib/server/runtime/daemon-state')
@@ -12,12 +15,12 @@ export async function register() {
12
15
  backfillAllKnownPeerIds()
13
16
  pruneThreadConnectorMirrors()
14
17
  } catch (err) {
15
- console.error('[instrumentation] connector session consolidation failed:', err)
18
+ log.error(TAG, 'connector session consolidation failed:', err)
16
19
  }
17
20
 
18
21
  // In worker-only mode, we FORCE the daemon to start, but skip the WebSocket listener
19
22
  if (isWorkerOnly) {
20
- console.log('[instrumentation] Booting in WORKER ONLY mode')
23
+ log.info(TAG, 'Booting in WORKER ONLY mode')
21
24
  ensureDaemonStarted('worker-boot')
22
25
  } else {
23
26
  // In normal mode, we start the WS server, and conditionally start the daemon if autostart allows
@@ -34,12 +37,12 @@ export async function register() {
34
37
  const shutdown = async (signal: string) => {
35
38
  if (shutdownState.shuttingDown) return
36
39
  shutdownState.shuttingDown = true
37
- console.log(`[server] ${signal} received, shutting down gracefully...`)
40
+ log.info(TAG, `${signal} received, shutting down gracefully...`)
38
41
  try {
39
42
  const { stopDaemon } = await import('@/lib/server/runtime/daemon-state')
40
43
  await stopDaemon({ source: signal })
41
44
  } catch (err) {
42
- console.error('[instrumentation] Failed to stop daemon during shutdown:', err)
45
+ log.error(TAG, 'Failed to stop daemon during shutdown:', err)
43
46
  }
44
47
  if (!isWorkerOnly) {
45
48
  await closeWsServer()
@@ -54,10 +57,10 @@ export async function register() {
54
57
  // that occur during dev server restarts when stdio pipes break
55
58
  process.on('uncaughtException', (err: NodeJS.ErrnoException) => {
56
59
  if (err.code === 'EPIPE') {
57
- console.warn('[instrumentation] Ignoring EPIPE (expected during dev server restart)')
60
+ log.warn(TAG, 'Ignoring EPIPE (expected during dev server restart)')
58
61
  return
59
62
  }
60
- console.error('[instrumentation] Uncaught exception:', err)
63
+ log.error(TAG, 'Uncaught exception:', err)
61
64
  process.exit(1)
62
65
  })
63
66
 
@@ -73,7 +76,7 @@ export async function register() {
73
76
  ) {
74
77
  return
75
78
  }
76
- console.error('[instrumentation] Unhandled rejection:', err)
79
+ log.error(TAG, 'Unhandled rejection:', err)
77
80
  })
78
81
 
79
82
  shutdownState.registered = true
@@ -0,0 +1,3 @@
1
+ export function createAssistantRenderId(): string {
2
+ return `assistant-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
3
+ }