@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
@@ -1,12 +1,20 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState } from 'react'
3
+ import { useEffect, useMemo, useRef, useState } from 'react'
4
4
  import { useRouter } from 'next/navigation'
5
5
  import ReactMarkdown from 'react-markdown'
6
6
  import remarkGfm from 'remark-gfm'
7
7
  import { useAppStore } from '@/stores/use-app-store'
8
- import { api } from '@/lib/app/api-client'
9
- import { createTask, updateTask, archiveTask, unarchiveTask } from '@/lib/tasks'
8
+ import { useAgentsQuery } from '@/features/agents/queries'
9
+ import {
10
+ useAppendTaskCommentMutation,
11
+ useCreateTaskMutation,
12
+ useTasksQuery,
13
+ useUpdateTaskMutation,
14
+ } from '@/features/tasks/queries'
15
+ import { useProjectsQuery } from '@/features/projects/queries'
16
+ import { useAppSettingsQuery } from '@/features/settings/queries'
17
+ import { useProtocolRunsQuery } from '@/features/protocols/queries'
10
18
  import { getMissionPath } from '@/lib/app/navigation'
11
19
  import { BottomSheet } from '@/components/shared/bottom-sheet'
12
20
  import { AgentPickerList } from '@/components/shared/agent-picker-list'
@@ -43,20 +51,17 @@ export function TaskSheet() {
43
51
  const setOpen = useAppStore((s) => s.setTaskSheetOpen)
44
52
  const editingId = useAppStore((s) => s.editingTaskId)
45
53
  const setEditingId = useAppStore((s) => s.setEditingTaskId)
46
- const tasks = useAppStore((s) => s.tasks)
47
- const loadTasks = useAppStore((s) => s.loadTasks)
48
- const agents = useAppStore((s) => s.agents)
49
- const loadAgents = useAppStore((s) => s.loadAgents)
50
-
51
- const projects = useAppStore((s) => s.projects)
52
- const loadProjects = useAppStore((s) => s.loadProjects)
53
54
  const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
54
55
 
55
56
  const viewOnly = useAppStore((s) => s.taskSheetViewOnly)
56
57
  const setViewOnly = useAppStore((s) => s.setTaskSheetViewOnly)
57
-
58
- const appSettings = useAppStore((s) => s.appSettings)
59
- const loadSettings = useAppStore((s) => s.loadSettings)
58
+ const { data: tasks = {}, isLoading: tasksLoading } = useTasksQuery({ includeArchived: true, enabled: open })
59
+ const { data: agents = {}, isLoading: agentsLoading } = useAgentsQuery({ enabled: open })
60
+ const { data: projects = {}, isLoading: projectsLoading } = useProjectsQuery({ enabled: open })
61
+ const { data: appSettings = {}, isLoading: settingsLoading } = useAppSettingsQuery({ enabled: open })
62
+ const createTaskMutation = useCreateTaskMutation()
63
+ const updateTaskMutation = useUpdateTaskMutation()
64
+ const appendCommentMutation = useAppendTaskCommentMutation()
60
65
 
61
66
  const [title, setTitle] = useState('')
62
67
  const [description, setDescription] = useState('')
@@ -82,69 +87,96 @@ export function TaskSheet() {
82
87
  const [qualityGateRequireArtifact, setQualityGateRequireArtifact] = useState(false)
83
88
  const [qualityGateRequireReport, setQualityGateRequireReport] = useState(false)
84
89
  const [structuredSessionOpen, setStructuredSessionOpen] = useState(false)
85
- const [activeStructuredRunId, setActiveStructuredRunId] = useState<string | null>(null)
90
+ const formInitRef = useRef<string | null>(null)
86
91
 
87
92
  const editing = editingId ? tasks[editingId] : null
88
- const agentList = Object.values(agents).sort((a, b) => a.name.localeCompare(b.name))
93
+ const agentList = useMemo(
94
+ () => Object.values(agents).sort((a, b) => a.name.localeCompare(b.name)),
95
+ [agents],
96
+ )
97
+ const { data: linkedProtocolRuns = [] } = useProtocolRunsQuery({
98
+ taskId: editing?.id || null,
99
+ limit: 6,
100
+ enabled: open && !!editing?.id,
101
+ })
102
+ const activeStructuredRunId =
103
+ linkedProtocolRuns.find((run) => !['completed', 'failed', 'cancelled', 'archived'].includes(run.status))?.id || null
89
104
 
90
105
  useEffect(() => {
91
- if (open) {
92
- loadAgents()
93
- loadProjects()
94
- loadSettings()
95
- const defaultGateEnabled = appSettings.taskQualityGateEnabled ?? true
96
- const defaultGateMinResult = normalizeGateNumber(appSettings.taskQualityGateMinResultChars, 80, 10, 2000)
97
- const defaultGateMinEvidence = normalizeGateNumber(appSettings.taskQualityGateMinEvidenceItems, 2, 0, 8)
98
- const defaultGateRequireVerification = appSettings.taskQualityGateRequireVerification ?? false
99
- const defaultGateRequireArtifact = appSettings.taskQualityGateRequireArtifact ?? false
100
- const defaultGateRequireReport = appSettings.taskQualityGateRequireReport ?? false
101
- if (editing) {
102
- setTitle(editing.title)
103
- setDescription(editing.description)
104
- setAgentId(editing.agentId)
105
- setProjectId(editing.projectId || '')
106
- setImages(editing.images || [])
107
- setCwd(editing.cwd || '')
108
- setFile(editing.file || null)
109
- setTags(editing.tags || [])
110
- setBlockedBy(editing.blockedBy || [])
111
- setDepSearch('')
112
- setDepError(null)
113
- setDueAt(editing.dueAt ? new Date(editing.dueAt).toISOString().slice(0, 10) : '')
114
- setCustomFields(editing.customFields || {})
115
- setPriority(editing.priority || '')
116
- const gate = (editing.qualityGate || null) as TaskQualityGateConfig | null
117
- setQualityGateEnabled(gate?.enabled ?? defaultGateEnabled)
118
- setQualityGateMinResultChars(normalizeGateNumber(gate?.minResultChars, defaultGateMinResult, 10, 2000))
119
- setQualityGateMinEvidenceItems(normalizeGateNumber(gate?.minEvidenceItems, defaultGateMinEvidence, 0, 8))
120
- setQualityGateRequireVerification(gate?.requireVerification ?? defaultGateRequireVerification)
121
- setQualityGateRequireArtifact(gate?.requireArtifact ?? defaultGateRequireArtifact)
122
- setQualityGateRequireReport(gate?.requireReport ?? defaultGateRequireReport)
123
- } else {
124
- setTitle('')
125
- setDescription('')
126
- setAgentId(agentList[0]?.id || '')
127
- setProjectId(activeProjectFilter || '')
128
- setImages([])
129
- setCwd('')
130
- setFile(null)
131
- setTags([])
132
- setBlockedBy([])
133
- setDepSearch('')
134
- setDepError(null)
135
- setDueAt('')
136
- setCustomFields({})
137
- setPriority('')
138
- setQualityGateEnabled(defaultGateEnabled)
139
- setQualityGateMinResultChars(defaultGateMinResult)
140
- setQualityGateMinEvidenceItems(defaultGateMinEvidence)
141
- setQualityGateRequireVerification(defaultGateRequireVerification)
142
- setQualityGateRequireArtifact(defaultGateRequireArtifact)
143
- setQualityGateRequireReport(defaultGateRequireReport)
144
- }
106
+ if (!open) return
107
+ if (tasksLoading || agentsLoading || projectsLoading || settingsLoading) return
108
+
109
+ const initKey = editingId ?? '__new__'
110
+ if (formInitRef.current === initKey) return
111
+
112
+ const defaultGateEnabled = appSettings.taskQualityGateEnabled ?? true
113
+ const defaultGateMinResult = normalizeGateNumber(appSettings.taskQualityGateMinResultChars, 80, 10, 2000)
114
+ const defaultGateMinEvidence = normalizeGateNumber(appSettings.taskQualityGateMinEvidenceItems, 2, 0, 8)
115
+ const defaultGateRequireVerification = appSettings.taskQualityGateRequireVerification ?? false
116
+ const defaultGateRequireArtifact = appSettings.taskQualityGateRequireArtifact ?? false
117
+ const defaultGateRequireReport = appSettings.taskQualityGateRequireReport ?? false
118
+
119
+ if (editingId && !editing) return
120
+
121
+ if (editing) {
122
+ setTitle(editing.title)
123
+ setDescription(editing.description)
124
+ setAgentId(editing.agentId)
125
+ setProjectId(editing.projectId || '')
126
+ setImages(editing.images || [])
127
+ setCwd(editing.cwd || '')
128
+ setFile(editing.file || null)
129
+ setTags(editing.tags || [])
130
+ setBlockedBy(editing.blockedBy || [])
131
+ setDepSearch('')
132
+ setDepError(null)
133
+ setDueAt(editing.dueAt ? new Date(editing.dueAt).toISOString().slice(0, 10) : '')
134
+ setCustomFields(editing.customFields || {})
135
+ setPriority(editing.priority || '')
136
+ const gate = (editing.qualityGate || null) as TaskQualityGateConfig | null
137
+ setQualityGateEnabled(gate?.enabled ?? defaultGateEnabled)
138
+ setQualityGateMinResultChars(normalizeGateNumber(gate?.minResultChars, defaultGateMinResult, 10, 2000))
139
+ setQualityGateMinEvidenceItems(normalizeGateNumber(gate?.minEvidenceItems, defaultGateMinEvidence, 0, 8))
140
+ setQualityGateRequireVerification(gate?.requireVerification ?? defaultGateRequireVerification)
141
+ setQualityGateRequireArtifact(gate?.requireArtifact ?? defaultGateRequireArtifact)
142
+ setQualityGateRequireReport(gate?.requireReport ?? defaultGateRequireReport)
143
+ formInitRef.current = initKey
144
+ return
145
145
  }
146
- // eslint-disable-next-line react-hooks/exhaustive-deps
147
- }, [open, editingId])
146
+
147
+ setTitle('')
148
+ setDescription('')
149
+ setAgentId(agentList[0]?.id || '')
150
+ setProjectId(activeProjectFilter || '')
151
+ setImages([])
152
+ setCwd('')
153
+ setFile(null)
154
+ setTags([])
155
+ setBlockedBy([])
156
+ setDepSearch('')
157
+ setDepError(null)
158
+ setDueAt('')
159
+ setCustomFields({})
160
+ setPriority('')
161
+ setQualityGateEnabled(defaultGateEnabled)
162
+ setQualityGateMinResultChars(defaultGateMinResult)
163
+ setQualityGateMinEvidenceItems(defaultGateMinEvidence)
164
+ setQualityGateRequireVerification(defaultGateRequireVerification)
165
+ setQualityGateRequireArtifact(defaultGateRequireArtifact)
166
+ setQualityGateRequireReport(defaultGateRequireReport)
167
+ formInitRef.current = initKey
168
+ }, [
169
+ activeProjectFilter,
170
+ agentList,
171
+ agentsLoading,
172
+ appSettings,
173
+ editing,
174
+ editingId,
175
+ open,
176
+ projectsLoading,
177
+ settingsLoading,
178
+ tasksLoading,
179
+ ])
148
180
 
149
181
  // Update default agent when agents load (only if no agent selected yet)
150
182
  useEffect(() => {
@@ -153,27 +185,8 @@ export function TaskSheet() {
153
185
  }
154
186
  }, [open, editing, agentId, agentList])
155
187
 
156
- useEffect(() => {
157
- if (!editing?.id || !open) {
158
- setActiveStructuredRunId(null)
159
- return
160
- }
161
- let cancelled = false
162
- void api<Array<{ id: string; status: string }>>('GET', `/protocols/runs?taskId=${encodeURIComponent(editing.id)}&limit=6`)
163
- .then((runs) => {
164
- if (cancelled) return
165
- const active = (Array.isArray(runs) ? runs : []).find((run) => !['completed', 'failed', 'cancelled', 'archived'].includes(run.status))
166
- setActiveStructuredRunId(active?.id || null)
167
- })
168
- .catch(() => {
169
- if (!cancelled) setActiveStructuredRunId(null)
170
- })
171
- return () => {
172
- cancelled = true
173
- }
174
- }, [editing?.id, open])
175
-
176
188
  const onClose = () => {
189
+ formInitRef.current = null
177
190
  setOpen(false)
178
191
  setEditingId(null)
179
192
  }
@@ -202,13 +215,13 @@ export function TaskSheet() {
202
215
  } as Partial<BoardTask> & { title: string; description: string; agentId: string }
203
216
  try {
204
217
  if (editing) {
205
- const res = await updateTask(editing.id, payload)
218
+ const res = await updateTaskMutation.mutateAsync({ id: editing.id, patch: payload })
206
219
  if (res && typeof res === 'object' && 'error' in res) {
207
220
  setDepError(String((res as unknown as Record<string, unknown>).error))
208
221
  return
209
222
  }
210
223
  } else {
211
- const res = await createTask(payload)
224
+ const res = await createTaskMutation.mutateAsync(payload)
212
225
  if (res && typeof res === 'object' && 'error' in res) {
213
226
  setDepError(String((res as unknown as Record<string, unknown>).error))
214
227
  return
@@ -219,7 +232,6 @@ export function TaskSheet() {
219
232
  return
220
233
  }
221
234
  setDepError(null)
222
- await loadTasks()
223
235
  onClose()
224
236
  }
225
237
 
@@ -244,24 +256,21 @@ export function TaskSheet() {
244
256
 
245
257
  const handleArchive = async () => {
246
258
  if (editing) {
247
- await archiveTask(editing.id)
248
- await loadTasks()
259
+ await updateTaskMutation.mutateAsync({ id: editing.id, patch: { status: 'archived' } })
249
260
  onClose()
250
261
  }
251
262
  }
252
263
 
253
264
  const handleUnarchive = async () => {
254
265
  if (editing) {
255
- await unarchiveTask(editing.id)
256
- await loadTasks()
266
+ await updateTaskMutation.mutateAsync({ id: editing.id, patch: { status: 'backlog' } })
257
267
  onClose()
258
268
  }
259
269
  }
260
270
 
261
271
  const handleQueue = async () => {
262
272
  if (editing && editing.status === 'backlog') {
263
- await updateTask(editing.id, { status: 'queued' })
264
- await loadTasks()
273
+ await updateTaskMutation.mutateAsync({ id: editing.id, patch: { status: 'queued' } })
265
274
  onClose()
266
275
  }
267
276
  }
@@ -269,14 +278,13 @@ export function TaskSheet() {
269
278
  const handleAddComment = async () => {
270
279
  if (!editing || !commentText.trim()) return
271
280
  const c: TaskComment = {
272
- id: crypto.randomUUID().slice(0, 8),
281
+ id: Math.random().toString(36).slice(2, 10),
273
282
  author: 'You',
274
283
  text: commentText.trim(),
275
284
  createdAt: Date.now(),
276
285
  }
277
286
  // Use atomic append to avoid race conditions with queue-added comments
278
- await updateTask(editing.id, { appendComment: c } as Partial<BoardTask> & { appendComment: TaskComment })
279
- await loadTasks()
287
+ await appendCommentMutation.mutateAsync({ id: editing.id, comment: c })
280
288
  setCommentText('')
281
289
  }
282
290
 
@@ -0,0 +1,20 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { fetchAgents } from '@/lib/agents'
3
+ import type { Agent } from '@/types'
4
+
5
+ type QueryOptions = {
6
+ enabled?: boolean
7
+ }
8
+
9
+ export const agentQueryKeys = {
10
+ all: ['agents'] as const,
11
+ }
12
+
13
+ export function useAgentsQuery(options: QueryOptions = {}) {
14
+ return useQuery<Record<string, Agent>>({
15
+ queryKey: agentQueryKeys.all,
16
+ queryFn: fetchAgents,
17
+ enabled: options.enabled,
18
+ staleTime: 60_000,
19
+ })
20
+ }
@@ -0,0 +1,20 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { api } from '@/lib/app/api-client'
3
+ import type { Chatroom } from '@/types'
4
+
5
+ type QueryOptions = {
6
+ enabled?: boolean
7
+ }
8
+
9
+ export const chatroomQueryKeys = {
10
+ all: ['chatrooms'] as const,
11
+ }
12
+
13
+ export function useChatroomsQuery(options: QueryOptions = {}) {
14
+ return useQuery<Record<string, Chatroom>>({
15
+ queryKey: chatroomQueryKeys.all,
16
+ queryFn: () => api<Record<string, Chatroom>>('GET', '/chatrooms'),
17
+ enabled: options.enabled,
18
+ staleTime: 30_000,
19
+ })
20
+ }
@@ -0,0 +1,27 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { api } from '@/lib/app/api-client'
3
+ import type { Message, Session } from '@/types'
4
+
5
+ export const chatQueryKeys = {
6
+ all: ['chats'] as const,
7
+ lists: () => ['chats', 'list'] as const,
8
+ detail: (id: string) => ['chats', id] as const,
9
+ messages: (sessionId: string) => ['chats', sessionId, 'messages'] as const,
10
+ }
11
+
12
+ export function useChatListQuery() {
13
+ return useQuery<Session[]>({
14
+ queryKey: chatQueryKeys.lists(),
15
+ queryFn: () => api<Session[]>('GET', '/chats'),
16
+ staleTime: 10_000,
17
+ })
18
+ }
19
+
20
+ export function useMessagesQuery(sessionId: string) {
21
+ return useQuery<Message[]>({
22
+ queryKey: chatQueryKeys.messages(sessionId),
23
+ queryFn: () => api<Message[]>('GET', `/chats/${sessionId}/messages`),
24
+ enabled: !!sessionId,
25
+ staleTime: 5_000,
26
+ })
27
+ }
@@ -0,0 +1,145 @@
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2
+ import { api } from '@/lib/app/api-client'
3
+ import type {
4
+ Connector,
5
+ ConnectorAccessMutationAction,
6
+ ConnectorAccessMutationResponse,
7
+ ConnectorAccessSnapshot,
8
+ } from '@/types'
9
+
10
+ type QueryOptions = {
11
+ enabled?: boolean
12
+ refetchInterval?: number | false
13
+ }
14
+
15
+ interface SaveConnectorInput {
16
+ id?: string | null
17
+ payload: Record<string, unknown>
18
+ }
19
+
20
+ async function invalidateConnectorQueries(queryClient: ReturnType<typeof useQueryClient>) {
21
+ await queryClient.invalidateQueries({ queryKey: connectorQueryKeys.all })
22
+ }
23
+
24
+ export const connectorQueryKeys = {
25
+ all: ['connectors'] as const,
26
+ list: () => ['connectors', 'list'] as const,
27
+ detail: (id: string) => ['connectors', 'detail', id] as const,
28
+ access: (id: string) => ['connectors', 'access', id] as const,
29
+ }
30
+
31
+ export function useConnectorsQuery(options: QueryOptions = {}) {
32
+ return useQuery<Record<string, Connector>>({
33
+ queryKey: connectorQueryKeys.list(),
34
+ queryFn: () => api<Record<string, Connector>>('GET', '/connectors'),
35
+ enabled: options.enabled,
36
+ staleTime: 10_000,
37
+ })
38
+ }
39
+
40
+ export function useConnectorQuery(id: string | null | undefined, options: QueryOptions = {}) {
41
+ return useQuery<Connector>({
42
+ queryKey: connectorQueryKeys.detail(id || 'unknown'),
43
+ queryFn: () => api<Connector>('GET', `/connectors/${id}`),
44
+ enabled: Boolean(id) && options.enabled !== false,
45
+ staleTime: 2_000,
46
+ refetchInterval: options.refetchInterval,
47
+ })
48
+ }
49
+
50
+ export function useConnectorAccessQuery(id: string | null | undefined, options: QueryOptions = {}) {
51
+ return useQuery<ConnectorAccessSnapshot>({
52
+ queryKey: connectorQueryKeys.access(id || 'unknown'),
53
+ queryFn: () => api<ConnectorAccessSnapshot>('GET', `/connectors/${id}/access`),
54
+ enabled: Boolean(id) && options.enabled !== false,
55
+ staleTime: 5_000,
56
+ })
57
+ }
58
+
59
+ export function useConnectorDoctorMutation() {
60
+ return useMutation({
61
+ mutationFn: (payload: Record<string, unknown>) =>
62
+ api<{
63
+ warnings?: string[]
64
+ policy?: Record<string, unknown> | null
65
+ }>('POST', '/connectors/doctor', payload),
66
+ })
67
+ }
68
+
69
+ export function useSaveConnectorMutation() {
70
+ const queryClient = useQueryClient()
71
+ return useMutation({
72
+ mutationFn: ({ id, payload }: SaveConnectorInput) =>
73
+ id ? api('PUT', `/connectors/${id}`, payload) : api('POST', '/connectors', payload),
74
+ onSuccess: async (_data, variables) => {
75
+ await Promise.all([
76
+ invalidateConnectorQueries(queryClient),
77
+ variables.id
78
+ ? queryClient.invalidateQueries({ queryKey: connectorQueryKeys.detail(variables.id) })
79
+ : Promise.resolve(),
80
+ ])
81
+ },
82
+ })
83
+ }
84
+
85
+ export function useConnectorActionMutation() {
86
+ const queryClient = useQueryClient()
87
+ return useMutation({
88
+ mutationFn: ({ id, action }: { id: string; action: string }) =>
89
+ api('PUT', `/connectors/${id}`, { action }),
90
+ onSuccess: async (_data, variables) => {
91
+ await Promise.all([
92
+ invalidateConnectorQueries(queryClient),
93
+ queryClient.invalidateQueries({ queryKey: connectorQueryKeys.detail(variables.id) }),
94
+ ])
95
+ },
96
+ })
97
+ }
98
+
99
+ export function useDeleteConnectorMutation() {
100
+ const queryClient = useQueryClient()
101
+ return useMutation({
102
+ mutationFn: (id: string) => api('DELETE', `/connectors/${id}`),
103
+ onSuccess: async (_data, id) => {
104
+ await Promise.all([
105
+ invalidateConnectorQueries(queryClient),
106
+ queryClient.removeQueries({ queryKey: connectorQueryKeys.detail(id) }),
107
+ queryClient.removeQueries({ queryKey: connectorQueryKeys.access(id) }),
108
+ ])
109
+ },
110
+ })
111
+ }
112
+
113
+ export function useConnectorAccessMutation(id: string | null | undefined) {
114
+ const queryClient = useQueryClient()
115
+ return useMutation({
116
+ mutationFn: ({
117
+ action,
118
+ senderId,
119
+ senderIdAlt,
120
+ code,
121
+ dmAddressingMode,
122
+ }: {
123
+ action: ConnectorAccessMutationAction
124
+ senderId?: string | null
125
+ senderIdAlt?: string | null
126
+ code?: string | null
127
+ dmAddressingMode?: 'open' | 'addressed' | null
128
+ }) => api<ConnectorAccessMutationResponse>('PUT', `/connectors/${id}/access`, {
129
+ action,
130
+ senderId: senderId || null,
131
+ senderIdAlt: senderIdAlt || null,
132
+ code: code || null,
133
+ dmAddressingMode: dmAddressingMode || null,
134
+ }),
135
+ onSuccess: async (result) => {
136
+ if (id) {
137
+ queryClient.setQueryData(connectorQueryKeys.access(id), result.snapshot)
138
+ await Promise.all([
139
+ queryClient.invalidateQueries({ queryKey: connectorQueryKeys.detail(id) }),
140
+ invalidateConnectorQueries(queryClient),
141
+ ])
142
+ }
143
+ },
144
+ })
145
+ }
@@ -0,0 +1,37 @@
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2
+ import { createCredential, fetchCredentials } from '@/lib/chat/chats'
3
+ import type { Credentials } from '@/types'
4
+
5
+ type QueryOptions = {
6
+ enabled?: boolean
7
+ }
8
+
9
+ export interface CreateCredentialInput {
10
+ provider: string
11
+ name: string
12
+ apiKey: string
13
+ }
14
+
15
+ export const credentialQueryKeys = {
16
+ all: ['credentials'] as const,
17
+ }
18
+
19
+ export function useCredentialsQuery(options: QueryOptions = {}) {
20
+ return useQuery<Credentials>({
21
+ queryKey: credentialQueryKeys.all,
22
+ queryFn: fetchCredentials,
23
+ enabled: options.enabled,
24
+ staleTime: 30_000,
25
+ })
26
+ }
27
+
28
+ export function useCreateCredentialMutation() {
29
+ const queryClient = useQueryClient()
30
+ return useMutation({
31
+ mutationFn: ({ provider, name, apiKey }: CreateCredentialInput) =>
32
+ createCredential(provider, name, apiKey),
33
+ onSuccess: async () => {
34
+ await queryClient.invalidateQueries({ queryKey: credentialQueryKeys.all })
35
+ },
36
+ })
37
+ }
@@ -0,0 +1,26 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { api } from '@/lib/app/api-client'
3
+
4
+ type QueryOptions = {
5
+ enabled?: boolean
6
+ }
7
+
8
+ export interface ConnectorExtensionOption {
9
+ id: string
10
+ name: string
11
+ description?: string
12
+ }
13
+
14
+ export const extensionQueryKeys = {
15
+ all: ['extensions'] as const,
16
+ connectorUi: () => ['extensions', 'connector-ui'] as const,
17
+ }
18
+
19
+ export function useConnectorExtensionOptionsQuery(options: QueryOptions = {}) {
20
+ return useQuery<ConnectorExtensionOption[]>({
21
+ queryKey: extensionQueryKeys.connectorUi(),
22
+ queryFn: () => api<ConnectorExtensionOption[]>('GET', '/extensions/ui?type=connectors'),
23
+ enabled: options.enabled,
24
+ staleTime: 60_000,
25
+ })
26
+ }
@@ -0,0 +1,36 @@
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
2
+ import { api } from '@/lib/app/api-client'
3
+ import type { ExternalAgentRuntime } from '@/types'
4
+
5
+ type QueryOptions = {
6
+ enabled?: boolean
7
+ }
8
+
9
+ export const externalAgentQueryKeys = {
10
+ all: ['external-agents'] as const,
11
+ }
12
+
13
+ export function useExternalAgentsQuery(options: QueryOptions = {}) {
14
+ return useQuery<ExternalAgentRuntime[]>({
15
+ queryKey: externalAgentQueryKeys.all,
16
+ queryFn: () => api<ExternalAgentRuntime[]>('GET', '/external-agents'),
17
+ enabled: options.enabled,
18
+ staleTime: 20_000,
19
+ })
20
+ }
21
+
22
+ export function useExternalAgentRuntimeMutation() {
23
+ const queryClient = useQueryClient()
24
+ return useMutation({
25
+ mutationFn: ({
26
+ runtimeId,
27
+ action,
28
+ }: {
29
+ runtimeId: string
30
+ action: 'activate' | 'drain' | 'cordon' | 'restart'
31
+ }) => api('PUT', `/external-agents/${runtimeId}`, { action }),
32
+ onSuccess: async () => {
33
+ await queryClient.invalidateQueries({ queryKey: externalAgentQueryKeys.all })
34
+ },
35
+ })
36
+ }