@swarmclawai/swarmclaw 0.7.7 → 0.8.0

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 (281) hide show
  1. package/README.md +12 -14
  2. package/next.config.ts +13 -2
  3. package/package.json +4 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +9 -0
  5. package/src/app/api/agents/route.ts +4 -0
  6. package/src/app/api/agents/thread-route.test.ts +133 -0
  7. package/src/app/api/approvals/route.test.ts +148 -0
  8. package/src/app/api/canvas/[sessionId]/route.ts +3 -1
  9. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
  10. package/src/app/api/chats/[id]/devserver/route.ts +48 -7
  11. package/src/app/api/chats/[id]/messages/route.ts +42 -18
  12. package/src/app/api/chats/[id]/route.ts +1 -1
  13. package/src/app/api/chats/[id]/stop/route.ts +5 -4
  14. package/src/app/api/chats/route.ts +23 -2
  15. package/src/app/api/clawhub/install/route.ts +28 -8
  16. package/src/app/api/connectors/[id]/route.ts +46 -3
  17. package/src/app/api/connectors/route.ts +12 -8
  18. package/src/app/api/external-agents/route.test.ts +165 -0
  19. package/src/app/api/gateways/[id]/health/route.ts +27 -12
  20. package/src/app/api/gateways/[id]/route.ts +2 -0
  21. package/src/app/api/gateways/health-route.test.ts +135 -0
  22. package/src/app/api/gateways/route.ts +2 -0
  23. package/src/app/api/mcp-servers/route.test.ts +130 -0
  24. package/src/app/api/openclaw/deploy/route.ts +38 -5
  25. package/src/app/api/plugins/install/route.ts +46 -6
  26. package/src/app/api/plugins/marketplace/route.ts +48 -15
  27. package/src/app/api/preview-server/route.ts +26 -11
  28. package/src/app/api/projects/[id]/route.ts +6 -2
  29. package/src/app/api/projects/route.ts +4 -3
  30. package/src/app/api/schedules/[id]/run/route.ts +4 -0
  31. package/src/app/api/schedules/route.test.ts +86 -0
  32. package/src/app/api/schedules/route.ts +6 -1
  33. package/src/app/api/secrets/[id]/route.ts +1 -0
  34. package/src/app/api/secrets/route.ts +2 -1
  35. package/src/app/api/settings/route.ts +2 -0
  36. package/src/app/api/setup/check-provider/route.test.ts +19 -0
  37. package/src/app/api/setup/check-provider/route.ts +40 -10
  38. package/src/app/api/skills/[id]/route.ts +12 -0
  39. package/src/app/api/skills/import/route.ts +14 -12
  40. package/src/app/api/skills/route.ts +13 -1
  41. package/src/app/api/tasks/[id]/route.ts +10 -1
  42. package/src/app/api/tasks/import/github/route.test.ts +65 -0
  43. package/src/app/api/tasks/import/github/route.ts +337 -0
  44. package/src/app/api/wallets/[id]/approve/route.ts +17 -3
  45. package/src/app/api/wallets/[id]/route.ts +79 -33
  46. package/src/app/api/wallets/[id]/send/route.ts +19 -33
  47. package/src/app/api/wallets/route.ts +78 -61
  48. package/src/app/api/webhooks/[id]/route.ts +33 -6
  49. package/src/app/api/webhooks/route.test.ts +272 -0
  50. package/src/cli/index.js +1 -0
  51. package/src/cli/spec.js +1 -0
  52. package/src/components/agents/agent-card.tsx +9 -2
  53. package/src/components/agents/agent-chat-list.tsx +18 -2
  54. package/src/components/agents/agent-list.tsx +1 -0
  55. package/src/components/agents/agent-sheet.tsx +257 -38
  56. package/src/components/agents/inspector-panel.tsx +41 -0
  57. package/src/components/canvas/canvas-panel.tsx +236 -65
  58. package/src/components/chat/chat-area.tsx +36 -19
  59. package/src/components/chat/chat-card.tsx +36 -13
  60. package/src/components/chat/chat-header.tsx +48 -16
  61. package/src/components/chat/chat-list.tsx +28 -4
  62. package/src/components/chat/checkpoint-timeline.tsx +50 -34
  63. package/src/components/chat/delegation-banner.test.ts +14 -1
  64. package/src/components/chat/delegation-banner.tsx +1 -1
  65. package/src/components/chat/message-bubble.tsx +208 -145
  66. package/src/components/chat/message-list.tsx +48 -19
  67. package/src/components/chatrooms/chatroom-message.tsx +2 -2
  68. package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
  69. package/src/components/connectors/connector-health.tsx +1 -1
  70. package/src/components/connectors/connector-list.tsx +7 -2
  71. package/src/components/connectors/connector-sheet.tsx +337 -148
  72. package/src/components/gateways/gateway-sheet.tsx +2 -2
  73. package/src/components/layout/app-layout.tsx +40 -23
  74. package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
  75. package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
  76. package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
  77. package/src/components/plugins/plugin-list.tsx +45 -9
  78. package/src/components/plugins/plugin-sheet.tsx +55 -7
  79. package/src/components/projects/project-detail.tsx +217 -0
  80. package/src/components/projects/project-sheet.tsx +176 -4
  81. package/src/components/providers/provider-list.tsx +2 -1
  82. package/src/components/providers/provider-sheet.tsx +21 -2
  83. package/src/components/schedules/schedule-card.tsx +25 -1
  84. package/src/components/schedules/schedule-sheet.tsx +44 -2
  85. package/src/components/secrets/secret-sheet.tsx +21 -2
  86. package/src/components/shared/agent-switch-dialog.tsx +12 -1
  87. package/src/components/shared/bottom-sheet.tsx +13 -3
  88. package/src/components/shared/command-palette.tsx +8 -1
  89. package/src/components/shared/confirm-dialog.tsx +19 -4
  90. package/src/components/shared/connector-platform-icon.test.ts +28 -0
  91. package/src/components/shared/connector-platform-icon.tsx +39 -6
  92. package/src/components/shared/settings/plugin-manager.tsx +29 -6
  93. package/src/components/shared/settings/section-capability-policy.tsx +45 -3
  94. package/src/components/shared/settings/section-voice.tsx +11 -3
  95. package/src/components/skills/skill-list.tsx +25 -0
  96. package/src/components/skills/skill-sheet.tsx +84 -12
  97. package/src/components/tasks/approvals-panel.tsx +289 -34
  98. package/src/components/tasks/task-board.tsx +410 -25
  99. package/src/components/tasks/task-card.tsx +66 -8
  100. package/src/components/tasks/task-sheet.tsx +16 -4
  101. package/src/components/ui/dialog.tsx +2 -2
  102. package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
  103. package/src/components/wallets/wallet-panel.tsx +435 -90
  104. package/src/components/wallets/wallet-section.tsx +198 -48
  105. package/src/components/webhooks/webhook-sheet.tsx +22 -2
  106. package/src/lib/approval-display.ts +20 -0
  107. package/src/lib/canvas-content.ts +198 -0
  108. package/src/lib/chat-artifact-summary.ts +165 -0
  109. package/src/lib/chat-display.test.ts +91 -0
  110. package/src/lib/chat-display.ts +58 -0
  111. package/src/lib/chat-streaming-state.test.ts +47 -1
  112. package/src/lib/chat-streaming-state.ts +42 -0
  113. package/src/lib/ollama-model.ts +10 -0
  114. package/src/lib/openclaw-endpoint.test.ts +8 -0
  115. package/src/lib/openclaw-endpoint.ts +6 -1
  116. package/src/lib/plugin-install-cors.ts +46 -0
  117. package/src/lib/plugin-sources.test.ts +43 -0
  118. package/src/lib/plugin-sources.ts +77 -0
  119. package/src/lib/providers/ollama.ts +16 -6
  120. package/src/lib/providers/openclaw.test.ts +54 -0
  121. package/src/lib/providers/openclaw.ts +127 -11
  122. package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
  123. package/src/lib/schedule-dedupe.test.ts +66 -1
  124. package/src/lib/schedule-dedupe.ts +169 -12
  125. package/src/lib/schedule-origin.test.ts +20 -0
  126. package/src/lib/schedule-origin.ts +15 -0
  127. package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
  128. package/src/lib/server/agent-availability.ts +16 -0
  129. package/src/lib/server/agent-runtime-config.ts +12 -4
  130. package/src/lib/server/agent-thread-session.test.ts +51 -0
  131. package/src/lib/server/agent-thread-session.ts +7 -0
  132. package/src/lib/server/approval-match.ts +205 -0
  133. package/src/lib/server/approvals-auto-approve.test.ts +538 -1
  134. package/src/lib/server/approvals.ts +214 -1
  135. package/src/lib/server/assistant-control.test.ts +29 -0
  136. package/src/lib/server/assistant-control.ts +23 -0
  137. package/src/lib/server/build-llm.test.ts +79 -0
  138. package/src/lib/server/build-llm.ts +14 -4
  139. package/src/lib/server/canvas-content.test.ts +32 -0
  140. package/src/lib/server/canvas-content.ts +6 -0
  141. package/src/lib/server/capability-router.test.ts +33 -0
  142. package/src/lib/server/capability-router.ts +80 -19
  143. package/src/lib/server/chat-execution-advanced.test.ts +651 -0
  144. package/src/lib/server/chat-execution-disabled.test.ts +94 -0
  145. package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
  146. package/src/lib/server/chat-execution.ts +378 -73
  147. package/src/lib/server/clawhub-client.test.ts +14 -8
  148. package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
  149. package/src/lib/server/connectors/manager.test.ts +1147 -0
  150. package/src/lib/server/connectors/manager.ts +461 -137
  151. package/src/lib/server/connectors/pairing.ts +26 -5
  152. package/src/lib/server/connectors/types.ts +2 -0
  153. package/src/lib/server/connectors/whatsapp.test.ts +134 -0
  154. package/src/lib/server/connectors/whatsapp.ts +271 -47
  155. package/src/lib/server/context-manager.ts +6 -1
  156. package/src/lib/server/daemon-state.ts +84 -47
  157. package/src/lib/server/data-dir.test.ts +37 -0
  158. package/src/lib/server/data-dir.ts +20 -1
  159. package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
  160. package/src/lib/server/devserver-launch.test.ts +60 -0
  161. package/src/lib/server/devserver-launch.ts +85 -0
  162. package/src/lib/server/elevenlabs.test.ts +247 -1
  163. package/src/lib/server/elevenlabs.ts +147 -43
  164. package/src/lib/server/ethereum.ts +590 -0
  165. package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
  166. package/src/lib/server/eval/agent-regression.test.ts +18 -1
  167. package/src/lib/server/eval/agent-regression.ts +383 -11
  168. package/src/lib/server/evm-swap.ts +475 -0
  169. package/src/lib/server/execution-log.ts +1 -0
  170. package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
  171. package/src/lib/server/heartbeat-service.ts +20 -11
  172. package/src/lib/server/heartbeat-wake.test.ts +112 -0
  173. package/src/lib/server/heartbeat-wake.ts +338 -57
  174. package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
  175. package/src/lib/server/main-agent-loop.test.ts +260 -0
  176. package/src/lib/server/main-agent-loop.ts +559 -14
  177. package/src/lib/server/mcp-client.test.ts +16 -0
  178. package/src/lib/server/mcp-client.ts +25 -0
  179. package/src/lib/server/memory-integration.test.ts +719 -0
  180. package/src/lib/server/memory-policy.test.ts +43 -0
  181. package/src/lib/server/memory-policy.ts +132 -0
  182. package/src/lib/server/memory-tiers.test.ts +60 -0
  183. package/src/lib/server/memory-tiers.ts +16 -0
  184. package/src/lib/server/ollama-runtime.ts +58 -0
  185. package/src/lib/server/openclaw-deploy.test.ts +109 -1
  186. package/src/lib/server/openclaw-deploy.ts +557 -81
  187. package/src/lib/server/openclaw-gateway.test.ts +131 -0
  188. package/src/lib/server/openclaw-gateway.ts +10 -4
  189. package/src/lib/server/openclaw-health.test.ts +35 -0
  190. package/src/lib/server/openclaw-health.ts +215 -47
  191. package/src/lib/server/orchestrator-lg.ts +3 -2
  192. package/src/lib/server/orchestrator.ts +2 -0
  193. package/src/lib/server/plugins-advanced.test.ts +351 -0
  194. package/src/lib/server/plugins.ts +211 -6
  195. package/src/lib/server/project-context.ts +162 -0
  196. package/src/lib/server/project-utils.ts +150 -0
  197. package/src/lib/server/queue-advanced.test.ts +528 -0
  198. package/src/lib/server/queue-followups.test.ts +409 -2
  199. package/src/lib/server/queue-reconcile.test.ts +128 -0
  200. package/src/lib/server/queue.ts +527 -68
  201. package/src/lib/server/scheduler.ts +29 -1
  202. package/src/lib/server/session-note.test.ts +36 -0
  203. package/src/lib/server/session-note.ts +42 -0
  204. package/src/lib/server/session-run-manager.ts +83 -4
  205. package/src/lib/server/session-tools/canvas.ts +14 -12
  206. package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
  207. package/src/lib/server/session-tools/connector.test.ts +138 -0
  208. package/src/lib/server/session-tools/connector.ts +366 -54
  209. package/src/lib/server/session-tools/context.ts +17 -3
  210. package/src/lib/server/session-tools/crud.ts +484 -84
  211. package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
  212. package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
  213. package/src/lib/server/session-tools/delegate.ts +102 -10
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
  215. package/src/lib/server/session-tools/discovery.ts +80 -12
  216. package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
  217. package/src/lib/server/session-tools/file.ts +43 -4
  218. package/src/lib/server/session-tools/human-loop.ts +35 -5
  219. package/src/lib/server/session-tools/index.ts +44 -9
  220. package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
  221. package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
  222. package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
  223. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
  224. package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
  225. package/src/lib/server/session-tools/memory.test.ts +93 -0
  226. package/src/lib/server/session-tools/memory.ts +554 -75
  227. package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
  228. package/src/lib/server/session-tools/platform-access.test.ts +58 -0
  229. package/src/lib/server/session-tools/platform.ts +60 -19
  230. package/src/lib/server/session-tools/plugin-creator.ts +57 -1
  231. package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
  232. package/src/lib/server/session-tools/schedule.ts +6 -1
  233. package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
  234. package/src/lib/server/session-tools/shell.ts +22 -3
  235. package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
  236. package/src/lib/server/session-tools/wallet.ts +1374 -139
  237. package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
  238. package/src/lib/server/session-tools/web.ts +621 -70
  239. package/src/lib/server/skill-discovery.ts +128 -0
  240. package/src/lib/server/skill-eligibility.test.ts +84 -0
  241. package/src/lib/server/skill-eligibility.ts +95 -0
  242. package/src/lib/server/skill-prompt-budget.test.ts +102 -0
  243. package/src/lib/server/skill-prompt-budget.ts +125 -0
  244. package/src/lib/server/skills-normalize.test.ts +54 -0
  245. package/src/lib/server/skills-normalize.ts +372 -26
  246. package/src/lib/server/solana.ts +214 -29
  247. package/src/lib/server/storage.ts +65 -36
  248. package/src/lib/server/stream-agent-chat.test.ts +437 -2
  249. package/src/lib/server/stream-agent-chat.ts +957 -79
  250. package/src/lib/server/system-events.ts +1 -1
  251. package/src/lib/server/tool-aliases.ts +2 -0
  252. package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
  253. package/src/lib/server/tool-capability-policy.test.ts +24 -0
  254. package/src/lib/server/tool-capability-policy.ts +29 -1
  255. package/src/lib/server/tool-loop-detection.test.ts +105 -0
  256. package/src/lib/server/tool-loop-detection.ts +260 -0
  257. package/src/lib/server/tool-planning.test.ts +44 -0
  258. package/src/lib/server/tool-planning.ts +271 -0
  259. package/src/lib/server/wallet-execution.test.ts +198 -0
  260. package/src/lib/server/wallet-portfolio.test.ts +98 -0
  261. package/src/lib/server/wallet-portfolio.ts +724 -0
  262. package/src/lib/server/wallet-service.test.ts +57 -0
  263. package/src/lib/server/wallet-service.ts +213 -0
  264. package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
  265. package/src/lib/server/watch-jobs.ts +17 -2
  266. package/src/lib/server/workspace-context.ts +111 -0
  267. package/src/lib/skill-save-payload.test.ts +39 -0
  268. package/src/lib/skill-save-payload.ts +37 -0
  269. package/src/lib/tasks.ts +28 -0
  270. package/src/lib/tool-definitions.ts +2 -1
  271. package/src/lib/tool-event-summary.test.ts +30 -0
  272. package/src/lib/tool-event-summary.ts +37 -0
  273. package/src/lib/validation/schemas.ts +1 -0
  274. package/src/lib/wallet-transactions.test.ts +75 -0
  275. package/src/lib/wallet-transactions.ts +43 -0
  276. package/src/lib/wallet.test.ts +17 -0
  277. package/src/lib/wallet.ts +183 -0
  278. package/src/proxy.test.ts +31 -0
  279. package/src/proxy.ts +34 -2
  280. package/src/stores/use-chat-store.ts +15 -1
  281. package/src/types/index.ts +249 -14
@@ -3,22 +3,36 @@
3
3
  import { useEffect, useCallback, useState, useRef, useMemo } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { useWs } from '@/hooks/use-ws'
6
- import { updateTask, bulkUpdateTasks } from '@/lib/tasks'
6
+ import { updateTask, bulkUpdateTasks, importGitHubIssues, type GitHubIssueImportResult } from '@/lib/tasks'
7
7
  import { TaskColumn } from './task-column'
8
8
  import { TaskCard } from './task-card'
9
9
  import { Skeleton } from '@/components/shared/skeleton'
10
10
  import { AgentAvatar } from '@/components/agents/agent-avatar'
11
+ import { BottomSheet } from '@/components/shared/bottom-sheet'
12
+ import { inputClass } from '@/components/shared/form-styles'
11
13
  import type { BoardTask, BoardTaskStatus } from '@/types'
12
14
  import { toast } from 'sonner'
13
15
 
14
16
  const ACTIVE_COLUMNS: BoardTaskStatus[] = ['backlog', 'queued', 'running', 'completed', 'failed']
15
17
  type BoardViewMode = 'board' | 'list'
16
18
  type AttentionFilter = 'all' | 'needs-attention' | 'approval' | 'blocked' | 'overdue' | 'failed'
19
+ type TaskScopeFilter = 'user-facing' | 'all' | 'agent'
17
20
 
18
21
  function isTaskOverdue(task: BoardTask): boolean {
19
22
  return !!task.dueAt && task.dueAt < Date.now() && task.status !== 'completed' && task.status !== 'archived'
20
23
  }
21
24
 
25
+ function isInternalAgentTask(task: BoardTask): boolean {
26
+ if (task.sourceType === 'schedule' || task.sourceType === 'delegation') return true
27
+ return Boolean(task.createdByAgentId || task.delegatedByAgentId)
28
+ }
29
+
30
+ function isTaskRelevantToAgent(task: BoardTask, agentId: string): boolean {
31
+ return task.agentId === agentId
32
+ || task.createdByAgentId === agentId
33
+ || task.delegatedByAgentId === agentId
34
+ }
35
+
22
36
  function matchesAttentionFilter(task: BoardTask, filter: AttentionFilter): boolean {
23
37
  const blocked = !!task.blockedBy?.length
24
38
  const pendingApproval = !!task.pendingApproval
@@ -139,8 +153,24 @@ export function TaskBoard() {
139
153
  if (typeof window === 'undefined') return ''
140
154
  return new URLSearchParams(window.location.search).get('tag') || ''
141
155
  })
156
+ const [taskScopeFilter, setTaskScopeFilter] = useState<TaskScopeFilter>(() => {
157
+ if (typeof window === 'undefined') return 'user-facing'
158
+ const params = new URLSearchParams(window.location.search)
159
+ if (params.get('agent')) return 'agent'
160
+ const raw = params.get('taskView')
161
+ return raw === 'all' ? 'all' : 'user-facing'
162
+ })
142
163
  const [viewMode, setViewMode] = useState<BoardViewMode>('board')
143
164
  const [attentionFilter, setAttentionFilter] = useState<AttentionFilter>('all')
165
+ const [githubImportOpen, setGitHubImportOpen] = useState(false)
166
+ const [githubRepo, setGitHubRepo] = useState('')
167
+ const [githubToken, setGitHubToken] = useState('')
168
+ const [githubState, setGitHubState] = useState<'open' | 'closed' | 'all'>('open')
169
+ const [githubLimit, setGitHubLimit] = useState('25')
170
+ const [githubLabels, setGitHubLabels] = useState('')
171
+ const [githubImporting, setGitHubImporting] = useState(false)
172
+ const [githubImportError, setGitHubImportError] = useState<string | null>(null)
173
+ const [githubImportResult, setGitHubImportResult] = useState<GitHubIssueImportResult | null>(null)
144
174
 
145
175
  // Seed activeProjectFilter from URL on mount
146
176
  useEffect(() => {
@@ -156,16 +186,19 @@ export function TaskBoard() {
156
186
  useEffect(() => {
157
187
  if (typeof window === 'undefined') return
158
188
  const params = new URLSearchParams()
159
- if (filterAgentId) params.set('agent', filterAgentId)
189
+ if (taskScopeFilter === 'agent' && filterAgentId) params.set('agent', filterAgentId)
190
+ else if (taskScopeFilter === 'all') params.set('taskView', 'all')
160
191
  if (filterTag) params.set('tag', filterTag)
161
192
  if (activeProjectFilter) params.set('project', activeProjectFilter)
162
193
  const qs = params.toString()
163
194
  const newUrl = `${window.location.pathname}${qs ? `?${qs}` : ''}`
164
195
  window.history.replaceState(null, '', newUrl)
165
- }, [filterAgentId, filterTag, activeProjectFilter])
196
+ }, [filterAgentId, filterTag, activeProjectFilter, taskScopeFilter])
166
197
 
167
198
  const [loaded, setLoaded] = useState(Object.keys(tasks).length > 0)
168
- useEffect(() => { Promise.all([loadTasks(), loadAgents(), loadProjects()]).then(() => setLoaded(true)) }, [])
199
+ useEffect(() => {
200
+ Promise.all([loadTasks(), loadAgents(), loadProjects()]).then(() => setLoaded(true))
201
+ }, [loadAgents, loadProjects, loadTasks])
169
202
  useWs('tasks', loadTasks, 5000)
170
203
 
171
204
  // Collect all unique tags across tasks
@@ -173,17 +206,28 @@ export function TaskBoard() {
173
206
 
174
207
  const columns: BoardTaskStatus[] = showArchived ? [...ACTIVE_COLUMNS, 'archived'] : ACTIVE_COLUMNS
175
208
 
176
- const matchesBaseFilters = useCallback((task: BoardTask) => {
209
+ const matchesScopeFilters = useCallback((task: BoardTask) => {
177
210
  if (!showArchived && task.status === 'archived') return false
178
- if (filterAgentId && task.agentId !== filterAgentId) return false
211
+ if (taskScopeFilter === 'user-facing' && isInternalAgentTask(task)) return false
212
+ if (taskScopeFilter === 'agent' && (!filterAgentId || !isTaskRelevantToAgent(task, filterAgentId))) return false
179
213
  if (filterTag && !(task.tags && task.tags.includes(filterTag))) return false
180
214
  if (activeProjectFilter && task.projectId !== activeProjectFilter) return false
215
+ return true
216
+ }, [activeProjectFilter, filterAgentId, filterTag, showArchived, taskScopeFilter])
217
+
218
+ const matchesBaseFilters = useCallback((task: BoardTask) => {
219
+ if (!matchesScopeFilters(task)) return false
181
220
  if (!matchesAttentionFilter(task, attentionFilter)) return false
182
221
  return true
183
- }, [activeProjectFilter, attentionFilter, filterAgentId, filterTag, showArchived])
222
+ }, [attentionFilter, matchesScopeFilters])
223
+
224
+ const scopedTasks = useMemo(
225
+ () => Object.values(tasks).filter(matchesScopeFilters),
226
+ [tasks, matchesScopeFilters],
227
+ )
184
228
 
185
229
  const filteredTasks = useMemo(() => (
186
- Object.values(tasks)
230
+ scopedTasks
187
231
  .filter(matchesBaseFilters)
188
232
  .sort((a, b) => {
189
233
  const rankDiff = attentionRank(a) - attentionRank(b)
@@ -192,7 +236,7 @@ export function TaskBoard() {
192
236
  if (dueDiff !== 0) return dueDiff
193
237
  return b.updatedAt - a.updatedAt
194
238
  })
195
- ), [tasks, matchesBaseFilters])
239
+ ), [scopedTasks, matchesBaseFilters])
196
240
 
197
241
  const tasksByStatus = useCallback((status: BoardTaskStatus) =>
198
242
  filteredTasks
@@ -221,9 +265,62 @@ export function TaskBoard() {
221
265
 
222
266
  const archivedCount = Object.values(tasks).filter((t) => t.status === 'archived').length
223
267
 
268
+ const resetGitHubImportState = useCallback(() => {
269
+ setGitHubImportError(null)
270
+ setGitHubImportResult(null)
271
+ }, [])
272
+
273
+ const handleGitHubImport = useCallback(async () => {
274
+ if (!githubRepo.trim()) {
275
+ setGitHubImportError('Repository is required.')
276
+ return
277
+ }
278
+
279
+ setGitHubImporting(true)
280
+ setGitHubImportError(null)
281
+ setGitHubImportResult(null)
282
+
283
+ try {
284
+ const rawLimit = Number.parseInt(githubLimit, 10)
285
+ const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 200)) : 25
286
+ const result = await importGitHubIssues({
287
+ repo: githubRepo.trim(),
288
+ token: githubToken.trim() || undefined,
289
+ state: githubState,
290
+ limit,
291
+ labels: githubLabels
292
+ .split(',')
293
+ .map((value) => value.trim())
294
+ .filter(Boolean),
295
+ projectId: activeProjectFilter,
296
+ })
297
+ setGitHubImportResult(result)
298
+ await loadTasks()
299
+ const summary = result.created.length > 0
300
+ ? `Imported ${result.created.length} issue(s) from ${result.repo}`
301
+ : `No new issues imported from ${result.repo}`
302
+ const suffix = result.skipped.length > 0 ? `, skipped ${result.skipped.length} existing` : ''
303
+ toast.success(summary + suffix)
304
+ } catch (err) {
305
+ const message = err instanceof Error ? err.message : 'GitHub import failed'
306
+ setGitHubImportError(message)
307
+ toast.error(message)
308
+ } finally {
309
+ setGitHubImporting(false)
310
+ }
311
+ }, [
312
+ activeProjectFilter,
313
+ githubLabels,
314
+ githubLimit,
315
+ githubRepo,
316
+ githubState,
317
+ githubToken,
318
+ loadTasks,
319
+ ])
320
+
224
321
  // Task counts per project (non-archived)
225
322
  const projectTaskCounts: Record<string, number> = {}
226
- for (const t of Object.values(tasks)) {
323
+ for (const t of scopedTasks) {
227
324
  if (t.projectId && t.status !== 'archived') {
228
325
  projectTaskCounts[t.projectId] = (projectTaskCounts[t.projectId] || 0) + 1
229
326
  }
@@ -231,7 +328,7 @@ export function TaskBoard() {
231
328
 
232
329
  // Summary stats
233
330
  const stats = useMemo(() => {
234
- const all = Object.values(tasks).filter((t) => t.status !== 'archived')
331
+ const all = scopedTasks.filter((t) => t.status !== 'archived')
235
332
  return {
236
333
  total: all.length,
237
334
  running: all.filter((t) => t.status === 'running').length,
@@ -242,7 +339,13 @@ export function TaskBoard() {
242
339
  approvals: all.filter((t) => !!t.pendingApproval).length,
243
340
  attention: all.filter((t) => matchesAttentionFilter(t, 'needs-attention')).length,
244
341
  }
245
- }, [tasks])
342
+ }, [scopedTasks])
343
+
344
+ const activeScopeLabel = useMemo(() => {
345
+ if (taskScopeFilter === 'all') return 'All tasks'
346
+ if (taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId]) return `${agents[filterAgentId].name} activity`
347
+ return 'User-facing tasks'
348
+ }, [agents, filterAgentId, taskScopeFilter])
246
349
 
247
350
  const activeAttentionLabel = useMemo(() => {
248
351
  if (attentionFilter === 'all') return null
@@ -301,6 +404,16 @@ export function TaskBoard() {
301
404
  <p className="text-[13px] text-text-3">
302
405
  {stats.total} task{stats.total !== 1 ? 's' : ''}
303
406
  </p>
407
+ <span className="inline-flex items-center gap-1 rounded-full bg-white/[0.04] px-2 py-1 text-[11px] font-600 text-text-2">
408
+ {taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId] ? (
409
+ <>
410
+ <AgentAvatar seed={agents[filterAgentId].avatarSeed || null} avatarUrl={agents[filterAgentId].avatarUrl} name={agents[filterAgentId].name} size={14} />
411
+ {activeScopeLabel}
412
+ </>
413
+ ) : (
414
+ activeScopeLabel
415
+ )}
416
+ </span>
304
417
  {stats.running > 0 && (
305
418
  <span className="inline-flex items-center gap-1 text-[11px] font-600 text-blue-400">
306
419
  <span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
@@ -338,41 +451,83 @@ export function TaskBoard() {
338
451
  <button
339
452
  onClick={() => setAgentDropdownOpen(!agentDropdownOpen)}
340
453
  className={`flex items-center gap-2 px-3 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
341
- ${filterAgentId
454
+ ${taskScopeFilter !== 'user-facing'
342
455
  ? 'bg-white/[0.06] border-white/[0.1] text-text-2'
343
456
  : 'bg-transparent border-white/[0.06] text-text-3 hover:bg-white/[0.03]'}`}
344
457
  style={{ fontFamily: 'inherit', minWidth: 130 }}
345
458
  >
346
- {filterAgentId && agents[filterAgentId] ? (
459
+ {taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId] ? (
347
460
  <>
348
461
  <AgentAvatar seed={agents[filterAgentId].avatarSeed || null} avatarUrl={agents[filterAgentId].avatarUrl} name={agents[filterAgentId].name} size={18} />
349
462
  {agents[filterAgentId].name}
350
463
  </>
351
- ) : 'All Agents'}
464
+ ) : taskScopeFilter === 'all' ? 'All Tasks' : 'User View'}
352
465
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none" className="ml-auto opacity-50">
353
466
  <path d="M2.5 4L5 6.5L7.5 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
354
467
  </svg>
355
468
  </button>
356
469
  {agentDropdownOpen && (
357
- <div className="absolute top-full right-0 mt-1 min-w-[200px] py-1 rounded-[12px] border border-white/[0.08] bg-surface-2 shadow-lg z-50">
470
+ <div className="absolute top-full right-0 mt-1 min-w-[240px] py-1 rounded-[12px] border border-white/[0.08] bg-surface-2 shadow-lg z-50">
471
+ <button
472
+ onClick={() => {
473
+ setTaskScopeFilter('user-facing')
474
+ setFilterAgentId('')
475
+ setAgentDropdownOpen(false)
476
+ }}
477
+ className={`w-full flex items-start gap-2.5 px-3 py-2.5 text-[13px] font-600 cursor-pointer border-none text-left transition-colors
478
+ ${taskScopeFilter === 'user-facing' ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
479
+ style={{ fontFamily: 'inherit' }}
480
+ >
481
+ <span className="mt-0.5 inline-flex h-5 items-center rounded-full bg-emerald-500/12 px-1.5 text-[10px] font-700 uppercase tracking-[0.08em] text-emerald-400">
482
+ Default
483
+ </span>
484
+ <span className="min-w-0">
485
+ <span className="block">User-facing tasks</span>
486
+ <span className="mt-0.5 block text-[11px] font-500 text-text-3/60">
487
+ Hide scheduled, delegated, and agent-created internal work.
488
+ </span>
489
+ </span>
490
+ </button>
358
491
  <button
359
- onClick={() => { setFilterAgentId(''); setAgentDropdownOpen(false) }}
360
- className={`w-full flex items-center gap-2.5 px-3 py-2 text-[13px] font-600 cursor-pointer border-none text-left transition-colors
361
- ${!filterAgentId ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
492
+ onClick={() => {
493
+ setTaskScopeFilter('all')
494
+ setFilterAgentId('')
495
+ setAgentDropdownOpen(false)
496
+ }}
497
+ className={`w-full flex items-start gap-2.5 px-3 py-2.5 text-[13px] font-600 cursor-pointer border-none text-left transition-colors
498
+ ${taskScopeFilter === 'all' ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
362
499
  style={{ fontFamily: 'inherit' }}
363
500
  >
364
- All Agents
501
+ <span className="mt-0.5 inline-flex h-5 items-center rounded-full bg-white/[0.06] px-1.5 text-[10px] font-700 uppercase tracking-[0.08em] text-text-3">
502
+ All
503
+ </span>
504
+ <span className="min-w-0">
505
+ <span className="block">All tasks</span>
506
+ <span className="mt-0.5 block text-[11px] font-500 text-text-3/60">
507
+ Include internal agent execution, schedules, and delegations.
508
+ </span>
509
+ </span>
365
510
  </button>
511
+ <div className="my-1 border-t border-white/[0.06]" />
366
512
  {Object.values(agents).sort((a, b) => a.name.localeCompare(b.name)).map((a) => (
367
513
  <button
368
514
  key={a.id}
369
- onClick={() => { setFilterAgentId(a.id); setAgentDropdownOpen(false) }}
515
+ onClick={() => {
516
+ setTaskScopeFilter('agent')
517
+ setFilterAgentId(a.id)
518
+ setAgentDropdownOpen(false)
519
+ }}
370
520
  className={`w-full flex items-center gap-2.5 px-3 py-2 text-[13px] font-600 cursor-pointer border-none text-left transition-colors
371
- ${filterAgentId === a.id ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
521
+ ${taskScopeFilter === 'agent' && filterAgentId === a.id ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
372
522
  style={{ fontFamily: 'inherit' }}
373
523
  >
374
524
  <AgentAvatar seed={a.avatarSeed || null} avatarUrl={a.avatarUrl} name={a.name} size={20} />
375
- {a.name}
525
+ <span className="min-w-0 flex-1">
526
+ <span className="block truncate">{a.name}</span>
527
+ <span className="mt-0.5 block text-[11px] font-500 text-text-3/60">
528
+ Assigned, created, or delegated by this agent
529
+ </span>
530
+ </span>
376
531
  </button>
377
532
  ))}
378
533
  </div>
@@ -451,6 +606,16 @@ export function TaskBoard() {
451
606
  >
452
607
  {showArchived ? 'Hide' : 'Show'} Archived{!showArchived && archivedCount > 0 ? ` (${archivedCount})` : ''}
453
608
  </button>
609
+ <button
610
+ onClick={() => {
611
+ resetGitHubImportState()
612
+ setGitHubImportOpen(true)
613
+ }}
614
+ className="px-4 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border border-white/[0.08] bg-white/[0.04] text-text-2 hover:bg-white/[0.08]"
615
+ style={{ fontFamily: 'inherit' }}
616
+ >
617
+ Import GitHub
618
+ </button>
454
619
  <button
455
620
  onClick={() => {
456
621
  setEditingTaskId(null)
@@ -520,8 +685,33 @@ export function TaskBoard() {
520
685
  ))}
521
686
  </div>
522
687
 
523
- {(activeProjectFilter && projects[activeProjectFilter]) || activeAttentionLabel ? (
688
+ {(activeProjectFilter && projects[activeProjectFilter]) || activeAttentionLabel || taskScopeFilter !== 'all' ? (
524
689
  <div className="flex flex-wrap items-center gap-2 px-8 pb-3">
690
+ {taskScopeFilter !== 'all' && (
691
+ <span className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] border text-[12px] font-600 ${
692
+ taskScopeFilter === 'agent'
693
+ ? 'bg-accent-soft border-accent-bright/20 text-accent-bright'
694
+ : 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400'
695
+ }`}>
696
+ {taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId] ? (
697
+ <>
698
+ <AgentAvatar seed={agents[filterAgentId].avatarSeed || null} avatarUrl={agents[filterAgentId].avatarUrl} name={agents[filterAgentId].name} size={14} />
699
+ {agents[filterAgentId].name} activity
700
+ </>
701
+ ) : (
702
+ 'User-facing tasks'
703
+ )}
704
+ <button
705
+ onClick={() => {
706
+ setTaskScopeFilter('all')
707
+ setFilterAgentId('')
708
+ }}
709
+ className="ml-1 cursor-pointer border-none bg-transparent p-0 text-[14px] leading-none text-current opacity-80 hover:opacity-100"
710
+ >
711
+ &times;
712
+ </button>
713
+ </span>
714
+ )}
525
715
  {activeProjectFilter && projects[activeProjectFilter] && (
526
716
  <span className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] bg-white/[0.04] border border-white/[0.06] text-[12px] font-600 text-text-2">
527
717
  <span className="w-2 h-2 rounded-full" style={{ backgroundColor: projects[activeProjectFilter].color || '#6366F1' }} />
@@ -593,7 +783,7 @@ export function TaskBoard() {
593
783
  ) : filteredTasks.length === 0 ? (
594
784
  <div className="max-w-3xl mx-auto rounded-[16px] border border-dashed border-white/[0.08] px-6 py-14 text-center">
595
785
  <p className="text-[14px] font-600 text-text-2 mb-1">No tasks match this view</p>
596
- <p className="text-[12px] text-text-3/60">Try clearing one of the active filters or switching back to the full board.</p>
786
+ <p className="text-[12px] text-text-3/60">Try clearing one of the active filters or switching back to all tasks.</p>
597
787
  </div>
598
788
  ) : (
599
789
  <div className="max-w-4xl mx-auto">
@@ -629,6 +819,201 @@ export function TaskBoard() {
629
819
  </div>
630
820
  )}
631
821
 
822
+ <BottomSheet
823
+ open={githubImportOpen}
824
+ onClose={() => {
825
+ setGitHubImportOpen(false)
826
+ setGitHubImportError(null)
827
+ }}
828
+ wide
829
+ title="Import GitHub Issues"
830
+ description="Pull issues from a GitHub repository into the task board as backlog items."
831
+ >
832
+ <div className="mx-auto w-full max-w-2xl">
833
+ <div className="mb-6">
834
+ <h2 className="font-display text-[24px] font-800 tracking-[-0.03em] text-text">Import GitHub Issues</h2>
835
+ <p className="mt-2 text-[14px] text-text-3">
836
+ Pull issues from a GitHub repository into the task board as backlog items.
837
+ </p>
838
+ </div>
839
+
840
+ <div className="grid gap-4 sm:grid-cols-[minmax(0,1fr)_180px]">
841
+ <label className="block">
842
+ <span className="mb-2 block text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/70">Repository</span>
843
+ <input
844
+ value={githubRepo}
845
+ onChange={(e) => setGitHubRepo(e.target.value)}
846
+ placeholder="owner/repo or https://github.com/owner/repo"
847
+ className={inputClass}
848
+ style={{ fontFamily: 'inherit' }}
849
+ />
850
+ </label>
851
+ <label className="block">
852
+ <span className="mb-2 block text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/70">State</span>
853
+ <select
854
+ value={githubState}
855
+ onChange={(e) => setGitHubState(e.target.value as 'open' | 'closed' | 'all')}
856
+ className={inputClass}
857
+ style={{ fontFamily: 'inherit' }}
858
+ >
859
+ <option value="open">Open issues</option>
860
+ <option value="closed">Closed issues</option>
861
+ <option value="all">All issues</option>
862
+ </select>
863
+ </label>
864
+ </div>
865
+
866
+ <div className="mt-4 grid gap-4 sm:grid-cols-2">
867
+ <label className="block">
868
+ <span className="mb-2 block text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/70">Limit</span>
869
+ <input
870
+ type="number"
871
+ min={1}
872
+ max={200}
873
+ value={githubLimit}
874
+ onChange={(e) => setGitHubLimit(e.target.value)}
875
+ className={inputClass}
876
+ style={{ fontFamily: 'inherit' }}
877
+ />
878
+ </label>
879
+ <label className="block">
880
+ <span className="mb-2 block text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/70">Labels</span>
881
+ <input
882
+ value={githubLabels}
883
+ onChange={(e) => setGitHubLabels(e.target.value)}
884
+ placeholder="bug, api, high priority"
885
+ className={inputClass}
886
+ style={{ fontFamily: 'inherit' }}
887
+ />
888
+ </label>
889
+ </div>
890
+
891
+ <label className="mt-4 block">
892
+ <span className="mb-2 block text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/70">GitHub token</span>
893
+ <input
894
+ type="password"
895
+ value={githubToken}
896
+ onChange={(e) => setGitHubToken(e.target.value)}
897
+ placeholder="Optional. Needed for private repos or higher rate limits."
898
+ className={inputClass}
899
+ style={{ fontFamily: 'inherit' }}
900
+ />
901
+ </label>
902
+
903
+ <div className="mt-3 flex flex-wrap items-center gap-2">
904
+ <span className="inline-flex items-center rounded-full bg-white/[0.05] px-3 py-1 text-[12px] font-600 text-text-2">
905
+ Imported tasks land in backlog
906
+ </span>
907
+ {activeProjectFilter && projects[activeProjectFilter] && (
908
+ <span className="inline-flex items-center gap-1.5 rounded-full bg-accent-soft px-3 py-1 text-[12px] font-600 text-accent-bright">
909
+ <span className="h-2 w-2 rounded-full" style={{ backgroundColor: projects[activeProjectFilter].color || '#6366F1' }} />
910
+ Project: {projects[activeProjectFilter].name}
911
+ </span>
912
+ )}
913
+ </div>
914
+
915
+ {githubImportError && (
916
+ <div className="mt-4 rounded-[14px] border border-red-500/20 bg-red-500/10 px-4 py-3 text-[13px] text-red-200">
917
+ {githubImportError}
918
+ </div>
919
+ )}
920
+
921
+ {githubImportResult && (
922
+ <div className="mt-5 rounded-[18px] border border-white/[0.08] bg-white/[0.03] p-4">
923
+ <div className="flex flex-wrap items-center gap-2">
924
+ <span className="text-[14px] font-700 text-text">{githubImportResult.repo}</span>
925
+ <span className="rounded-full bg-white/[0.05] px-2 py-1 text-[11px] font-600 text-text-3">
926
+ {githubImportResult.fetched} fetched
927
+ </span>
928
+ <span className="rounded-full bg-emerald-500/10 px-2 py-1 text-[11px] font-600 text-emerald-300">
929
+ {githubImportResult.created.length} created
930
+ </span>
931
+ <span className="rounded-full bg-amber-500/10 px-2 py-1 text-[11px] font-600 text-amber-300">
932
+ {githubImportResult.skipped.length} skipped
933
+ </span>
934
+ </div>
935
+
936
+ {githubImportResult.created.length > 0 && (
937
+ <div className="mt-4">
938
+ <p className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Created</p>
939
+ <div className="mt-2 flex flex-col gap-2">
940
+ {githubImportResult.created.slice(0, 8).map((item) => (
941
+ item.url ? (
942
+ <a
943
+ key={`created-${item.taskId || item.number}`}
944
+ href={item.url}
945
+ target="_blank"
946
+ rel="noreferrer"
947
+ className="rounded-[12px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[13px] text-text-2 no-underline transition-colors hover:bg-white/[0.05]"
948
+ >
949
+ #{item.number} {item.title}
950
+ </a>
951
+ ) : (
952
+ <div
953
+ key={`created-${item.taskId || item.number}`}
954
+ className="rounded-[12px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[13px] text-text-2"
955
+ >
956
+ #{item.number} {item.title}
957
+ </div>
958
+ )
959
+ ))}
960
+ </div>
961
+ </div>
962
+ )}
963
+
964
+ {githubImportResult.skipped.length > 0 && (
965
+ <div className="mt-4">
966
+ <p className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/60">Skipped existing</p>
967
+ <div className="mt-2 flex flex-col gap-2">
968
+ {githubImportResult.skipped.slice(0, 8).map((item) => (
969
+ item.url ? (
970
+ <a
971
+ key={`skipped-${item.taskId || item.number}`}
972
+ href={item.url}
973
+ target="_blank"
974
+ rel="noreferrer"
975
+ className="rounded-[12px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[13px] text-text-3 no-underline transition-colors hover:bg-white/[0.05] hover:text-text-2"
976
+ >
977
+ #{item.number} {item.title}
978
+ </a>
979
+ ) : (
980
+ <div
981
+ key={`skipped-${item.taskId || item.number}`}
982
+ className="rounded-[12px] border border-white/[0.06] bg-white/[0.02] px-3 py-2 text-[13px] text-text-3"
983
+ >
984
+ #{item.number} {item.title}
985
+ </div>
986
+ )
987
+ ))}
988
+ </div>
989
+ </div>
990
+ )}
991
+ </div>
992
+ )}
993
+
994
+ <div className="mt-6 flex items-center justify-end gap-3">
995
+ <button
996
+ onClick={() => {
997
+ setGitHubImportOpen(false)
998
+ setGitHubImportError(null)
999
+ }}
1000
+ className="px-4 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-[13px] font-600 text-text-3 transition-colors hover:bg-white/[0.04] hover:text-text-2"
1001
+ style={{ fontFamily: 'inherit' }}
1002
+ >
1003
+ Close
1004
+ </button>
1005
+ <button
1006
+ onClick={handleGitHubImport}
1007
+ disabled={githubImporting}
1008
+ className="px-5 py-2.5 rounded-[12px] border-none bg-accent-bright text-white text-[14px] font-700 transition-all disabled:cursor-not-allowed disabled:opacity-60"
1009
+ style={{ fontFamily: 'inherit' }}
1010
+ >
1011
+ {githubImporting ? 'Importing...' : 'Import issues'}
1012
+ </button>
1013
+ </div>
1014
+ </div>
1015
+ </BottomSheet>
1016
+
632
1017
  {/* Bulk action bar */}
633
1018
  {selectionMode && (
634
1019
  <div className="absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-2 px-4 py-3 rounded-[16px] bg-surface-2/95 backdrop-blur-xl border border-white/[0.1] shadow-[0_8px_40px_rgba(0,0,0,0.5)] z-50">