@swarmclawai/swarmclaw 0.7.8 → 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 (251) hide show
  1. package/README.md +12 -15
  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 +22 -2
  15. package/src/app/api/clawhub/install/route.ts +28 -8
  16. package/src/app/api/connectors/[id]/route.ts +26 -1
  17. package/src/app/api/external-agents/route.test.ts +165 -0
  18. package/src/app/api/gateways/[id]/health/route.ts +27 -12
  19. package/src/app/api/gateways/[id]/route.ts +2 -0
  20. package/src/app/api/gateways/health-route.test.ts +135 -0
  21. package/src/app/api/gateways/route.ts +2 -0
  22. package/src/app/api/mcp-servers/route.test.ts +130 -0
  23. package/src/app/api/openclaw/deploy/route.ts +38 -5
  24. package/src/app/api/plugins/install/route.ts +46 -6
  25. package/src/app/api/plugins/marketplace/route.ts +48 -15
  26. package/src/app/api/preview-server/route.ts +26 -11
  27. package/src/app/api/schedules/[id]/run/route.ts +4 -0
  28. package/src/app/api/schedules/route.test.ts +86 -0
  29. package/src/app/api/schedules/route.ts +6 -1
  30. package/src/app/api/setup/check-provider/route.test.ts +19 -0
  31. package/src/app/api/setup/check-provider/route.ts +40 -10
  32. package/src/app/api/skills/[id]/route.ts +12 -0
  33. package/src/app/api/skills/import/route.ts +14 -12
  34. package/src/app/api/skills/route.ts +13 -1
  35. package/src/app/api/tasks/[id]/route.ts +10 -1
  36. package/src/app/api/tasks/import/github/route.test.ts +65 -0
  37. package/src/app/api/tasks/import/github/route.ts +337 -0
  38. package/src/app/api/wallets/[id]/approve/route.ts +17 -3
  39. package/src/app/api/wallets/[id]/route.ts +79 -33
  40. package/src/app/api/wallets/[id]/send/route.ts +19 -33
  41. package/src/app/api/wallets/route.ts +78 -61
  42. package/src/app/api/webhooks/[id]/route.ts +33 -6
  43. package/src/app/api/webhooks/route.test.ts +272 -0
  44. package/src/cli/index.js +1 -0
  45. package/src/cli/spec.js +1 -0
  46. package/src/components/agents/agent-card.tsx +9 -2
  47. package/src/components/agents/agent-chat-list.tsx +18 -2
  48. package/src/components/agents/agent-list.tsx +1 -0
  49. package/src/components/agents/agent-sheet.tsx +73 -24
  50. package/src/components/agents/inspector-panel.tsx +41 -0
  51. package/src/components/canvas/canvas-panel.tsx +236 -65
  52. package/src/components/chat/chat-card.tsx +36 -13
  53. package/src/components/chat/chat-header.tsx +44 -16
  54. package/src/components/chat/chat-list.tsx +28 -4
  55. package/src/components/chat/checkpoint-timeline.tsx +50 -34
  56. package/src/components/chat/message-bubble.tsx +208 -145
  57. package/src/components/chat/message-list.tsx +48 -19
  58. package/src/components/chatrooms/chatroom-message.tsx +2 -2
  59. package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
  60. package/src/components/connectors/connector-health.tsx +1 -1
  61. package/src/components/connectors/connector-list.tsx +7 -2
  62. package/src/components/connectors/connector-sheet.tsx +337 -148
  63. package/src/components/gateways/gateway-sheet.tsx +2 -2
  64. package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
  65. package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
  66. package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
  67. package/src/components/plugins/plugin-list.tsx +45 -9
  68. package/src/components/plugins/plugin-sheet.tsx +55 -7
  69. package/src/components/providers/provider-list.tsx +2 -1
  70. package/src/components/providers/provider-sheet.tsx +21 -2
  71. package/src/components/schedules/schedule-card.tsx +25 -1
  72. package/src/components/schedules/schedule-sheet.tsx +44 -2
  73. package/src/components/secrets/secret-sheet.tsx +21 -2
  74. package/src/components/shared/agent-switch-dialog.tsx +12 -1
  75. package/src/components/shared/bottom-sheet.tsx +13 -3
  76. package/src/components/shared/command-palette.tsx +8 -1
  77. package/src/components/shared/confirm-dialog.tsx +19 -4
  78. package/src/components/shared/connector-platform-icon.test.ts +28 -0
  79. package/src/components/shared/connector-platform-icon.tsx +39 -6
  80. package/src/components/shared/settings/plugin-manager.tsx +29 -6
  81. package/src/components/shared/settings/section-capability-policy.tsx +7 -3
  82. package/src/components/skills/skill-list.tsx +25 -0
  83. package/src/components/skills/skill-sheet.tsx +84 -12
  84. package/src/components/tasks/approvals-panel.tsx +191 -95
  85. package/src/components/tasks/task-board.tsx +273 -2
  86. package/src/components/tasks/task-card.tsx +38 -9
  87. package/src/components/ui/dialog.tsx +2 -2
  88. package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
  89. package/src/components/wallets/wallet-panel.tsx +435 -90
  90. package/src/components/wallets/wallet-section.tsx +198 -48
  91. package/src/components/webhooks/webhook-sheet.tsx +22 -2
  92. package/src/lib/approval-display.ts +20 -0
  93. package/src/lib/canvas-content.ts +198 -0
  94. package/src/lib/chat-artifact-summary.ts +165 -0
  95. package/src/lib/chat-display.test.ts +91 -0
  96. package/src/lib/chat-display.ts +58 -0
  97. package/src/lib/chat-streaming-state.test.ts +47 -1
  98. package/src/lib/chat-streaming-state.ts +42 -0
  99. package/src/lib/ollama-model.ts +10 -0
  100. package/src/lib/openclaw-endpoint.test.ts +8 -0
  101. package/src/lib/openclaw-endpoint.ts +6 -1
  102. package/src/lib/plugin-install-cors.ts +46 -0
  103. package/src/lib/plugin-sources.test.ts +43 -0
  104. package/src/lib/plugin-sources.ts +77 -0
  105. package/src/lib/providers/ollama.ts +16 -6
  106. package/src/lib/providers/openclaw.test.ts +54 -0
  107. package/src/lib/providers/openclaw.ts +127 -11
  108. package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
  109. package/src/lib/schedule-dedupe.test.ts +66 -1
  110. package/src/lib/schedule-dedupe.ts +169 -12
  111. package/src/lib/schedule-origin.test.ts +20 -0
  112. package/src/lib/schedule-origin.ts +15 -0
  113. package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
  114. package/src/lib/server/agent-availability.ts +16 -0
  115. package/src/lib/server/agent-runtime-config.ts +12 -4
  116. package/src/lib/server/agent-thread-session.test.ts +51 -0
  117. package/src/lib/server/agent-thread-session.ts +7 -0
  118. package/src/lib/server/approval-match.ts +205 -0
  119. package/src/lib/server/approvals-auto-approve.test.ts +538 -1
  120. package/src/lib/server/approvals.ts +214 -1
  121. package/src/lib/server/assistant-control.test.ts +29 -0
  122. package/src/lib/server/assistant-control.ts +23 -0
  123. package/src/lib/server/build-llm.test.ts +79 -0
  124. package/src/lib/server/build-llm.ts +14 -4
  125. package/src/lib/server/canvas-content.test.ts +32 -0
  126. package/src/lib/server/canvas-content.ts +6 -0
  127. package/src/lib/server/capability-router.test.ts +11 -0
  128. package/src/lib/server/capability-router.ts +26 -1
  129. package/src/lib/server/chat-execution-advanced.test.ts +651 -0
  130. package/src/lib/server/chat-execution-disabled.test.ts +94 -0
  131. package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
  132. package/src/lib/server/chat-execution.ts +353 -72
  133. package/src/lib/server/clawhub-client.test.ts +14 -8
  134. package/src/lib/server/connectors/manager.test.ts +1147 -0
  135. package/src/lib/server/connectors/manager.ts +362 -63
  136. package/src/lib/server/connectors/pairing.ts +26 -5
  137. package/src/lib/server/connectors/types.ts +2 -0
  138. package/src/lib/server/connectors/whatsapp.test.ts +134 -0
  139. package/src/lib/server/connectors/whatsapp.ts +271 -47
  140. package/src/lib/server/context-manager.ts +6 -1
  141. package/src/lib/server/daemon-state.ts +1 -1
  142. package/src/lib/server/data-dir.test.ts +37 -0
  143. package/src/lib/server/data-dir.ts +20 -1
  144. package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
  145. package/src/lib/server/devserver-launch.test.ts +60 -0
  146. package/src/lib/server/devserver-launch.ts +85 -0
  147. package/src/lib/server/elevenlabs.test.ts +189 -1
  148. package/src/lib/server/elevenlabs.ts +147 -43
  149. package/src/lib/server/ethereum.ts +590 -0
  150. package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
  151. package/src/lib/server/eval/agent-regression.test.ts +18 -1
  152. package/src/lib/server/eval/agent-regression.ts +383 -11
  153. package/src/lib/server/evm-swap.ts +475 -0
  154. package/src/lib/server/execution-log.ts +1 -0
  155. package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
  156. package/src/lib/server/heartbeat-service.ts +15 -10
  157. package/src/lib/server/heartbeat-wake.test.ts +112 -0
  158. package/src/lib/server/heartbeat-wake.ts +338 -57
  159. package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
  160. package/src/lib/server/mcp-client.test.ts +16 -0
  161. package/src/lib/server/mcp-client.ts +25 -0
  162. package/src/lib/server/memory-integration.test.ts +719 -0
  163. package/src/lib/server/memory-policy.test.ts +43 -0
  164. package/src/lib/server/memory-policy.ts +132 -0
  165. package/src/lib/server/memory-tiers.test.ts +60 -0
  166. package/src/lib/server/memory-tiers.ts +16 -0
  167. package/src/lib/server/ollama-runtime.ts +58 -0
  168. package/src/lib/server/openclaw-deploy.test.ts +109 -1
  169. package/src/lib/server/openclaw-deploy.ts +557 -81
  170. package/src/lib/server/openclaw-gateway.test.ts +131 -0
  171. package/src/lib/server/openclaw-gateway.ts +10 -4
  172. package/src/lib/server/openclaw-health.test.ts +35 -0
  173. package/src/lib/server/openclaw-health.ts +215 -47
  174. package/src/lib/server/orchestrator-lg.ts +2 -2
  175. package/src/lib/server/plugins-advanced.test.ts +351 -0
  176. package/src/lib/server/plugins.ts +205 -5
  177. package/src/lib/server/queue-advanced.test.ts +528 -0
  178. package/src/lib/server/queue-followups.test.ts +262 -0
  179. package/src/lib/server/queue-reconcile.test.ts +128 -0
  180. package/src/lib/server/queue.ts +293 -61
  181. package/src/lib/server/scheduler.ts +29 -1
  182. package/src/lib/server/session-note.test.ts +36 -0
  183. package/src/lib/server/session-note.ts +42 -0
  184. package/src/lib/server/session-run-manager.ts +52 -4
  185. package/src/lib/server/session-tools/canvas.ts +14 -12
  186. package/src/lib/server/session-tools/connector.test.ts +138 -0
  187. package/src/lib/server/session-tools/connector.ts +348 -61
  188. package/src/lib/server/session-tools/context.ts +12 -3
  189. package/src/lib/server/session-tools/crud.ts +221 -10
  190. package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
  191. package/src/lib/server/session-tools/delegate.ts +64 -8
  192. package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
  193. package/src/lib/server/session-tools/discovery.ts +80 -12
  194. package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
  195. package/src/lib/server/session-tools/file.ts +43 -4
  196. package/src/lib/server/session-tools/human-loop.ts +35 -5
  197. package/src/lib/server/session-tools/index.ts +44 -9
  198. package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
  199. package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
  200. package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
  201. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
  202. package/src/lib/server/session-tools/memory.test.ts +93 -0
  203. package/src/lib/server/session-tools/memory.ts +546 -79
  204. package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
  205. package/src/lib/server/session-tools/plugin-creator.ts +57 -1
  206. package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
  207. package/src/lib/server/session-tools/schedule.ts +6 -1
  208. package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
  209. package/src/lib/server/session-tools/shell.ts +22 -3
  210. package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
  211. package/src/lib/server/session-tools/wallet.ts +1374 -139
  212. package/src/lib/server/session-tools/web-inputs.test.ts +162 -1
  213. package/src/lib/server/session-tools/web.ts +468 -64
  214. package/src/lib/server/skill-discovery.ts +128 -0
  215. package/src/lib/server/skill-eligibility.test.ts +84 -0
  216. package/src/lib/server/skill-eligibility.ts +95 -0
  217. package/src/lib/server/skill-prompt-budget.test.ts +102 -0
  218. package/src/lib/server/skill-prompt-budget.ts +125 -0
  219. package/src/lib/server/skills-normalize.test.ts +54 -0
  220. package/src/lib/server/skills-normalize.ts +372 -26
  221. package/src/lib/server/solana.ts +214 -29
  222. package/src/lib/server/storage.ts +65 -36
  223. package/src/lib/server/stream-agent-chat.test.ts +419 -9
  224. package/src/lib/server/stream-agent-chat.ts +887 -83
  225. package/src/lib/server/system-events.ts +1 -1
  226. package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
  227. package/src/lib/server/tool-loop-detection.test.ts +105 -0
  228. package/src/lib/server/tool-loop-detection.ts +260 -0
  229. package/src/lib/server/tool-planning.ts +4 -2
  230. package/src/lib/server/wallet-execution.test.ts +198 -0
  231. package/src/lib/server/wallet-portfolio.test.ts +98 -0
  232. package/src/lib/server/wallet-portfolio.ts +724 -0
  233. package/src/lib/server/wallet-service.test.ts +57 -0
  234. package/src/lib/server/wallet-service.ts +213 -0
  235. package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
  236. package/src/lib/server/watch-jobs.ts +17 -2
  237. package/src/lib/server/workspace-context.ts +111 -0
  238. package/src/lib/skill-save-payload.test.ts +39 -0
  239. package/src/lib/skill-save-payload.ts +37 -0
  240. package/src/lib/tasks.ts +28 -0
  241. package/src/lib/tool-event-summary.test.ts +30 -0
  242. package/src/lib/tool-event-summary.ts +37 -0
  243. package/src/lib/validation/schemas.ts +1 -0
  244. package/src/lib/wallet-transactions.test.ts +75 -0
  245. package/src/lib/wallet-transactions.ts +43 -0
  246. package/src/lib/wallet.test.ts +17 -0
  247. package/src/lib/wallet.ts +183 -0
  248. package/src/proxy.test.ts +31 -0
  249. package/src/proxy.ts +34 -2
  250. package/src/stores/use-chat-store.ts +15 -1
  251. package/src/types/index.ts +210 -14
@@ -3,11 +3,13 @@
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
 
@@ -160,6 +162,15 @@ export function TaskBoard() {
160
162
  })
161
163
  const [viewMode, setViewMode] = useState<BoardViewMode>('board')
162
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)
163
174
 
164
175
  // Seed activeProjectFilter from URL on mount
165
176
  useEffect(() => {
@@ -185,7 +196,9 @@ export function TaskBoard() {
185
196
  }, [filterAgentId, filterTag, activeProjectFilter, taskScopeFilter])
186
197
 
187
198
  const [loaded, setLoaded] = useState(Object.keys(tasks).length > 0)
188
- useEffect(() => { Promise.all([loadTasks(), loadAgents(), loadProjects()]).then(() => setLoaded(true)) }, [])
199
+ useEffect(() => {
200
+ Promise.all([loadTasks(), loadAgents(), loadProjects()]).then(() => setLoaded(true))
201
+ }, [loadAgents, loadProjects, loadTasks])
189
202
  useWs('tasks', loadTasks, 5000)
190
203
 
191
204
  // Collect all unique tags across tasks
@@ -252,6 +265,59 @@ export function TaskBoard() {
252
265
 
253
266
  const archivedCount = Object.values(tasks).filter((t) => t.status === 'archived').length
254
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
+
255
321
  // Task counts per project (non-archived)
256
322
  const projectTaskCounts: Record<string, number> = {}
257
323
  for (const t of scopedTasks) {
@@ -540,6 +606,16 @@ export function TaskBoard() {
540
606
  >
541
607
  {showArchived ? 'Hide' : 'Show'} Archived{!showArchived && archivedCount > 0 ? ` (${archivedCount})` : ''}
542
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>
543
619
  <button
544
620
  onClick={() => {
545
621
  setEditingTaskId(null)
@@ -743,6 +819,201 @@ export function TaskBoard() {
743
819
  </div>
744
820
  )}
745
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
+
746
1017
  {/* Bulk action bar */}
747
1018
  {selectionMode && (
748
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">
@@ -8,8 +8,8 @@ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
8
8
  import { AgentAvatar } from '@/components/agents/agent-avatar'
9
9
  import type { BoardTask } from '@/types'
10
10
 
11
- function timeAgo(ts: number) {
12
- const diff = Date.now() - ts
11
+ function timeAgo(ts: number, now: number) {
12
+ const diff = now - ts
13
13
  if (diff < 60_000) return 'just now'
14
14
  if (diff < 3600_000) return `${Math.floor(diff / 60_000)}m ago`
15
15
  if (diff < 86400_000) return `${Math.floor(diff / 3600_000)}h ago`
@@ -35,13 +35,21 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
35
35
  const [dragging, setDragging] = useState(false)
36
36
  const [confirmArchive, setConfirmArchive] = useState(false)
37
37
  const [allowDrag, setAllowDrag] = useState(false)
38
+ const [now, setNow] = useState(() => Date.now())
38
39
 
39
40
  useEffect(() => {
40
41
  if (typeof window === 'undefined') return
41
- const isCoarsePointer = typeof window.matchMedia === 'function'
42
- ? window.matchMedia('(pointer: coarse)').matches
43
- : 'ontouchstart' in window
44
- setAllowDrag(!isCoarsePointer)
42
+ const frame = window.requestAnimationFrame(() => {
43
+ const isCoarsePointer = typeof window.matchMedia === 'function'
44
+ ? window.matchMedia('(pointer: coarse)').matches
45
+ : 'ontouchstart' in window
46
+ setAllowDrag(!isCoarsePointer)
47
+ })
48
+ const timer = window.setInterval(() => setNow(Date.now()), 60_000)
49
+ return () => {
50
+ window.cancelAnimationFrame(frame)
51
+ window.clearInterval(timer)
52
+ }
45
53
  }, [])
46
54
 
47
55
  const tasks = useAppStore((s) => s.tasks)
@@ -49,6 +57,7 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
49
57
  const project = task.projectId ? projects[task.projectId] : null
50
58
  const creatorAgent = task.createdByAgentId ? agents[task.createdByAgentId] : null
51
59
  const delegatorAgent = task.delegatedByAgentId ? agents[task.delegatedByAgentId] : null
60
+ const githubSource = task.externalSource?.source === 'github' ? task.externalSource : null
52
61
 
53
62
  const priorityConfig = {
54
63
  critical: { label: 'Critical', cls: 'bg-red-500/10 text-red-400' },
@@ -59,7 +68,7 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
59
68
  const prio = task.priority && priorityConfig[task.priority]
60
69
 
61
70
  const isBlocked = Array.isArray(task.blockedBy) && task.blockedBy.length > 0
62
- const isOverdue = task.dueAt && task.dueAt < Date.now() && task.status !== 'completed' && task.status !== 'archived'
71
+ const isOverdue = task.dueAt && task.dueAt < now && task.status !== 'completed' && task.status !== 'archived'
63
72
  const borderColor = isBlocked ? 'border-l-rose-500'
64
73
  : task.pendingApproval ? 'border-l-amber-500'
65
74
  : task.status === 'running' ? 'border-l-emerald-500'
@@ -210,7 +219,7 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
210
219
  </div>
211
220
  )}
212
221
 
213
- {(creatorAgent || delegatorAgent || task.sourceType === 'schedule') && (
222
+ {(creatorAgent || delegatorAgent || task.sourceType === 'schedule' || githubSource) && (
214
223
  <div className="flex flex-wrap gap-1.5 mb-3">
215
224
  {delegatorAgent && (
216
225
  <span className="inline-flex items-center gap-1.5 rounded-[7px] bg-amber-500/10 px-2 py-1 text-[10px] font-600 text-amber-300">
@@ -233,6 +242,26 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
233
242
  {task.sourceScheduleName ? `Scheduled via ${task.sourceScheduleName}` : 'Scheduled task'}
234
243
  </span>
235
244
  )}
245
+ {githubSource && (
246
+ githubSource.url ? (
247
+ <a
248
+ href={githubSource.url}
249
+ target="_blank"
250
+ rel="noreferrer"
251
+ onClick={(e) => e.stopPropagation()}
252
+ className="inline-flex items-center gap-1.5 rounded-[7px] bg-sky-500/10 px-2 py-1 text-[10px] font-600 text-sky-300 hover:bg-sky-500/15"
253
+ >
254
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" className="shrink-0">
255
+ <path d="M12 .5C5.65.5.5 5.65.5 12A11.5 11.5 0 0 0 8.36 22.9c.57.1.78-.25.78-.55 0-.27-.01-1.17-.02-2.13-3.2.69-3.88-1.36-3.88-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.71.08-.71 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.68 0-1.25.45-2.27 1.18-3.07-.12-.29-.51-1.45.11-3.02 0 0 .96-.31 3.15 1.17a10.9 10.9 0 0 1 5.73 0c2.18-1.48 3.14-1.17 3.14-1.17.63 1.57.24 2.73.12 3.02.74.8 1.18 1.82 1.18 3.07 0 4.41-2.7 5.38-5.27 5.66.42.36.78 1.06.78 2.14 0 1.55-.01 2.79-.01 3.17 0 .31.2.66.79.55A11.5 11.5 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z" />
256
+ </svg>
257
+ {githubSource.repo ? `${githubSource.repo}#${githubSource.number}` : `GitHub #${githubSource.number ?? githubSource.id}`}
258
+ </a>
259
+ ) : (
260
+ <span className="inline-flex items-center gap-1.5 rounded-[7px] bg-sky-500/10 px-2 py-1 text-[10px] font-600 text-sky-300">
261
+ GitHub {githubSource.repo ? `${githubSource.repo}#${githubSource.number}` : `#${githubSource.number ?? githubSource.id}`}
262
+ </span>
263
+ )
264
+ )}
236
265
  </div>
237
266
  )}
238
267
 
@@ -248,7 +277,7 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
248
277
  {project.name}
249
278
  </span>
250
279
  )}
251
- <span className="text-[11px] text-text-3">{timeAgo(task.updatedAt)}</span>
280
+ <span className="text-[11px] text-text-3">{timeAgo(task.updatedAt, now)}</span>
252
281
  {task.comments && task.comments.length > 0 && (
253
282
  <span className="flex items-center gap-1 text-[11px] text-text-3">
254
283
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/60">
@@ -39,7 +39,7 @@ function DialogOverlay({
39
39
  <DialogPrimitive.Overlay
40
40
  data-slot="dialog-overlay"
41
41
  className={cn(
42
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/72 backdrop-blur-md",
42
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[1400] bg-black/72 backdrop-blur-md",
43
43
  className
44
44
  )}
45
45
  {...props}
@@ -61,7 +61,7 @@ function DialogContent({
61
61
  <DialogPrimitive.Content
62
62
  data-slot="dialog-content"
63
63
  className={cn(
64
- "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
64
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-[1400] grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
65
65
  className
66
66
  )}
67
67
  style={{ animation: 'spring-in 0.4s var(--ease-spring)' }}
@@ -4,6 +4,7 @@ import { useState, useCallback } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
5
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
6
6
  import type { WalletTransaction } from '@/types'
7
+ import { formatWalletAmount, getWalletAssetSymbol, getWalletAtomicAmount } from '@/lib/wallet'
7
8
 
8
9
  interface WalletApprovalDialogProps {
9
10
  transaction: WalletTransaction
@@ -33,7 +34,8 @@ export function WalletApprovalDialog({ transaction, walletAddress, onClose, onRe
33
34
  }
34
35
  }, [transaction, onResolved, onClose])
35
36
 
36
- const amountSol = transaction.amountLamports / 1e9
37
+ const amountFormatted = formatWalletAmount(transaction.chain, getWalletAtomicAmount(transaction), { minFractionDigits: 4, maxFractionDigits: 6 })
38
+ const symbol = getWalletAssetSymbol(transaction.chain)
37
39
 
38
40
  return (
39
41
  <Dialog open onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}>
@@ -57,7 +59,7 @@ export function WalletApprovalDialog({ transaction, walletAddress, onClose, onRe
57
59
  <div className="rounded-[14px] border border-white/[0.06] bg-black/20 p-4 space-y-3">
58
60
  <div className="flex items-center justify-between">
59
61
  <span className="text-[11px] uppercase tracking-wide text-text-3/70">Amount</span>
60
- <span className="text-[16px] font-600 text-text-1">{amountSol.toFixed(4)} SOL</span>
62
+ <span className="text-[16px] font-600 text-text-1">{amountFormatted} {symbol}</span>
61
63
  </div>
62
64
  <div>
63
65
  <span className="mb-1 block text-[11px] uppercase tracking-wide text-text-3/70">From</span>