@swarmclawai/swarmclaw 1.2.4 → 1.2.6

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 (262) hide show
  1. package/README.md +14 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/route.test.ts +49 -0
  49. package/src/app/api/providers/ollama/route.ts +6 -5
  50. package/src/app/api/schedules/[id]/route.ts +14 -108
  51. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  52. package/src/app/api/schedules/route.ts +9 -51
  53. package/src/app/api/settings/route.ts +4 -3
  54. package/src/app/api/setup/check-provider/route.ts +23 -1
  55. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  56. package/src/app/api/system/status/route.ts +2 -2
  57. package/src/app/api/tasks/[id]/route.ts +16 -202
  58. package/src/app/api/tasks/bulk/route.ts +5 -86
  59. package/src/app/api/tasks/metrics/route.ts +2 -1
  60. package/src/app/api/tasks/route.ts +11 -171
  61. package/src/app/api/upload/route.ts +1 -1
  62. package/src/app/api/uploads/[filename]/route.ts +1 -1
  63. package/src/app/api/uploads/route.ts +1 -1
  64. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  65. package/src/app/layout.tsx +9 -6
  66. package/src/app/protocols/page.tsx +71 -89
  67. package/src/app/tasks/page.tsx +32 -32
  68. package/src/cli/index.js +1 -0
  69. package/src/cli/spec.js +1 -0
  70. package/src/components/agents/agent-sheet.tsx +5 -5
  71. package/src/components/auth/setup-wizard/index.tsx +4 -4
  72. package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
  73. package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
  74. package/src/components/auth/setup-wizard/utils.ts +1 -1
  75. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  76. package/src/components/connectors/connector-list.tsx +26 -40
  77. package/src/components/connectors/connector-sheet.tsx +95 -149
  78. package/src/components/gateways/gateway-sheet.tsx +61 -110
  79. package/src/components/layout/live-query-sync.tsx +121 -0
  80. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  81. package/src/components/providers/app-query-provider.tsx +17 -0
  82. package/src/components/providers/provider-list.tsx +60 -61
  83. package/src/components/providers/provider-sheet.tsx +74 -56
  84. package/src/components/skills/skill-list.tsx +5 -18
  85. package/src/components/skills/skill-sheet.tsx +21 -20
  86. package/src/components/skills/skills-workspace.tsx +48 -87
  87. package/src/components/tasks/task-card.tsx +20 -13
  88. package/src/components/tasks/task-column.tsx +22 -7
  89. package/src/components/tasks/task-list.tsx +8 -11
  90. package/src/components/tasks/task-sheet.tsx +111 -103
  91. package/src/features/agents/queries.ts +20 -0
  92. package/src/features/chatrooms/queries.ts +20 -0
  93. package/src/features/chats/queries.ts +27 -0
  94. package/src/features/connectors/queries.ts +145 -0
  95. package/src/features/credentials/queries.ts +37 -0
  96. package/src/features/extensions/queries.ts +26 -0
  97. package/src/features/external-agents/queries.ts +36 -0
  98. package/src/features/gateways/queries.ts +274 -0
  99. package/src/features/missions/queries.ts +23 -0
  100. package/src/features/projects/queries.ts +20 -0
  101. package/src/features/protocols/queries.ts +149 -0
  102. package/src/features/providers/queries.ts +142 -0
  103. package/src/features/settings/queries.ts +20 -0
  104. package/src/features/skills/queries.ts +182 -0
  105. package/src/features/tasks/queries.ts +189 -0
  106. package/src/hooks/use-ws.ts +3 -2
  107. package/src/lib/app/api-client.ts +2 -2
  108. package/src/lib/providers/index.test.ts +108 -0
  109. package/src/lib/providers/index.ts +38 -15
  110. package/src/lib/query/client.ts +17 -0
  111. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  112. package/src/lib/server/agents/agent-service.ts +429 -0
  113. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  114. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  115. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  116. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  117. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  118. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  119. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  120. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  121. package/src/lib/server/build-llm.ts +7 -15
  122. package/src/lib/server/capability-router.test.ts +70 -1
  123. package/src/lib/server/capability-router.ts +24 -99
  124. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  125. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  126. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  127. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  128. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  129. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  130. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  131. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  132. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  133. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  134. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  135. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  136. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  137. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  138. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  139. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  140. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  141. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  142. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  143. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  144. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  145. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  146. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  147. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  148. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  149. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  150. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  151. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  152. package/src/lib/server/chats/chat-session-service.ts +410 -0
  153. package/src/lib/server/connectors/access.ts +1 -1
  154. package/src/lib/server/connectors/commands.ts +7 -6
  155. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  156. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  157. package/src/lib/server/connectors/connector-service.ts +453 -0
  158. package/src/lib/server/connectors/delivery.ts +17 -12
  159. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  160. package/src/lib/server/connectors/media.ts +1 -1
  161. package/src/lib/server/connectors/response-media.ts +1 -1
  162. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  163. package/src/lib/server/connectors/session.ts +9 -7
  164. package/src/lib/server/connectors/voice-note.ts +2 -1
  165. package/src/lib/server/context-manager.ts +20 -1
  166. package/src/lib/server/cost.ts +2 -3
  167. package/src/lib/server/credentials/credential-repository.ts +43 -4
  168. package/src/lib/server/credentials/credential-service.ts +112 -0
  169. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  170. package/src/lib/server/daemon/controller.ts +577 -0
  171. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  172. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  173. package/src/lib/server/daemon/types.ts +101 -0
  174. package/src/lib/server/embeddings.ts +3 -9
  175. package/src/lib/server/eval/agent-regression.ts +3 -2
  176. package/src/lib/server/eval/runner.ts +2 -2
  177. package/src/lib/server/execution-brief.test.ts +167 -0
  178. package/src/lib/server/execution-brief.ts +295 -0
  179. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  180. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  181. package/src/lib/server/execution-engine/index.ts +35 -0
  182. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  183. package/src/lib/server/execution-engine/types.ts +33 -0
  184. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  185. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  186. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  187. package/src/lib/server/messages/message-repository.ts +330 -0
  188. package/src/lib/server/missions/mission-service/core.ts +8 -6
  189. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  190. package/src/lib/server/openclaw/doctor.ts +1 -1
  191. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  192. package/src/lib/server/openclaw/gateway.ts +5 -14
  193. package/src/lib/server/openclaw/health.ts +3 -11
  194. package/src/lib/server/openclaw/sync.ts +8 -6
  195. package/src/lib/server/persistence/storage-context.ts +3 -0
  196. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  197. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  198. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  199. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  200. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  201. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  202. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  203. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  204. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  205. package/src/lib/server/protocols/protocol-types.ts +10 -7
  206. package/src/lib/server/provider-endpoint.ts +7 -12
  207. package/src/lib/server/provider-model-discovery.ts +2 -11
  208. package/src/lib/server/query-expansion.ts +5 -6
  209. package/src/lib/server/run-context.test.ts +365 -0
  210. package/src/lib/server/run-context.ts +367 -0
  211. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  212. package/src/lib/server/runtime/queue/core.ts +61 -190
  213. package/src/lib/server/runtime/run-ledger.ts +8 -0
  214. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  215. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  216. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  217. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  218. package/src/lib/server/service-result.ts +16 -0
  219. package/src/lib/server/session-note.ts +2 -3
  220. package/src/lib/server/session-reset-policy.ts +4 -3
  221. package/src/lib/server/session-tools/connector.ts +9 -6
  222. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  223. package/src/lib/server/session-tools/crud.ts +162 -10
  224. package/src/lib/server/session-tools/delegate.ts +1 -1
  225. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  226. package/src/lib/server/session-tools/memory.ts +6 -4
  227. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  228. package/src/lib/server/session-tools/session-info.ts +119 -12
  229. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  230. package/src/lib/server/session-tools/skills.ts +15 -15
  231. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  232. package/src/lib/server/session-tools/subagent.ts +125 -7
  233. package/src/lib/server/session-tools/team-context.ts +4 -3
  234. package/src/lib/server/session-tools/wallet.ts +0 -58
  235. package/src/lib/server/sessions/session-lineage.ts +55 -0
  236. package/src/lib/server/sessions/session-repository.ts +2 -2
  237. package/src/lib/server/skills/learned-skills.ts +24 -23
  238. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  239. package/src/lib/server/skills/skill-repository.ts +136 -13
  240. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  241. package/src/lib/server/storage-normalization.test.ts +44 -267
  242. package/src/lib/server/storage-normalization.ts +75 -0
  243. package/src/lib/server/storage.ts +19 -0
  244. package/src/lib/server/structured-extract.ts +3 -14
  245. package/src/lib/server/tasks/task-followups.ts +16 -11
  246. package/src/lib/server/tasks/task-result.test.ts +25 -29
  247. package/src/lib/server/tasks/task-result.ts +5 -9
  248. package/src/lib/server/tasks/task-route-service.ts +449 -0
  249. package/src/lib/server/text-normalization.ts +41 -0
  250. package/src/lib/server/tool-planning.ts +6 -42
  251. package/src/lib/server/upload-path.ts +5 -0
  252. package/src/lib/server/working-state/extraction.ts +614 -0
  253. package/src/lib/server/working-state/normalization.ts +866 -0
  254. package/src/lib/server/working-state/prompt.ts +60 -0
  255. package/src/lib/server/working-state/repository.ts +38 -0
  256. package/src/lib/server/working-state/service.test.ts +253 -0
  257. package/src/lib/server/working-state/service.ts +293 -0
  258. package/src/lib/validation/schemas.ts +1 -0
  259. package/src/lib/ws-client.ts +3 -3
  260. package/src/stores/slices/task-slice.ts +1 -4
  261. package/src/stores/use-chatroom-store.ts +2 -2
  262. package/src/types/index.ts +277 -12
@@ -2,10 +2,6 @@
2
2
 
3
3
  import { useCallback, useEffect, useMemo, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
- import { useChatroomStore } from '@/stores/use-chatroom-store'
6
- import { useWs } from '@/hooks/use-ws'
7
- import { useMountedRef } from '@/hooks/use-mounted-ref'
8
- import { api } from '@/lib/app/api-client'
9
5
  import type { Connector } from '@/types'
10
6
  import {
11
7
  ConnectorPlatformIcon,
@@ -16,6 +12,9 @@ import {
16
12
  import { AgentAvatar } from '@/components/agents/agent-avatar'
17
13
  import { PageLoader } from '@/components/ui/page-loader'
18
14
  import { StatusDot } from '@/components/ui/status-dot'
15
+ import { useConnectorsQuery, useConnectorActionMutation } from '@/features/connectors/queries'
16
+ import { useAgentsQuery } from '@/features/agents/queries'
17
+ import { useChatroomsQuery } from '@/features/chatrooms/queries'
19
18
 
20
19
  function relativeTime(ts: number): string {
21
20
  const diff = Date.now() - ts
@@ -45,33 +44,24 @@ function getConnectorGroup(connector: Connector): ConnectorGroup {
45
44
  }
46
45
 
47
46
  export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
48
- const connectors = useAppStore((s) => s.connectors)
49
- const loadConnectors = useAppStore((s) => s.loadConnectors)
50
47
  const setConnectorSheetOpen = useAppStore((s) => s.setConnectorSheetOpen)
51
48
  const setEditingConnectorId = useAppStore((s) => s.setEditingConnectorId)
52
- const agents = useAppStore((s) => s.agents)
53
- const loadAgents = useAppStore((s) => s.loadAgents)
54
- const chatrooms = useChatroomStore((s) => s.chatrooms)
55
- const loadChatrooms = useChatroomStore((s) => s.loadChatrooms)
49
+ const connectorsQuery = useConnectorsQuery()
50
+ const agentsQuery = useAgentsQuery()
51
+ const chatroomsQuery = useChatroomsQuery()
52
+ const connectorActionMutation = useConnectorActionMutation()
53
+ const connectors = useMemo(() => connectorsQuery.data ?? {}, [connectorsQuery.data])
54
+ const agents = agentsQuery.data ?? {}
55
+ const chatrooms = chatroomsQuery.data ?? {}
56
56
  const [toggling, setToggling] = useState<string | null>(null)
57
57
  const [reconnecting, setReconnecting] = useState<string | null>(null)
58
- const [loaded, setLoaded] = useState(false)
59
58
  const [error, setError] = useState<string | null>(null)
60
59
  const [groupFilter, setGroupFilter] = useState<'all' | ConnectorGroup>('all')
61
- const mountedRef = useMountedRef()
62
60
  const openConnector = useCallback((id: string | null) => {
63
61
  setEditingConnectorId(id)
64
62
  setConnectorSheetOpen(true)
65
63
  }, [setEditingConnectorId, setConnectorSheetOpen])
66
64
 
67
- const refresh = useCallback(async () => {
68
- await Promise.all([loadConnectors(), loadAgents(), loadChatrooms()])
69
- if (mountedRef.current) setLoaded(true)
70
- }, [loadConnectors, loadAgents, loadChatrooms, mountedRef])
71
-
72
- useEffect(() => { void refresh() }, [refresh])
73
- useWs('connectors', loadConnectors, 15_000)
74
-
75
65
  // Auto-clear error after 5s
76
66
  useEffect(() => {
77
67
  if (error) { const t = setTimeout(() => setError(null), 5000); return () => clearTimeout(t) }
@@ -80,38 +70,34 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
80
70
  const handleToggle = async (e: React.MouseEvent, c: Connector) => {
81
71
  e.stopPropagation()
82
72
  const action = c.status === 'running' ? 'stop' : 'start'
83
- if (mountedRef.current) {
84
- setToggling(c.id)
85
- setError(null)
86
- }
73
+ setToggling(c.id)
74
+ setError(null)
87
75
  try {
88
- await api('PUT', `/connectors/${c.id}`, { action })
89
- await refresh()
76
+ await connectorActionMutation.mutateAsync({ id: c.id, action })
90
77
  } catch (err: unknown) {
91
78
  const msg = err instanceof Error && err.message ? err.message : `Failed to ${action}`
92
- if (mountedRef.current) setError(msg)
93
- await refresh()
79
+ setError(msg)
94
80
  } finally {
95
- if (mountedRef.current) setToggling(null)
81
+ setToggling(null)
96
82
  }
97
83
  }
98
84
 
99
85
  const handleReconnect = async (e: React.MouseEvent, c: Connector) => {
100
86
  e.stopPropagation()
101
- if (mountedRef.current) {
102
- setReconnecting(c.id)
103
- setError(null)
104
- }
87
+ setReconnecting(c.id)
88
+ setError(null)
105
89
  try {
106
- try { await api('PUT', `/connectors/${c.id}`, { action: 'stop' }) } catch { /* may already be stopped */ }
107
- await api('PUT', `/connectors/${c.id}`, { action: 'start' })
108
- await refresh()
90
+ try {
91
+ await connectorActionMutation.mutateAsync({ id: c.id, action: 'stop' })
92
+ } catch {
93
+ // Connector may already be stopped.
94
+ }
95
+ await connectorActionMutation.mutateAsync({ id: c.id, action: 'start' })
109
96
  } catch (err: unknown) {
110
97
  const msg = err instanceof Error && err.message ? err.message : 'Failed to reconnect'
111
- if (mountedRef.current) setError(msg)
112
- await refresh()
98
+ setError(msg)
113
99
  } finally {
114
- if (mountedRef.current) setReconnecting(null)
100
+ setReconnecting(null)
115
101
  }
116
102
  }
117
103
 
@@ -158,7 +144,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
158
144
  },
159
145
  }
160
146
 
161
- if (!loaded) {
147
+ if (connectorsQuery.isPending || agentsQuery.isPending || chatroomsQuery.isPending) {
162
148
  return <PageLoader label="Loading connectors..." />
163
149
  }
164
150
 
@@ -1,15 +1,12 @@
1
1
  'use client'
2
2
 
3
- import { useState, useEffect, useCallback, useMemo } from 'react'
3
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { BottomSheet } from '@/components/shared/bottom-sheet'
6
- import { api } from '@/lib/app/api-client'
7
- import { useWs } from '@/hooks/use-ws'
8
6
  import { toast } from 'sonner'
9
7
  import type {
10
8
  Connector,
11
9
  ConnectorAccessMutationAction,
12
- ConnectorAccessMutationResponse,
13
10
  ConnectorAccessSnapshot,
14
11
  ConnectorPlatform,
15
12
  } from '@/types'
@@ -20,11 +17,25 @@ import { SheetFooter } from '@/components/shared/sheet-footer'
20
17
  import { SectionLabel } from '@/components/shared/section-label'
21
18
  import { HintTip } from '@/components/shared/hint-tip'
22
19
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
23
- import { useChatroomStore } from '@/stores/use-chatroom-store'
24
20
  import { ConnectorHealth } from '@/components/connectors/connector-health'
25
21
  import { ConnectorAccessPanel } from '@/components/connectors/connector-access-panel'
26
22
  import { AdvancedSettingsSection } from '@/components/shared/advanced-settings-section'
27
23
  import { errorMessage } from '@/lib/shared-utils'
24
+ import {
25
+ useConnectorsQuery,
26
+ useConnectorAccessMutation,
27
+ useConnectorAccessQuery,
28
+ useConnectorActionMutation,
29
+ useConnectorDoctorMutation,
30
+ useConnectorQuery,
31
+ useDeleteConnectorMutation,
32
+ useSaveConnectorMutation,
33
+ } from '@/features/connectors/queries'
34
+ import { useAgentsQuery } from '@/features/agents/queries'
35
+ import { useCredentialsQuery, useCreateCredentialMutation } from '@/features/credentials/queries'
36
+ import { useChatroomsQuery } from '@/features/chatrooms/queries'
37
+ import { useAppSettingsQuery } from '@/features/settings/queries'
38
+ import { useConnectorExtensionOptionsQuery } from '@/features/extensions/queries'
28
39
 
29
40
  /** Auto-detect URLs in text and make them clickable links that open in a new tab */
30
41
  function linkify(text: string) {
@@ -58,11 +69,6 @@ interface ConnectorDoctorPolicyPreview {
58
69
  typingIndicators?: boolean
59
70
  }
60
71
 
61
- interface ConnectorDoctorResponse {
62
- warnings?: string[]
63
- policy?: ConnectorDoctorPolicyPreview | null
64
- }
65
-
66
72
  interface ConnectorConfigOption {
67
73
  value: string
68
74
  label: string
@@ -538,16 +544,33 @@ const ACCESS_CONTROL_FIELDS: ConnectorConfigField[] = [
538
544
  export function ConnectorSheet() {
539
545
  const open = useAppStore((s) => s.connectorSheetOpen)
540
546
  const setOpen = useAppStore((s) => s.setConnectorSheetOpen)
541
- // ... (existing state)
542
- const [dynamicPlatforms, setDynamicPlatforms] = useState<Array<{ id: string; name: string; description?: string }>>([])
543
-
544
- useEffect(() => {
545
- if (open) {
546
- api<Array<{ id: string; name: string; description?: string }>>('GET', '/extensions/ui?type=connectors').then(list => {
547
- setDynamicPlatforms(list || [])
548
- }).catch(() => {})
549
- }
550
- }, [open])
547
+ const editingId = useAppStore((s) => s.editingConnectorId)
548
+ const setEditingId = useAppStore((s) => s.setEditingConnectorId)
549
+ const connectorsQuery = useConnectorsQuery({ enabled: open })
550
+ const connectorDetailQuery = useConnectorQuery(editingId, {
551
+ enabled: open && !!editingId,
552
+ refetchInterval: open && editingId ? 2_000 : false,
553
+ })
554
+ const agentsQuery = useAgentsQuery({ enabled: open })
555
+ const credentialsQuery = useCredentialsQuery({ enabled: open })
556
+ const chatroomsQuery = useChatroomsQuery({ enabled: open })
557
+ const appSettingsQuery = useAppSettingsQuery({ enabled: open })
558
+ const connectorExtensionOptionsQuery = useConnectorExtensionOptionsQuery({ enabled: open })
559
+ const connectorDoctorMutation = useConnectorDoctorMutation()
560
+ const connectorAccessMutation = useConnectorAccessMutation(editingId)
561
+ const connectorAccessQuery = useConnectorAccessQuery(editingId, { enabled: open && !!editingId })
562
+ const saveConnectorMutation = useSaveConnectorMutation()
563
+ const connectorActionMutation = useConnectorActionMutation()
564
+ const deleteConnectorMutation = useDeleteConnectorMutation()
565
+ const createCredentialMutation = useCreateCredentialMutation()
566
+ const initializedKeyRef = useRef<string | null>(null)
567
+
568
+ const dynamicPlatforms = useMemo(() => connectorExtensionOptionsQuery.data ?? [], [connectorExtensionOptionsQuery.data])
569
+ const connectors = connectorsQuery.data ?? {}
570
+ const agents = agentsQuery.data ?? {}
571
+ const appSettings = appSettingsQuery.data ?? {}
572
+ const credentials = credentialsQuery.data ?? {}
573
+ const chatrooms = chatroomsQuery.data ?? {}
551
574
 
552
575
  const ALL_PLATFORMS = useMemo(() => {
553
576
  const extensionPlatforms = dynamicPlatforms.map(p => ({
@@ -561,18 +584,6 @@ export function ConnectorSheet() {
561
584
  }))
562
585
  return [...PLATFORMS, ...extensionPlatforms]
563
586
  }, [dynamicPlatforms])
564
- const editingId = useAppStore((s) => s.editingConnectorId)
565
- const setEditingId = useAppStore((s) => s.setEditingConnectorId)
566
- const connectors = useAppStore((s) => s.connectors)
567
- const loadConnectors = useAppStore((s) => s.loadConnectors)
568
- const agents = useAppStore((s) => s.agents)
569
- const appSettings = useAppStore((s) => s.appSettings)
570
- const credentials = useAppStore((s) => s.credentials)
571
- const loadAgents = useAppStore((s) => s.loadAgents)
572
- const loadCredentials = useAppStore((s) => s.loadCredentials)
573
-
574
- const chatrooms = useChatroomStore((s) => s.chatrooms)
575
- const loadChatrooms = useChatroomStore((s) => s.loadChatrooms)
576
587
 
577
588
  const [name, setName] = useState('')
578
589
  const [platform, setPlatform] = useState<ConnectorPlatform>('discord')
@@ -584,9 +595,6 @@ export function ConnectorSheet() {
584
595
  const [saving, setSaving] = useState(false)
585
596
  const [actionLoading, setActionLoading] = useState(false)
586
597
  const [showSetup, setShowSetup] = useState(false)
587
- const [qrDataUrl, setQrDataUrl] = useState<string | null>(null)
588
- const [waAuthenticated, setWaAuthenticated] = useState(false)
589
- const [waHasCreds, setWaHasCreds] = useState(false)
590
598
  const [waConnecting, setWaConnecting] = useState(false)
591
599
  const [showNewCred, setShowNewCred] = useState(false)
592
600
  const [newCredName, setNewCredName] = useState('')
@@ -595,8 +603,6 @@ export function ConnectorSheet() {
595
603
  const [doctorWarnings, setDoctorWarnings] = useState<string[]>([])
596
604
  const [doctorPolicy, setDoctorPolicy] = useState<ConnectorDoctorPolicyPreview | null>(null)
597
605
  const [doctorLoading, setDoctorLoading] = useState(false)
598
- const [accessSnapshot, setAccessSnapshot] = useState<ConnectorAccessSnapshot | null>(null)
599
- const [accessLoading, setAccessLoading] = useState(false)
600
606
  const [accessError, setAccessError] = useState<string | null>(null)
601
607
  const [accessPending, setAccessPending] = useState(false)
602
608
  const [confirmDelete, setConfirmDelete] = useState(false)
@@ -611,21 +617,22 @@ export function ConnectorSheet() {
611
617
  const supportsAccessControls = platform === 'whatsapp' || platform === 'bluebubbles'
612
618
 
613
619
  const editing = editingId ? connectors[editingId] as Connector | undefined : null
614
-
615
- useEffect(() => {
616
- if (open) {
617
- loadAgents()
618
- loadCredentials()
619
- loadChatrooms()
620
- setShowSetup(false)
621
- setShowAdvancedSettings(false)
622
- }
623
- // eslint-disable-next-line react-hooks/exhaustive-deps
624
- }, [open])
620
+ const runtimeConnector = connectorDetailQuery.data ?? editing ?? null
621
+ const accessSnapshot = connectorAccessQuery.data ?? null
622
+ const accessLoading = connectorAccessQuery.isPending
625
623
 
626
624
  // Sync form fields when editing connector changes (by ID, not reference)
627
625
  const editingIdRef = editing?.id ?? null
628
626
  useEffect(() => {
627
+ if (!open) {
628
+ initializedKeyRef.current = null
629
+ return
630
+ }
631
+ if (editingId && !editing) return
632
+ const initKey = editing?.id || '__new__'
633
+ if (initializedKeyRef.current === initKey) return
634
+ setShowSetup(false)
635
+ setShowAdvancedSettings(false)
629
636
  if (editing) {
630
637
  setName(editing.name)
631
638
  setPlatform(editing.platform)
@@ -642,43 +649,22 @@ export function ConnectorSheet() {
642
649
  setChatroomId('')
643
650
  setCredentialId('')
644
651
  setConfig({})
652
+ setShowNewCred(false)
653
+ setNewCredName('')
654
+ setNewCredValue('')
645
655
  }
646
- setQrDataUrl(null)
647
- setWaAuthenticated(false)
648
- setWaHasCreds(false)
649
656
  setWaConnecting(false)
650
- }, [editing, editingIdRef, open])
657
+ initializedKeyRef.current = initKey
658
+ }, [editing, editingId, editingIdRef, open])
651
659
 
652
660
  // Poll for QR code when WhatsApp connector is running or connecting
653
- const isWaRunning = editing?.platform === 'whatsapp' && (editing?.status === 'running' || waConnecting)
654
- const pollWaStatus = useCallback(async () => {
655
- if (!editing) return
656
- try {
657
- const data = await api<Record<string, unknown>>('GET', `/connectors/${editing.id}`)
658
- setQrDataUrl((data.qrDataUrl as string | null) || null)
659
- setWaAuthenticated((data.authenticated as boolean) ?? false)
660
- setWaHasCreds((data.hasCredentials as boolean) ?? false)
661
- if (data.status === 'running' && editing.status !== 'running') {
662
- const store = useAppStore.getState()
663
- const updated = { ...store.connectors }
664
- if (updated[editing.id]) {
665
- updated[editing.id] = { ...updated[editing.id], status: 'running' as const }
666
- useAppStore.setState({ connectors: updated })
667
- }
668
- }
669
- } catch { /* ignore */ }
670
- }, [editing])
671
-
672
- useEffect(() => {
673
- if (editing && isWaRunning) pollWaStatus()
674
- }, [editing, editing?.id, isWaRunning, pollWaStatus])
675
-
676
- useWs('connectors', pollWaStatus, isWaRunning ? 2000 : undefined)
677
-
661
+ const qrDataUrl = runtimeConnector?.qrDataUrl ?? null
662
+ const waAuthenticated = runtimeConnector?.authenticated === true
663
+ const waHasCreds = runtimeConnector?.hasCredentials === true
678
664
  const loadDoctorPreview = useCallback(async () => {
679
665
  setDoctorLoading(true)
680
666
  try {
681
- const data = await api<ConnectorDoctorResponse>('POST', '/connectors/doctor', {
667
+ const data = await connectorDoctorMutation.mutateAsync({
682
668
  id: editing?.id || null,
683
669
  name,
684
670
  platform,
@@ -695,6 +681,7 @@ export function ConnectorSheet() {
695
681
  } finally {
696
682
  setDoctorLoading(false)
697
683
  }
684
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- connectorDoctorMutation omitted: mutateAsync is stable at runtime but the wrapper isn't reference-stable, causing an infinite render loop
698
685
  }, [editing?.id, name, platform, routeMode, agentId, chatroomId, credentialId, config])
699
686
 
700
687
  useEffect(() => {
@@ -702,8 +689,6 @@ export function ConnectorSheet() {
702
689
  setDoctorWarnings([])
703
690
  setDoctorPolicy(null)
704
691
  setDoctorLoading(false)
705
- setAccessSnapshot(null)
706
- setAccessLoading(false)
707
692
  setAccessError(null)
708
693
  setAccessPending(false)
709
694
  setConfirmDelete(false)
@@ -732,35 +717,6 @@ export function ConnectorSheet() {
732
717
  })
733
718
  }, [])
734
719
 
735
- const loadAccessSnapshot = useCallback(async () => {
736
- if (!editing?.id) {
737
- setAccessSnapshot(null)
738
- setAccessError(null)
739
- return
740
- }
741
- setAccessLoading(true)
742
- setAccessError(null)
743
- try {
744
- const snapshot = await api<ConnectorAccessSnapshot>('GET', `/connectors/${editing.id}/access`)
745
- setAccessSnapshot(snapshot)
746
- } catch (err: unknown) {
747
- setAccessSnapshot(null)
748
- setAccessError(errorMessage(err))
749
- } finally {
750
- setAccessLoading(false)
751
- }
752
- }, [editing?.id])
753
-
754
- useEffect(() => {
755
- if (!open || !editing?.id) {
756
- setAccessSnapshot(null)
757
- setAccessLoading(false)
758
- setAccessError(null)
759
- return
760
- }
761
- void loadAccessSnapshot()
762
- }, [editing?.id, loadAccessSnapshot, open])
763
-
764
720
  const handleAccessAction = useCallback(async (
765
721
  action: ConnectorAccessMutationAction,
766
722
  payload?: {
@@ -774,16 +730,14 @@ export function ConnectorSheet() {
774
730
  setAccessPending(true)
775
731
  setAccessError(null)
776
732
  try {
777
- const result = await api<ConnectorAccessMutationResponse>('PUT', `/connectors/${editing.id}/access`, {
733
+ const result = await connectorAccessMutation.mutateAsync({
778
734
  action,
779
735
  senderId: payload?.senderId || null,
780
736
  senderIdAlt: payload?.senderIdAlt || null,
781
737
  code: payload?.code || null,
782
738
  dmAddressingMode: payload?.dmAddressingMode || null,
783
739
  })
784
- setAccessSnapshot(result.snapshot)
785
740
  applySnapshotToConfig(result.snapshot)
786
- await loadConnectors()
787
741
  } catch (err: unknown) {
788
742
  const message = errorMessage(err)
789
743
  setAccessError(message)
@@ -791,7 +745,7 @@ export function ConnectorSheet() {
791
745
  } finally {
792
746
  setAccessPending(false)
793
747
  }
794
- }, [applySnapshotToConfig, editing?.id, loadConnectors])
748
+ }, [applySnapshotToConfig, connectorAccessMutation, editing?.id])
795
749
 
796
750
  const handleSave = async () => {
797
751
  const hasTarget = routeMode === 'agent' ? !!agentId : !!chatroomId
@@ -801,12 +755,16 @@ export function ConnectorSheet() {
801
755
  ? { agentId, chatroomId: null }
802
756
  : { agentId: null, chatroomId }
803
757
  try {
804
- if (editing) {
805
- await api('PUT', `/connectors/${editing.id}`, { name, ...routePayload, credentialId: credentialId || null, config })
806
- } else {
807
- await api('POST', '/connectors', { name: name || `${platformConfig?.label} Bot`, platform, ...routePayload, credentialId: credentialId || null, config })
808
- }
809
- await loadConnectors()
758
+ await saveConnectorMutation.mutateAsync({
759
+ id: editing?.id,
760
+ payload: {
761
+ name: name || `${platformConfig?.label} Bot`,
762
+ platform,
763
+ ...routePayload,
764
+ credentialId: credentialId || null,
765
+ config,
766
+ },
767
+ })
810
768
  setOpen(false)
811
769
  setEditingId(null)
812
770
  } catch (err: unknown) {
@@ -820,19 +778,12 @@ export function ConnectorSheet() {
820
778
  if (!editing) return
821
779
  setActionLoading(true)
822
780
  try {
823
- await api('PUT', `/connectors/${editing.id}`, { action })
781
+ await connectorActionMutation.mutateAsync({ id: editing.id, action })
824
782
  if (action === 'start' && editing.platform === 'whatsapp') {
825
783
  setWaConnecting(true)
826
- setWaAuthenticated(false)
827
- setQrDataUrl(null)
828
- // Don't reset waHasCreds — it will be updated by poll
829
784
  } else if (action === 'stop') {
830
785
  setWaConnecting(false)
831
- setWaAuthenticated(false)
832
- setWaHasCreds(false)
833
- setQrDataUrl(null)
834
786
  }
835
- await loadConnectors()
836
787
  } catch (err: unknown) {
837
788
  setWaConnecting(false)
838
789
  toast.error(`Failed to ${action}: ${errorMessage(err)}`)
@@ -845,8 +796,7 @@ export function ConnectorSheet() {
845
796
  if (!editing) return
846
797
  setDeleting(true)
847
798
  try {
848
- await api('DELETE', `/connectors/${editing.id}`)
849
- await loadConnectors()
799
+ await deleteConnectorMutation.mutateAsync(editing.id)
850
800
  setConfirmDelete(false)
851
801
  setOpen(false)
852
802
  setEditingId(null)
@@ -861,13 +811,9 @@ export function ConnectorSheet() {
861
811
  if (!editing) return
862
812
  setActionLoading(true)
863
813
  try {
864
- await api('PUT', `/connectors/${editing.id}`, { action: 'repair' })
865
- setWaAuthenticated(false)
866
- setWaHasCreds(false)
867
- setQrDataUrl(null)
814
+ await connectorActionMutation.mutateAsync({ id: editing.id, action: 'repair' })
868
815
  setWaConnecting(true)
869
816
  setConfirmWhatsAppAction(null)
870
- await loadConnectors()
871
817
  } catch (err: unknown) {
872
818
  toast.error(`Failed to ${mode === 'unlink' ? 'unlink' : 're-pair'}: ${errorMessage(err)}`)
873
819
  } finally {
@@ -888,9 +834,9 @@ export function ConnectorSheet() {
888
834
  if (advancedPlatformFields.some((field) => hasConfiguredValue(field.key))) badges.push('Platform overrides')
889
835
  if (advancedAccessFields.some((field) => hasConfiguredValue(field.key)) || accessSnapshot?.pendingPairingRequests.length || accessSnapshot?.storedAllowedSenderIds.length) badges.push('Access lists')
890
836
  if (COMMON_CONFIG_FIELDS.some((field) => hasConfiguredValue(field.key))) badges.push('Runtime policy')
891
- if (doctorWarnings.length > 0 || editing?.lastError) badges.push('Diagnostics')
837
+ if (doctorWarnings.length > 0 || runtimeConnector?.lastError) badges.push('Diagnostics')
892
838
  return Array.from(new Set(badges))
893
- }, [accessSnapshot?.pendingPairingRequests.length, accessSnapshot?.storedAllowedSenderIds.length, advancedAccessFields, advancedPlatformFields, doctorWarnings.length, editing?.lastError, hasConfiguredValue])
839
+ }, [accessSnapshot?.pendingPairingRequests.length, accessSnapshot?.storedAllowedSenderIds.length, advancedAccessFields, advancedPlatformFields, doctorWarnings.length, hasConfiguredValue, runtimeConnector?.lastError])
894
840
  const configuredAdvancedCount = useMemo(() => {
895
841
  const advancedKeys = new Set([
896
842
  ...advancedPlatformFields.map((field) => field.key),
@@ -1063,10 +1009,10 @@ export function ConnectorSheet() {
1063
1009
  <div className="text-[14px] font-600 text-text">{platformConfig.label}</div>
1064
1010
  <div className="flex items-center gap-2 mt-0.5">
1065
1011
  <span className={`w-2 h-2 rounded-full ${
1066
- editing.status === 'running' ? 'bg-green-400 shadow-[0_0_6px_rgba(74,222,128,0.5)]' :
1067
- editing.status === 'error' ? 'bg-red-400' : 'bg-white/20'
1012
+ runtimeConnector?.status === 'running' ? 'bg-green-400 shadow-[0_0_6px_rgba(74,222,128,0.5)]' :
1013
+ runtimeConnector?.status === 'error' ? 'bg-red-400' : 'bg-white/20'
1068
1014
  }`} />
1069
- <span className="text-[12px] text-text-3 capitalize">{editing.status}</span>
1015
+ <span className="text-[12px] text-text-3 capitalize">{runtimeConnector?.status || editing.status}</span>
1070
1016
  </div>
1071
1017
  </div>
1072
1018
  </div>
@@ -1233,12 +1179,12 @@ export function ConnectorSheet() {
1233
1179
  onClick={async () => {
1234
1180
  setSavingCred(true)
1235
1181
  try {
1236
- const cred = await api<{ id: string }>('POST', '/credentials', {
1182
+ const cred = await createCredentialMutation.mutateAsync({
1237
1183
  provider: platform,
1238
1184
  name: newCredName.trim() || `${platformConfig.label} Bot Token`,
1239
1185
  apiKey: newCredValue.trim(),
1240
1186
  })
1241
- await loadCredentials()
1187
+ await credentialsQuery.refetch()
1242
1188
  setCredentialId(cred.id)
1243
1189
  setShowNewCred(false)
1244
1190
  setNewCredName('')
@@ -1288,7 +1234,7 @@ export function ConnectorSheet() {
1288
1234
 
1289
1235
  {/* Start/Stop controls for editing */}
1290
1236
  {editing && (() => {
1291
- const effectiveRunning = editing.status === 'running' || waConnecting
1237
+ const effectiveRunning = runtimeConnector?.status === 'running' || waConnecting
1292
1238
  return (
1293
1239
  <div className="mb-6 p-4 rounded-[14px] border border-white/[0.06] bg-white/[0.01]">
1294
1240
  <div className="flex items-center justify-between">
@@ -1297,10 +1243,10 @@ export function ConnectorSheet() {
1297
1243
  <div className="text-[12px] text-text-3 mt-0.5 flex items-center gap-1.5">
1298
1244
  <span className={`w-2 h-2 rounded-full inline-block ${
1299
1245
  effectiveRunning ? 'bg-green-400 shadow-[0_0_6px_rgba(74,222,128,0.5)]' :
1300
- editing.status === 'error' ? 'bg-red-400' : 'bg-white/20'
1246
+ runtimeConnector?.status === 'error' ? 'bg-red-400' : 'bg-white/20'
1301
1247
  }`} />
1302
1248
  {effectiveRunning ? (waAuthenticated ? 'Connected and listening' : 'Connecting...') :
1303
- editing.status === 'error' ? 'Error — see below' : 'Not connected'}
1249
+ runtimeConnector?.status === 'error' ? 'Error — see below' : 'Not connected'}
1304
1250
  </div>
1305
1251
  </div>
1306
1252
  {effectiveRunning ? (
@@ -1328,7 +1274,7 @@ export function ConnectorSheet() {
1328
1274
  })()}
1329
1275
 
1330
1276
  {/* WhatsApp QR code */}
1331
- {editing && editing.platform === 'whatsapp' && (editing.status === 'running' || waConnecting) && qrDataUrl && (
1277
+ {editing && platform === 'whatsapp' && (runtimeConnector?.status === 'running' || waConnecting) && qrDataUrl && (
1332
1278
  <div className="mb-6 p-5 rounded-[14px] border border-white/[0.06] bg-white/[0.01] text-center"
1333
1279
  style={{ animation: 'fade-in 0.3s ease-out' }}>
1334
1280
  <div className="text-[13px] font-600 text-text-2 mb-1">Scan with WhatsApp</div>
@@ -1344,7 +1290,7 @@ export function ConnectorSheet() {
1344
1290
  )}
1345
1291
 
1346
1292
  {/* WhatsApp connected (authenticated, no QR) */}
1347
- {editing && editing.platform === 'whatsapp' && (editing.status === 'running' || waConnecting) && !qrDataUrl && waAuthenticated && (
1293
+ {editing && platform === 'whatsapp' && (runtimeConnector?.status === 'running' || waConnecting) && !qrDataUrl && waAuthenticated && (
1348
1294
  <div className="mb-6 p-5 rounded-[14px] border border-white/[0.06] bg-white/[0.01] text-center">
1349
1295
  <div className="text-[13px] font-600 text-green-400 mb-1">Connected</div>
1350
1296
  <p className="text-[11px] text-text-3 mb-3">WhatsApp is paired and listening for messages</p>
@@ -1360,7 +1306,7 @@ export function ConnectorSheet() {
1360
1306
  )}
1361
1307
 
1362
1308
  {/* WhatsApp waiting for QR / reconnecting (not yet authenticated, no QR yet) */}
1363
- {editing && editing.platform === 'whatsapp' && (editing.status === 'running' || waConnecting) && !qrDataUrl && !waAuthenticated && (
1309
+ {editing && platform === 'whatsapp' && (runtimeConnector?.status === 'running' || waConnecting) && !qrDataUrl && !waAuthenticated && (
1364
1310
  <div className="mb-6 p-5 rounded-[14px] border border-white/[0.06] bg-white/[0.01] text-center">
1365
1311
  <div className="flex items-center justify-center gap-2 mb-1">
1366
1312
  <span className="w-3 h-3 rounded-full border-2 border-blue-500 border-t-transparent animate-spin" />
@@ -1387,10 +1333,10 @@ export function ConnectorSheet() {
1387
1333
  )}
1388
1334
 
1389
1335
  {/* Error display */}
1390
- {editing?.lastError && (
1336
+ {runtimeConnector?.lastError && (
1391
1337
  <div className="mb-6 p-4 rounded-[14px] bg-red-500/[0.06] border border-red-500/15">
1392
1338
  <div className="text-[12px] font-600 text-red-400 mb-1">Error</div>
1393
- <div className="text-[12px] text-red-400/70 leading-[1.5] font-mono">{editing.lastError}</div>
1339
+ <div className="text-[12px] text-red-400/70 leading-[1.5] font-mono">{runtimeConnector.lastError}</div>
1394
1340
  </div>
1395
1341
  )}
1396
1342