@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,8 +2,15 @@
2
2
 
3
3
  import { useEffect, useCallback, useState, useRef, useMemo } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
- import { useWs } from '@/hooks/use-ws'
6
- import { updateTask, bulkUpdateTasks, importGitHubIssues, type GitHubIssueImportResult } from '@/lib/tasks'
5
+ import { useAgentsQuery } from '@/features/agents/queries'
6
+ import { useProjectsQuery } from '@/features/projects/queries'
7
+ import {
8
+ useBulkUpdateTasksMutation,
9
+ useImportGitHubIssuesMutation,
10
+ useTasksQuery,
11
+ useUpdateTaskMutation,
12
+ type GitHubIssueImportResult,
13
+ } from '@/features/tasks/queries'
7
14
  import { TaskColumn } from '@/components/tasks/task-column'
8
15
  import { TaskCard } from '@/components/tasks/task-card'
9
16
  import { Skeleton } from '@/components/shared/skeleton'
@@ -63,18 +70,18 @@ function attentionRank(task: BoardTask, now: number | null): number {
63
70
 
64
71
  export default function TasksPage() {
65
72
  const now = useNow()
66
- const tasks = useAppStore((s) => s.tasks)
67
- const loadTasks = useAppStore((s) => s.loadTasks)
68
- const loadAgents = useAppStore((s) => s.loadAgents)
69
73
  const setTaskSheetOpen = useAppStore((s) => s.setTaskSheetOpen)
70
74
  const setEditingTaskId = useAppStore((s) => s.setEditingTaskId)
71
- const agents = useAppStore((s) => s.agents)
72
- const projects = useAppStore((s) => s.projects)
73
- const loadProjects = useAppStore((s) => s.loadProjects)
74
75
  const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
75
76
  const setActiveProjectFilter = useAppStore((s) => s.setActiveProjectFilter)
76
77
  const showArchived = useAppStore((s) => s.showArchivedTasks)
77
78
  const setShowArchived = useAppStore((s) => s.setShowArchivedTasks)
79
+ const { data: tasks = {}, isLoading: tasksLoading } = useTasksQuery({ includeArchived: true })
80
+ const { data: agents = {}, isLoading: agentsLoading } = useAgentsQuery()
81
+ const { data: projects = {}, isLoading: projectsLoading } = useProjectsQuery()
82
+ const bulkUpdateTasksMutation = useBulkUpdateTasksMutation()
83
+ const updateTaskMutation = useUpdateTaskMutation()
84
+ const importGitHubIssuesMutation = useImportGitHubIssuesMutation()
78
85
 
79
86
  // Bulk selection
80
87
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
@@ -97,8 +104,7 @@ export default function TasksPage() {
97
104
  if (selectedIds.size === 0) return
98
105
  setBulkActing(true)
99
106
  try {
100
- await bulkUpdateTasks([...selectedIds], { status })
101
- await loadTasks()
107
+ await bulkUpdateTasksMutation.mutateAsync({ ids: [...selectedIds], patch: { status } })
102
108
  toast.success(`Moved ${selectedIds.size} task(s) to ${status}`)
103
109
  clearSelection()
104
110
  } catch {
@@ -106,15 +112,13 @@ export default function TasksPage() {
106
112
  } finally {
107
113
  setBulkActing(false)
108
114
  }
109
- // eslint-disable-next-line react-hooks/exhaustive-deps
110
- }, [selectedIds])
115
+ }, [bulkUpdateTasksMutation, clearSelection, selectedIds])
111
116
 
112
117
  const handleBulkAgent = useCallback(async (agentId: string) => {
113
118
  if (selectedIds.size === 0) return
114
119
  setBulkActing(true)
115
120
  try {
116
- await bulkUpdateTasks([...selectedIds], { agentId })
117
- await loadTasks()
121
+ await bulkUpdateTasksMutation.mutateAsync({ ids: [...selectedIds], patch: { agentId } })
118
122
  const name = agents[agentId]?.name || 'agent'
119
123
  toast.success(`Assigned ${selectedIds.size} task(s) to ${name}`)
120
124
  clearSelection()
@@ -123,15 +127,13 @@ export default function TasksPage() {
123
127
  } finally {
124
128
  setBulkActing(false)
125
129
  }
126
- // eslint-disable-next-line react-hooks/exhaustive-deps
127
- }, [selectedIds, agents])
130
+ }, [agents, bulkUpdateTasksMutation, clearSelection, selectedIds])
128
131
 
129
132
  const handleBulkProject = useCallback(async (projectId: string | null) => {
130
133
  if (selectedIds.size === 0) return
131
134
  setBulkActing(true)
132
135
  try {
133
- await bulkUpdateTasks([...selectedIds], { projectId })
134
- await loadTasks()
136
+ await bulkUpdateTasksMutation.mutateAsync({ ids: [...selectedIds], patch: { projectId } })
135
137
  toast.success(projectId ? `Assigned ${selectedIds.size} task(s) to project` : `Cleared project from ${selectedIds.size} task(s)`)
136
138
  clearSelection()
137
139
  } catch {
@@ -139,8 +141,7 @@ export default function TasksPage() {
139
141
  } finally {
140
142
  setBulkActing(false)
141
143
  }
142
- // eslint-disable-next-line react-hooks/exhaustive-deps
143
- }, [selectedIds])
144
+ }, [bulkUpdateTasksMutation, clearSelection, selectedIds])
144
145
 
145
146
  // Bulk action bar dropdowns
146
147
  const [bulkAgentOpen, setBulkAgentOpen] = useState(false)
@@ -196,11 +197,7 @@ export default function TasksPage() {
196
197
  window.history.replaceState(null, '', newUrl)
197
198
  }, [activeProjectFilter, filterAgentId, filterTag, filtersHydrated, taskScopeFilter])
198
199
 
199
- const [loaded, setLoaded] = useState(Object.keys(tasks).length > 0)
200
- useEffect(() => {
201
- Promise.all([loadTasks(), loadAgents(), loadProjects()]).then(() => setLoaded(true))
202
- }, [loadAgents, loadProjects, loadTasks])
203
- useWs('tasks', loadTasks, 5000)
200
+ const loaded = !tasksLoading && !agentsLoading && !projectsLoading
204
201
 
205
202
  // Collect all unique tags across tasks
206
203
  const allTags = dedup(Object.values(tasks).flatMap((t) => t.tags || [])).sort()
@@ -259,10 +256,8 @@ export default function TasksPage() {
259
256
  const handleDrop = useCallback(async (taskId: string, newStatus: BoardTaskStatus) => {
260
257
  const task = tasks[taskId]
261
258
  if (!task || task.status === newStatus) return
262
- await updateTask(taskId, { status: newStatus })
263
- await loadTasks()
264
- // eslint-disable-next-line react-hooks/exhaustive-deps
265
- }, [tasks])
259
+ await updateTaskMutation.mutateAsync({ id: taskId, patch: { status: newStatus } })
260
+ }, [tasks, updateTaskMutation])
266
261
 
267
262
  const archivedCount = Object.values(tasks).filter((t) => t.status === 'archived').length
268
263
 
@@ -284,7 +279,7 @@ export default function TasksPage() {
284
279
  try {
285
280
  const rawLimit = Number.parseInt(githubLimit, 10)
286
281
  const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 200)) : 25
287
- const result = await importGitHubIssues({
282
+ const result = await importGitHubIssuesMutation.mutateAsync({
288
283
  repo: githubRepo.trim(),
289
284
  token: githubToken.trim() || undefined,
290
285
  state: githubState,
@@ -296,7 +291,6 @@ export default function TasksPage() {
296
291
  projectId: activeProjectFilter,
297
292
  })
298
293
  setGitHubImportResult(result)
299
- await loadTasks()
300
294
  const summary = result.created.length > 0
301
295
  ? `Imported ${result.created.length} issue(s) from ${result.repo}`
302
296
  : `No new issues imported from ${result.repo}`
@@ -316,7 +310,7 @@ export default function TasksPage() {
316
310
  githubRepo,
317
311
  githubState,
318
312
  githubToken,
319
- loadTasks,
313
+ importGitHubIssuesMutation,
320
314
  ])
321
315
 
322
316
  // Task counts per project (non-archived)
@@ -759,6 +753,9 @@ export default function TasksPage() {
759
753
  <TaskColumn
760
754
  status={status}
761
755
  tasks={tasksByStatus(status)}
756
+ agents={agents}
757
+ projects={projects}
758
+ tasksById={tasks}
762
759
  onDrop={handleDrop}
763
760
  selectionMode={selectionMode}
764
761
  selectedIds={selectedIds}
@@ -804,6 +801,9 @@ export default function TasksPage() {
804
801
  <TaskCard
805
802
  key={task.id}
806
803
  task={task}
804
+ agents={agents}
805
+ projects={projects}
806
+ tasksById={tasks}
807
807
  index={idx}
808
808
  selectionMode={selectionMode}
809
809
  selected={selectedIds.has(task.id)}
package/src/cli/index.js CHANGED
@@ -596,6 +596,7 @@ const COMMAND_GROUPS = [
596
596
  defaultBody: { action: 'status' },
597
597
  }),
598
598
  cmd('checkpoints', 'GET', '/chats/:id/checkpoints', 'List checkpoint history for a chat'),
599
+ cmd('migrate-messages', 'POST', '/chats/migrate-messages', 'Migrate messages from session blobs to relational table'),
599
600
  ],
600
601
  },
601
602
  {
package/src/cli/spec.js CHANGED
@@ -428,6 +428,7 @@ const COMMAND_GROUPS = {
428
428
  delete: { description: 'Delete one chat', method: 'DELETE', path: '/chats/:id', params: ['id'] },
429
429
  'delete-many': { description: 'Delete multiple chats (body: {"ids":[...]})', method: 'DELETE', path: '/chats' },
430
430
  'heartbeat-disable-all': { description: 'Disable all chat heartbeats and cancel queued heartbeat runs', method: 'POST', path: '/chats/heartbeat' },
431
+ 'migrate-messages': { description: 'Migrate messages from session blobs to relational table', method: 'POST', path: '/chats/migrate-messages' },
431
432
  messages: { description: 'Get chat message history', method: 'GET', path: '/chats/:id/messages', params: ['id'] },
432
433
  'messages-update': { description: 'Update chat message metadata (e.g. bookmark)', method: 'PUT', path: '/chats/:id/messages', params: ['id'] },
433
434
  'messages-send': { description: 'Append a user/system message to a chat', method: 'POST', path: '/chats/:id/messages', params: ['id'] },
@@ -447,7 +447,7 @@ export function AgentSheet() {
447
447
  }))
448
448
  setOpenclawEnabled(editing.provider === 'openclaw')
449
449
  setProjectId(editing.projectId)
450
- setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
450
+ setAvatarSeed(editing.avatarSeed || Math.random().toString(36).slice(2, 10))
451
451
  setAvatarUrl(editing.avatarUrl || null)
452
452
  setThinkingLevel(editing.thinkingLevel || '')
453
453
  setMemoryScopeMode(editing.memoryScopeMode || 'auto')
@@ -527,7 +527,7 @@ export function AgentSheet() {
527
527
  }))
528
528
  setOpenclawEnabled(src.provider === 'openclaw')
529
529
  setProjectId(src.projectId)
530
- setAvatarSeed(crypto.randomUUID().slice(0, 8))
530
+ setAvatarSeed(Math.random().toString(36).slice(2, 10))
531
531
  setAvatarUrl(null)
532
532
  setThinkingLevel(src.thinkingLevel || '')
533
533
  setMemoryScopeMode(src.memoryScopeMode || 'auto')
@@ -725,7 +725,7 @@ export function AgentSheet() {
725
725
 
726
726
  const addRoutingTargetFromCurrent = () => {
727
727
  const nextTarget: AgentRoutingTarget = {
728
- id: crypto.randomUUID(),
728
+ id: Math.random().toString(16).slice(2, 10),
729
729
  label: routingTargets.length === 0 ? 'Primary route' : `Route ${routingTargets.length + 1}`,
730
730
  role: routingTargets.length === 0 ? 'primary' : 'backup',
731
731
  provider,
@@ -1165,7 +1165,7 @@ export function AgentSheet() {
1165
1165
  type="button"
1166
1166
  onClick={() => {
1167
1167
  setAvatarUrl(null)
1168
- if (!avatarSeed) setAvatarSeed(crypto.randomUUID().slice(0, 8))
1168
+ if (!avatarSeed) setAvatarSeed(Math.random().toString(36).slice(2, 10))
1169
1169
  }}
1170
1170
  className="text-[11px] text-text-3 hover:text-red-400 transition-colors self-start cursor-pointer"
1171
1171
  >
@@ -1186,7 +1186,7 @@ export function AgentSheet() {
1186
1186
  />
1187
1187
  <button
1188
1188
  type="button"
1189
- onClick={() => { setAvatarSeed(crypto.randomUUID().slice(0, 8)); setAvatarUrl(null) }}
1189
+ onClick={() => { setAvatarSeed(Math.random().toString(36).slice(2, 10)); setAvatarUrl(null) }}
1190
1190
  className="inline-flex items-center gap-1.5 px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 cursor-pointer transition-all hover:bg-white/[0.04] hover:text-text-2 active:scale-95 shrink-0"
1191
1191
  style={{ fontFamily: 'inherit' }}
1192
1192
  title="Shuffle avatar"
@@ -119,7 +119,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
119
119
  if (!editingProviderId && draftAgents.length === 0) {
120
120
  const cp = configured
121
121
  setDraftAgents([{
122
- id: `auto:${crypto.randomUUID().slice(0, 8)}`,
122
+ id: `auto:${Math.random().toString(36).slice(2, 10)}`,
123
123
  templateId: 'auto',
124
124
  name: 'Assistant',
125
125
  description: 'A helpful assistant.',
@@ -140,7 +140,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
140
140
  autoDraftSkillSuggestions: true,
141
141
  orchestratorEnabled: false,
142
142
  orchestratorMission: '',
143
- avatarSeed: crypto.randomUUID().slice(0, 8),
143
+ avatarSeed: Math.random().toString(36).slice(2, 10),
144
144
  avatarUrl: null,
145
145
  enabled: true,
146
146
  }])
@@ -182,7 +182,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
182
182
  const addBlankAgent = () => {
183
183
  const defaultProvider = configuredProviders[0] || null
184
184
  const newAgent: StarterDraftAgent = {
185
- id: `custom:${crypto.randomUUID().slice(0, 8)}`,
185
+ id: `custom:${Math.random().toString(36).slice(2, 10)}`,
186
186
  templateId: 'custom',
187
187
  name: `Agent ${draftAgents.length + 1}`,
188
188
  description: '',
@@ -203,7 +203,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
203
203
  autoDraftSkillSuggestions: true,
204
204
  orchestratorEnabled: false,
205
205
  orchestratorMission: '',
206
- avatarSeed: crypto.randomUUID().slice(0, 8),
206
+ avatarSeed: Math.random().toString(36).slice(2, 10),
207
207
  avatarUrl: null,
208
208
  enabled: true,
209
209
  }
@@ -361,7 +361,7 @@ export function StepAgents({
361
361
  />
362
362
  <button
363
363
  type="button"
364
- onClick={() => onUpdateDraft(draft.id, { avatarSeed: crypto.randomUUID().slice(0, 8), avatarUrl: null })}
364
+ onClick={() => onUpdateDraft(draft.id, { avatarSeed: Math.random().toString(36).slice(2, 10), avatarUrl: null })}
365
365
  className="inline-flex items-center gap-1.5 px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 cursor-pointer transition-all hover:bg-white/[0.04] hover:text-text-2 active:scale-95 shrink-0"
366
366
  title="Shuffle avatar"
367
367
  >
@@ -216,7 +216,7 @@ export function StepConnect({
216
216
  }
217
217
 
218
218
  const configured: ConfiguredProvider = {
219
- id: crypto.randomUUID(),
219
+ id: Math.random().toString(16).slice(2, 10),
220
220
  setupProvider: provider,
221
221
  provider: resolvedProvider,
222
222
  name: providerLabel.trim() || selectedProvider.name,
@@ -170,7 +170,7 @@ export function buildStarterDrafts(args: {
170
170
  autoDraftSkillSuggestions: previous?.autoDraftSkillSuggestions ?? true,
171
171
  orchestratorEnabled: previous?.orchestratorEnabled ?? false,
172
172
  orchestratorMission: previous?.orchestratorMission || '',
173
- avatarSeed: previous?.avatarSeed || crypto.randomUUID().slice(0, 8),
173
+ avatarSeed: previous?.avatarSeed || Math.random().toString(36).slice(2, 10),
174
174
  avatarUrl: previous?.avatarUrl || null,
175
175
  enabled: previous?.enabled ?? true,
176
176
  }
@@ -7,146 +7,10 @@ import { BottomSheet } from '@/components/shared/bottom-sheet'
7
7
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
8
8
  import { toast } from 'sonner'
9
9
  import { AgentAvatar } from '@/components/agents/agent-avatar'
10
- import type { Agent, ChatroomRoutingRule } from '@/types'
10
+ import type { Agent } from '@/types'
11
11
  import { CheckIcon } from '@/components/shared/check-icon'
12
12
  import { WORKER_ONLY_PROVIDER_IDS } from '@/lib/provider-sets'
13
13
 
14
- function genRuleId(): string {
15
- return `rule-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
16
- }
17
-
18
- interface RuleFormState {
19
- type: 'keyword' | 'capability'
20
- pattern: string
21
- keywords: string
22
- agentId: string
23
- priority: number
24
- }
25
-
26
- const emptyRuleForm: RuleFormState = {
27
- type: 'keyword',
28
- pattern: '',
29
- keywords: '',
30
- agentId: '',
31
- priority: 10,
32
- }
33
-
34
- function RoutingRuleForm({
35
- rule,
36
- memberAgents,
37
- onSave,
38
- onCancel,
39
- }: {
40
- rule: RuleFormState
41
- memberAgents: Agent[]
42
- onSave: (form: RuleFormState) => void
43
- onCancel: () => void
44
- }) {
45
- const [form, setForm] = useState<RuleFormState>(rule)
46
-
47
- return (
48
- <div className="p-3 rounded-[8px] bg-white/[0.04] border border-white/[0.08] space-y-3">
49
- <div className="flex gap-2">
50
- {(['keyword', 'capability'] as const).map((t) => (
51
- <button
52
- key={t}
53
- type="button"
54
- onClick={() => setForm((f) => ({ ...f, type: t }))}
55
- className={`flex-1 py-1.5 text-[11px] font-600 capitalize rounded-[6px] cursor-pointer transition-all ${
56
- form.type === t
57
- ? 'bg-accent-soft text-accent-bright'
58
- : 'bg-white/[0.04] text-text-3 hover:text-text-2'
59
- }`}
60
- >
61
- {t}
62
- </button>
63
- ))}
64
- </div>
65
-
66
- {form.type === 'keyword' && (
67
- <>
68
- <div>
69
- <label className="block text-[11px] font-600 text-text-3 mb-1">Keywords (comma-separated)</label>
70
- <input
71
- type="text"
72
- value={form.keywords}
73
- onChange={(e) => setForm((f) => ({ ...f, keywords: e.target.value }))}
74
- placeholder="e.g. deploy, devops, infrastructure"
75
- className="w-full px-2.5 py-1.5 rounded-[6px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
76
- />
77
- </div>
78
- <div>
79
- <label className="block text-[11px] font-600 text-text-3 mb-1">Regex pattern (optional)</label>
80
- <input
81
- type="text"
82
- value={form.pattern}
83
- onChange={(e) => setForm((f) => ({ ...f, pattern: e.target.value }))}
84
- placeholder="e.g. deploy|release|ship"
85
- className="w-full px-2.5 py-1.5 rounded-[6px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
86
- />
87
- </div>
88
- </>
89
- )}
90
-
91
- {form.type === 'capability' && (
92
- <div>
93
- <label className="block text-[11px] font-600 text-text-3 mb-1">Capability pattern</label>
94
- <input
95
- type="text"
96
- value={form.pattern}
97
- onChange={(e) => setForm((f) => ({ ...f, pattern: e.target.value }))}
98
- placeholder="e.g. frontend, research, devops"
99
- className="w-full px-2.5 py-1.5 rounded-[6px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
100
- />
101
- </div>
102
- )}
103
-
104
- <div className="flex gap-2">
105
- <div className="flex-1">
106
- <label className="block text-[11px] font-600 text-text-3 mb-1">Route to agent</label>
107
- <select
108
- value={form.agentId}
109
- onChange={(e) => setForm((f) => ({ ...f, agentId: e.target.value }))}
110
- className="w-full px-2.5 py-1.5 rounded-[6px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text focus:outline-none focus:border-accent-bright/40"
111
- >
112
- <option value="">Select agent...</option>
113
- {memberAgents.map((a) => (
114
- <option key={a.id} value={a.id}>{a.name}</option>
115
- ))}
116
- </select>
117
- </div>
118
- <div className="w-20">
119
- <label className="block text-[11px] font-600 text-text-3 mb-1">Priority</label>
120
- <input
121
- type="number"
122
- value={form.priority}
123
- onChange={(e) => setForm((f) => ({ ...f, priority: parseInt(e.target.value, 10) || 0 }))}
124
- className="w-full px-2.5 py-1.5 rounded-[6px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text focus:outline-none focus:border-accent-bright/40"
125
- />
126
- </div>
127
- </div>
128
-
129
- <div className="flex gap-2 justify-end">
130
- <button
131
- type="button"
132
- onClick={onCancel}
133
- className="px-3 py-1.5 text-[11px] font-600 text-text-3 hover:text-text-2 cursor-pointer"
134
- >
135
- Cancel
136
- </button>
137
- <button
138
- type="button"
139
- onClick={() => onSave(form)}
140
- disabled={!form.agentId || (form.type === 'keyword' && !form.keywords.trim() && !form.pattern.trim()) || (form.type === 'capability' && !form.pattern.trim())}
141
- className="px-3 py-1.5 text-[11px] font-600 bg-accent-bright text-white rounded-[6px] hover:bg-accent-bright/90 disabled:opacity-50 cursor-pointer"
142
- >
143
- Save Rule
144
- </button>
145
- </div>
146
- </div>
147
- )
148
- }
149
-
150
14
  export function ChatroomSheet() {
151
15
  const open = useChatroomStore((s) => s.chatroomSheetOpen)
152
16
  const editingId = useChatroomStore((s) => s.editingChatroomId)
@@ -163,10 +27,8 @@ export function ChatroomSheet() {
163
27
  const [selectedAgentIds, setSelectedAgentIds] = useState<string[]>([])
164
28
  const [chatMode, setChatMode] = useState<'sequential' | 'parallel'>('sequential')
165
29
  const [autoAddress, setAutoAddress] = useState(false)
166
- const [routingRules, setRoutingRules] = useState<ChatroomRoutingRule[]>([])
30
+ const [routingGuidance, setRoutingGuidance] = useState('')
167
31
  const [saving, setSaving] = useState(false)
168
- const [addingRule, setAddingRule] = useState(false)
169
- const [editingRuleId, setEditingRuleId] = useState<string | null>(null)
170
32
  const [confirmDelete, setConfirmDelete] = useState(false)
171
33
 
172
34
  const editing = editingId ? chatrooms[editingId] : null
@@ -178,17 +40,15 @@ export function ChatroomSheet() {
178
40
  setSelectedAgentIds([...editing.agentIds])
179
41
  setChatMode(editing.chatMode || 'sequential')
180
42
  setAutoAddress(editing.autoAddress || false)
181
- setRoutingRules([...(editing.routingRules || [])])
43
+ setRoutingGuidance(editing.routingGuidance || '')
182
44
  } else {
183
45
  setName('')
184
46
  setDescription('')
185
47
  setSelectedAgentIds([])
186
48
  setChatMode('sequential')
187
49
  setAutoAddress(false)
188
- setRoutingRules([])
50
+ setRoutingGuidance('')
189
51
  }
190
- setAddingRule(false)
191
- setEditingRuleId(null)
192
52
  setConfirmDelete(false)
193
53
  }, [editing, open])
194
54
 
@@ -206,7 +66,7 @@ export function ChatroomSheet() {
206
66
  agentIds: selectedAgentIds,
207
67
  chatMode,
208
68
  autoAddress,
209
- routingRules: routingRules.length > 0 ? routingRules : undefined,
69
+ routingGuidance: routingGuidance.trim() || null,
210
70
  }
211
71
  if (editing) {
212
72
  await updateChatroom(editing.id, payload)
@@ -245,53 +105,10 @@ export function ChatroomSheet() {
245
105
  )
246
106
  }
247
107
 
248
- const handleAddRule = (form: RuleFormState) => {
249
- const rule: ChatroomRoutingRule = {
250
- id: genRuleId(),
251
- type: form.type,
252
- agentId: form.agentId,
253
- priority: form.priority,
254
- ...(form.pattern.trim() ? { pattern: form.pattern.trim() } : {}),
255
- ...(form.type === 'keyword' && form.keywords.trim()
256
- ? { keywords: form.keywords.split(',').map((k) => k.trim()).filter(Boolean) }
257
- : {}),
258
- }
259
- setRoutingRules((prev) => [...prev, rule].sort((a, b) => a.priority - b.priority))
260
- setAddingRule(false)
261
- }
262
-
263
- const handleEditRule = (form: RuleFormState) => {
264
- setRoutingRules((prev) =>
265
- prev.map((r) =>
266
- r.id === editingRuleId
267
- ? {
268
- ...r,
269
- type: form.type,
270
- agentId: form.agentId,
271
- priority: form.priority,
272
- pattern: form.pattern.trim() || undefined,
273
- keywords:
274
- form.type === 'keyword' && form.keywords.trim()
275
- ? form.keywords.split(',').map((k) => k.trim()).filter(Boolean)
276
- : undefined,
277
- }
278
- : r,
279
- ).sort((a, b) => a.priority - b.priority),
280
- )
281
- setEditingRuleId(null)
282
- }
283
-
284
- const removeRule = (ruleId: string) => {
285
- setRoutingRules((prev) => prev.filter((r) => r.id !== ruleId))
286
- }
287
-
288
108
  const agentList = Object.values(agents).filter(
289
109
  (a: Agent) => !a.trashedAt && !WORKER_ONLY_PROVIDER_IDS.has(a.provider)
290
110
  ) as Agent[]
291
111
 
292
- const memberAgents = agentList.filter((a) => selectedAgentIds.includes(a.id))
293
- const sortedRules = [...routingRules].sort((a, b) => a.priority - b.priority)
294
-
295
112
  return (
296
113
  <BottomSheet open={open} onClose={() => setChatroomSheetOpen(false)}>
297
114
  <div className="p-6 max-w-[560px] mx-auto">
@@ -350,7 +167,7 @@ export function ChatroomSheet() {
350
167
  <div>
351
168
  <button
352
169
  type="button"
353
- onClick={() => setAutoAddress((v) => !v)}
170
+ onClick={() => setAutoAddress((value) => !value)}
354
171
  className="w-full flex items-center gap-2.5 px-3 py-2.5 rounded-[8px] border border-white/[0.08] bg-white/[0.03] cursor-pointer transition-all hover:bg-white/[0.05]"
355
172
  >
356
173
  <div className={`w-8 h-[18px] rounded-full transition-all relative ${autoAddress ? 'bg-accent-bright' : 'bg-white/[0.12]'}`}>
@@ -361,7 +178,7 @@ export function ChatroomSheet() {
361
178
  <p className="text-[11px] text-text-3 mt-0.5">
362
179
  {autoAddress
363
180
  ? 'Every message is sent to all agents, no @mention needed'
364
- : 'Only agents you @mention will respond'}
181
+ : 'Only agents you @mention respond unless routing guidance selects someone'}
365
182
  </p>
366
183
  </div>
367
184
  </button>
@@ -405,95 +222,18 @@ export function ChatroomSheet() {
405
222
  )}
406
223
  </div>
407
224
 
408
- {/* Routing Rules */}
409
225
  <div>
410
- <label className="block text-[12px] font-600 text-text-2 mb-1.5">
411
- Routing Rules ({sortedRules.length})
412
- </label>
226
+ <label className="block text-[12px] font-600 text-text-2 mb-1.5">Routing Guidance</label>
413
227
  <p className="text-[11px] text-text-3 mb-2">
414
- Route messages to specific agents based on keywords or capabilities. Evaluated before auto-address.
228
+ Optional. Used only when there is no explicit @mention and auto-address is off. Describe which kinds of messages should go to which members.
415
229
  </p>
416
-
417
- {sortedRules.length > 0 && (
418
- <div className="space-y-2 mb-2">
419
- {sortedRules.map((rule) => {
420
- const agent = agents[rule.agentId]
421
- if (editingRuleId === rule.id) {
422
- return (
423
- <RoutingRuleForm
424
- key={rule.id}
425
- rule={{
426
- type: rule.type,
427
- pattern: rule.pattern || '',
428
- keywords: rule.keywords?.join(', ') || '',
429
- agentId: rule.agentId,
430
- priority: rule.priority,
431
- }}
432
- memberAgents={memberAgents}
433
- onSave={handleEditRule}
434
- onCancel={() => setEditingRuleId(null)}
435
- />
436
- )
437
- }
438
- return (
439
- <div
440
- key={rule.id}
441
- className="flex items-center gap-2 px-3 py-2 rounded-[8px] bg-white/[0.04] border border-white/[0.08]"
442
- >
443
- <span className="text-[10px] font-700 text-text-3 bg-white/[0.06] px-1.5 py-0.5 rounded">
444
- P{rule.priority}
445
- </span>
446
- <span className="text-[10px] font-600 text-accent-bright/70 uppercase">
447
- {rule.type}
448
- </span>
449
- <span className="text-[12px] text-text-2 flex-1 truncate">
450
- {rule.type === 'keyword'
451
- ? (rule.keywords?.join(', ') || rule.pattern || '(no match)')
452
- : (rule.pattern || '(no pattern)')}
453
- </span>
454
- <span className="text-[11px] text-text-3 truncate max-w-[100px]">
455
- {agent?.name || 'Unknown'}
456
- </span>
457
- <button
458
- type="button"
459
- onClick={() => setEditingRuleId(rule.id)}
460
- className="text-[11px] text-text-3 hover:text-text-2 cursor-pointer px-1"
461
- >
462
- Edit
463
- </button>
464
- <button
465
- type="button"
466
- onClick={() => removeRule(rule.id)}
467
- className="text-[11px] text-red-400 hover:text-red-300 cursor-pointer px-1"
468
- >
469
- Remove
470
- </button>
471
- </div>
472
- )
473
- })}
474
- </div>
475
- )}
476
-
477
- {addingRule ? (
478
- <RoutingRuleForm
479
- rule={emptyRuleForm}
480
- memberAgents={memberAgents}
481
- onSave={handleAddRule}
482
- onCancel={() => setAddingRule(false)}
483
- />
484
- ) : (
485
- <button
486
- type="button"
487
- onClick={() => setAddingRule(true)}
488
- disabled={memberAgents.length === 0}
489
- className="w-full py-2 rounded-[8px] border border-dashed border-white/[0.12] text-[12px] font-600 text-text-3 hover:text-text-2 hover:border-white/[0.2] cursor-pointer transition-all disabled:opacity-40 disabled:cursor-not-allowed"
490
- >
491
- + Add Rule
492
- </button>
493
- )}
494
- {memberAgents.length === 0 && (
495
- <p className="text-[11px] text-text-3 mt-1">Add members first to create routing rules.</p>
496
- )}
230
+ <textarea
231
+ value={routingGuidance}
232
+ onChange={(e) => setRoutingGuidance(e.target.value)}
233
+ placeholder={'Examples:\nRoute deployment issues to Ops.\nPrefer Maya for design reviews and UI polish.\nSend pricing or market-analysis requests to Research.'}
234
+ rows={6}
235
+ className="w-full px-3 py-2 rounded-[8px] bg-white/[0.06] border border-white/[0.08] text-[13px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40 resize-y min-h-[132px]"
236
+ />
497
237
  </div>
498
238
  </div>
499
239