@swarmclawai/swarmclaw 0.2.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 (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. package/tsconfig.json +34 -0
@@ -0,0 +1,438 @@
1
+ 'use client'
2
+
3
+ import { useState, useMemo } from 'react'
4
+ import type { ToolEvent } from '@/stores/use-chat-store'
5
+
6
+ const TOOL_COLORS: Record<string, string> = {
7
+ execute_command: '#F59E0B',
8
+ read_file: '#10B981',
9
+ write_file: '#10B981',
10
+ list_files: '#10B981',
11
+ copy_file: '#10B981',
12
+ move_file: '#10B981',
13
+ delete_file: '#EF4444',
14
+ edit_file: '#10B981',
15
+ send_file: '#10B981',
16
+ web_search: '#3B82F6',
17
+ web_fetch: '#3B82F6',
18
+ delegate_to_claude_code: '#6366F1',
19
+ delegate_to_codex_cli: '#0EA5E9',
20
+ delegate_to_opencode_cli: '#14B8A6',
21
+ whoami_tool: '#8B5CF6',
22
+ connector_message_tool: '#EC4899',
23
+ search_history_tool: '#8B5CF6',
24
+ manage_tasks: '#EC4899',
25
+ manage_schedules: '#EC4899',
26
+ manage_agents: '#EC4899',
27
+ manage_skills: '#EC4899',
28
+ manage_documents: '#EC4899',
29
+ manage_webhooks: '#EC4899',
30
+ manage_connectors: '#EC4899',
31
+ manage_sessions: '#EC4899',
32
+ memory: '#A855F7',
33
+ browser: '#3B82F6',
34
+ }
35
+
36
+ /** Sub-labels for browser actions shown after the main "Browser" label */
37
+ const BROWSER_ACTION_LABELS: Record<string, string> = {
38
+ navigate: 'Navigate',
39
+ screenshot: 'Screenshot',
40
+ snapshot: 'Snapshot',
41
+ click: 'Click',
42
+ type: 'Type',
43
+ press_key: 'Key Press',
44
+ select: 'Select',
45
+ evaluate: 'Run JS',
46
+ pdf: 'Save PDF',
47
+ upload: 'Upload',
48
+ wait: 'Wait',
49
+ }
50
+
51
+ export const TOOL_LABELS: Record<string, string> = {
52
+ execute_command: 'Shell',
53
+ read_file: 'Read File',
54
+ write_file: 'Write File',
55
+ list_files: 'List Files',
56
+ copy_file: 'Copy File',
57
+ move_file: 'Move File',
58
+ delete_file: 'Delete File',
59
+ edit_file: 'Edit File',
60
+ send_file: 'Send File',
61
+ web_search: 'Web Search',
62
+ web_fetch: 'Web Fetch',
63
+ claude_code: 'Claude Code',
64
+ codex_cli: 'Codex CLI',
65
+ opencode_cli: 'OpenCode CLI',
66
+ delegate_to_claude_code: 'Claude Code',
67
+ delegate_to_codex_cli: 'Codex CLI',
68
+ delegate_to_opencode_cli: 'OpenCode CLI',
69
+ whoami_tool: 'Who Am I',
70
+ connector_message_tool: 'Connector Message',
71
+ search_history_tool: 'Search History',
72
+ manage_tasks: 'Tasks',
73
+ manage_schedules: 'Schedules',
74
+ manage_agents: 'Agents',
75
+ manage_skills: 'Skills',
76
+ manage_documents: 'Documents',
77
+ manage_webhooks: 'Webhooks',
78
+ manage_connectors: 'Connectors',
79
+ manage_sessions: 'Sessions',
80
+ memory: 'Memory',
81
+ browser: 'Browser',
82
+ }
83
+
84
+ export const TOOL_DESCRIPTIONS: Record<string, string> = {
85
+ execute_command: 'Run shell commands in the working directory',
86
+ read_file: 'Read file contents from disk',
87
+ write_file: 'Write or create files on disk',
88
+ list_files: 'List files and directories',
89
+ copy_file: 'Copy a file to another path',
90
+ move_file: 'Move or rename a file',
91
+ delete_file: 'Delete files or directories (when explicitly enabled)',
92
+ edit_file: 'Edit existing files with find-and-replace',
93
+ send_file: 'Send files to the user (images, PDFs, videos, documents, etc.)',
94
+ web_search: 'Search the web for information',
95
+ web_fetch: 'Fetch and read web page content',
96
+ claude_code: 'Enable delegation to Claude Code CLI',
97
+ codex_cli: 'Enable delegation to OpenAI Codex CLI',
98
+ opencode_cli: 'Enable delegation to OpenCode CLI',
99
+ delegate_to_claude_code: 'Delegate complex coding tasks to Claude Code',
100
+ delegate_to_codex_cli: 'Delegate complex coding tasks to Codex CLI',
101
+ delegate_to_opencode_cli: 'Delegate complex coding tasks to OpenCode CLI',
102
+ whoami_tool: 'Reveal the current session and agent identity context',
103
+ connector_message_tool: 'Send proactive outbound messages via running connectors',
104
+ search_history_tool: 'Search chat history for relevant prior context',
105
+ manage_tasks: 'Create, update, and manage tasks on the board',
106
+ manage_schedules: 'Create and manage cron schedules',
107
+ manage_agents: 'Create and configure other agents',
108
+ manage_skills: 'Create and manage agent skills',
109
+ manage_documents: 'Upload and search indexed documents',
110
+ manage_webhooks: 'Register and manage inbound webhooks',
111
+ manage_connectors: 'Manage chat platform connectors (Slack, Discord, etc.)',
112
+ manage_sessions: 'Create and manage chat sessions',
113
+ memory: 'Store and recall information across conversations',
114
+ browser: 'Browse the web, take screenshots, and interact with pages',
115
+ }
116
+
117
+ /**
118
+ * Recursively parse stringified JSON values so nested escaped JSON
119
+ * like `"{\"title\": \"Test\"}"` becomes a proper object.
120
+ */
121
+ function deepParseJson(value: unknown): unknown {
122
+ if (typeof value === 'string') {
123
+ try {
124
+ const parsed = JSON.parse(value)
125
+ if (typeof parsed === 'object' && parsed !== null) {
126
+ return deepParseJson(parsed)
127
+ }
128
+ return parsed
129
+ } catch {
130
+ return value
131
+ }
132
+ }
133
+ if (Array.isArray(value)) {
134
+ return value.map(deepParseJson)
135
+ }
136
+ if (typeof value === 'object' && value !== null) {
137
+ const result: Record<string, unknown> = {}
138
+ for (const [k, v] of Object.entries(value)) {
139
+ result[k] = deepParseJson(v)
140
+ }
141
+ return result
142
+ }
143
+ return value
144
+ }
145
+
146
+ /** Pretty-print JSON, recursively parsing stringified nested values */
147
+ function formatJson(raw: string): string {
148
+ try {
149
+ const parsed = deepParseJson(JSON.parse(raw))
150
+ return JSON.stringify(parsed, null, 2)
151
+ } catch {
152
+ return raw
153
+ }
154
+ }
155
+
156
+ /** Extract a human-readable preview from tool input */
157
+ function getInputPreview(name: string, input: string): string {
158
+ try {
159
+ let parsed = JSON.parse(input)
160
+ // Unwrap LangChain's { input: ... } wrapper
161
+ if (parsed.input && Object.keys(parsed).length === 1) {
162
+ const inner = parsed.input
163
+ if (typeof inner === 'string') {
164
+ try { parsed = JSON.parse(inner) } catch { parsed = inner }
165
+ } else if (typeof inner === 'object' && inner !== null) {
166
+ parsed = inner
167
+ }
168
+ }
169
+
170
+ // Consolidated browser tool — show action + relevant detail
171
+ if (name === 'browser') {
172
+ const act = parsed.action || ''
173
+ if (act === 'navigate') return parsed.url || ''
174
+ if (act === 'click') return parsed.element || (parsed.ref ? `element #${parsed.ref}` : '')
175
+ if (act === 'type') return parsed.text ? `"${parsed.text.slice(0, 50)}"` : ''
176
+ if (act === 'press_key') return parsed.key || ''
177
+ if (act === 'select') return parsed.option || ''
178
+ if (act === 'evaluate') return parsed.expression?.slice(0, 60) || ''
179
+ if (act === 'wait') return parsed.text ? `for "${parsed.text}"` : `${parsed.timeout || 30000}ms`
180
+ if (act === 'upload') return parsed.paths?.join(', ')?.slice(0, 60) || ''
181
+ return ''
182
+ }
183
+ if (name === 'send_file') return parsed.filePath || ''
184
+
185
+ if (parsed.command) return parsed.command
186
+ if (parsed.filePath) return parsed.filePath
187
+ if (parsed.dirPath) return parsed.dirPath
188
+ if (parsed.query) return parsed.query
189
+ if (parsed.url) return parsed.url
190
+ if (parsed.task) return parsed.task.slice(0, 80)
191
+ if (parsed.action) {
192
+ const detail = parsed.data?.title || parsed.data?.name || parsed.data?.content?.slice(0, 40) || parsed.id || ''
193
+ return detail ? `${parsed.action}: ${detail}` : parsed.action
194
+ }
195
+ const keys = Object.keys(parsed)
196
+ if (keys.length === 1) {
197
+ const val = parsed[keys[0]]
198
+ const str = typeof val === 'string' ? val : JSON.stringify(val)
199
+ return `${keys[0]}: ${str.slice(0, 60)}`
200
+ }
201
+ if (keys.length <= 3) return keys.join(', ')
202
+ return `${keys.slice(0, 2).join(', ')} +${keys.length - 2} more`
203
+ } catch {
204
+ return input.slice(0, 80)
205
+ }
206
+ }
207
+
208
+ /** Extract embedded images, videos, PDFs, and file links from tool output */
209
+ function extractMedia(output: string): { images: string[]; videos: string[]; pdfs: { name: string; url: string }[]; files: { name: string; url: string }[]; cleanText: string } {
210
+ const images: string[] = []
211
+ const videos: string[] = []
212
+ const pdfs: { name: string; url: string }[] = []
213
+ const files: { name: string; url: string }[] = []
214
+
215
+ // Extract ![alt](/api/uploads/filename) — detect videos vs images by extension
216
+ let cleanText = output.replace(/!\[([^\]]*)\]\(\/api\/uploads\/([^)]+)\)/g, (_match, _alt, filename) => {
217
+ const url = `/api/uploads/${filename}`
218
+ if (/\.(mp4|webm|mov|avi)$/i.test(filename)) {
219
+ videos.push(url)
220
+ } else {
221
+ images.push(url)
222
+ }
223
+ return ''
224
+ })
225
+
226
+ // Extract [label](/api/uploads/filename) — separate PDFs for inline preview
227
+ cleanText = cleanText.replace(/\[([^\]]*)\]\(\/api\/uploads\/([^)]+)\)/g, (_match, label, filename) => {
228
+ const url = `/api/uploads/${filename}`
229
+ if (/\.pdf$/i.test(filename)) {
230
+ pdfs.push({ name: label || filename, url })
231
+ } else {
232
+ files.push({ name: label || filename, url })
233
+ }
234
+ return ''
235
+ })
236
+
237
+ // Clean up leftover whitespace
238
+ cleanText = cleanText.replace(/\n{3,}/g, '\n\n').trim()
239
+
240
+ return { images, videos, pdfs, files, cleanText }
241
+ }
242
+
243
+ export function ToolCallBubble({ event }: { event: ToolEvent }) {
244
+ const [expanded, setExpanded] = useState(false)
245
+ const [imgExpanded, setImgExpanded] = useState(false)
246
+ const isError = event.status === 'error'
247
+ const color = isError ? '#F43F5E' : (TOOL_COLORS[event.name] || '#6366F1')
248
+ const isRunning = event.status === 'running'
249
+
250
+ // For browser tool, extract the action to show a more specific label
251
+ const label = useMemo(() => {
252
+ if (event.name === 'browser') {
253
+ try {
254
+ let parsed = JSON.parse(event.input)
255
+ // Unwrap LangChain {input: "..."} wrapper — inner value is a stringified JSON
256
+ if (parsed?.input && Object.keys(parsed).length === 1) {
257
+ const inner = typeof parsed.input === 'string' ? JSON.parse(parsed.input) : parsed.input
258
+ if (typeof inner === 'object' && inner !== null) parsed = inner
259
+ }
260
+ const action = parsed?.action || ''
261
+ const sub = BROWSER_ACTION_LABELS[action]
262
+ return sub ? `Browser · ${sub}` : 'Browser'
263
+ } catch { return 'Browser' }
264
+ }
265
+ return TOOL_LABELS[event.name] || event.name.replace(/_/g, ' ')
266
+ }, [event.name, event.input])
267
+
268
+ const inputPreview = useMemo(() => getInputPreview(event.name, event.input), [event.name, event.input])
269
+ const formattedInput = useMemo(() => formatJson(event.input), [event.input])
270
+
271
+ const media = useMemo(() => {
272
+ if (!event.output) return { images: [], videos: [], pdfs: [], files: [], cleanText: '' }
273
+ return extractMedia(event.output)
274
+ }, [event.output])
275
+
276
+ const formattedCleanOutput = useMemo(() => {
277
+ if (!media.cleanText) return ''
278
+ return formatJson(media.cleanText)
279
+ }, [media.cleanText])
280
+
281
+ const hasMedia = media.images.length > 0 || media.videos.length > 0 || media.pdfs.length > 0 || media.files.length > 0
282
+
283
+ return (
284
+ <div className="w-full text-left">
285
+ <button
286
+ onClick={() => isError && setExpanded(!expanded)}
287
+ className={`w-full text-left rounded-[12px] border bg-surface/80 backdrop-blur-sm transition-all duration-200 ${isError ? 'hover:bg-surface-2 cursor-pointer' : ''}`}
288
+ style={{ borderLeft: `3px solid ${color}`, borderColor: `${color}33` }}
289
+ >
290
+ <div className="flex items-center gap-2.5 px-3.5 py-2.5">
291
+ {isRunning ? (
292
+ <span className="w-3.5 h-3.5 shrink-0 rounded-full border-2 border-current animate-spin" style={{ color, borderTopColor: 'transparent' }} />
293
+ ) : isError ? (
294
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" className="shrink-0">
295
+ <line x1="18" y1="6" x2="6" y2="18" />
296
+ <line x1="6" y1="6" x2="18" y2="18" />
297
+ </svg>
298
+ ) : (
299
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" className="shrink-0">
300
+ <polyline points="20 6 9 17 4 12" />
301
+ </svg>
302
+ )}
303
+ <span className="text-[12px] font-700 uppercase tracking-wider shrink-0" style={{ color }}>
304
+ {label}
305
+ </span>
306
+ <span className="text-[12px] text-text-2 font-mono truncate flex-1">
307
+ {inputPreview}
308
+ </span>
309
+ {hasMedia && !expanded && (
310
+ <span className="text-[10px] text-text-3/50 font-500 shrink-0">
311
+ {media.images.length > 0 && `${media.images.length} image${media.images.length > 1 ? 's' : ''}`}
312
+ {media.videos.length > 0 && `${(media.images.length > 0) ? ' · ' : ''}${media.videos.length} video${media.videos.length > 1 ? 's' : ''}`}
313
+ {media.pdfs.length > 0 && `${(media.images.length > 0 || media.videos.length > 0) ? ' · ' : ''}${media.pdfs.length} PDF${media.pdfs.length > 1 ? 's' : ''}`}
314
+ {media.files.length > 0 && `${(media.images.length > 0 || media.videos.length > 0 || media.pdfs.length > 0) ? ' · ' : ''}${media.files.length} file${media.files.length > 1 ? 's' : ''}`}
315
+ </span>
316
+ )}
317
+ {isError && (
318
+ <svg
319
+ width="12" height="12" viewBox="0 0 24 24" fill="none"
320
+ stroke="currentColor" strokeWidth="2" strokeLinecap="round"
321
+ className={`shrink-0 text-text-3/70 transition-transform duration-200 ${expanded ? 'rotate-180' : ''}`}
322
+ >
323
+ <polyline points="6 9 12 15 18 9" />
324
+ </svg>
325
+ )}
326
+ </div>
327
+
328
+ {expanded && isError && (
329
+ <div className="px-3.5 pb-3 space-y-2" onClick={(e) => e.stopPropagation()}>
330
+ <div className="text-[11px] text-text-3/60 uppercase tracking-wider font-600">Input</div>
331
+ <pre className="text-[12px] text-text-2 font-mono whitespace-pre-wrap break-all bg-bg/50 rounded-[8px] px-3 py-2 max-h-[200px] overflow-y-auto">
332
+ {formattedInput}
333
+ </pre>
334
+ {event.output && (
335
+ <>
336
+ <div className="text-[11px] text-text-3/60 uppercase tracking-wider font-600 mt-2">Error</div>
337
+ {formattedCleanOutput && (
338
+ <pre className="text-[12px] text-text-2 font-mono whitespace-pre-wrap break-all bg-bg/50 rounded-[8px] px-3 py-2 max-h-[300px] overflow-y-auto">
339
+ {formattedCleanOutput}
340
+ </pre>
341
+ )}
342
+ </>
343
+ )}
344
+ </div>
345
+ )}
346
+ </button>
347
+
348
+ {/* Render images below the tool call bubble (always visible when present) */}
349
+ {media.images.length > 0 && (
350
+ <div className="mt-2 flex flex-col gap-2">
351
+ {media.images.map((src, i) => (
352
+ <div key={i} className="relative group/img">
353
+ <img
354
+ src={src}
355
+ alt={`Screenshot ${i + 1}`}
356
+ className={`rounded-[10px] border border-white/10 cursor-pointer transition-all duration-200 hover:border-white/25 ${imgExpanded ? 'max-w-full' : 'max-w-[400px]'}`}
357
+ onClick={(e) => { e.stopPropagation(); setImgExpanded(!imgExpanded) }}
358
+ onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
359
+ />
360
+ <a
361
+ href={src}
362
+ download
363
+ onClick={(e) => e.stopPropagation()}
364
+ className="absolute top-2 right-2 opacity-0 group-hover/img:opacity-100 transition-opacity bg-black/60 backdrop-blur-sm rounded-[8px] p-1.5 hover:bg-black/80"
365
+ title="Download"
366
+ >
367
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round">
368
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
369
+ <polyline points="7 10 12 15 17 10" />
370
+ <line x1="12" y1="15" x2="12" y2="3" />
371
+ </svg>
372
+ </a>
373
+ </div>
374
+ ))}
375
+ </div>
376
+ )}
377
+
378
+ {/* Render videos */}
379
+ {media.videos.length > 0 && (
380
+ <div className="mt-2 flex flex-col gap-2">
381
+ {media.videos.map((src, i) => (
382
+ <video key={i} src={src} controls playsInline className="max-w-full rounded-[10px] border border-white/10" />
383
+ ))}
384
+ </div>
385
+ )}
386
+
387
+ {/* Render PDFs inline with iframe preview + download */}
388
+ {media.pdfs.length > 0 && (
389
+ <div className="mt-2 flex flex-col gap-2">
390
+ {media.pdfs.map((file, i) => (
391
+ <div key={i} className="rounded-[10px] border border-white/10 overflow-hidden">
392
+ <iframe src={file.url} className="w-full h-[400px] bg-white" title={file.name} />
393
+ <a
394
+ href={file.url}
395
+ download
396
+ onClick={(e) => e.stopPropagation()}
397
+ className="flex items-center gap-2 px-3 py-2 bg-surface/80 border-t border-white/10 text-[12px] text-text-2 hover:text-text no-underline transition-colors"
398
+ >
399
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
400
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
401
+ <polyline points="7 10 12 15 17 10" />
402
+ <line x1="12" y1="15" x2="12" y2="3" />
403
+ </svg>
404
+ {file.name}
405
+ </a>
406
+ </div>
407
+ ))}
408
+ </div>
409
+ )}
410
+
411
+ {/* Render other file download links */}
412
+ {media.files.length > 0 && (
413
+ <div className="mt-2 flex flex-col gap-1.5">
414
+ {media.files.map((file, i) => (
415
+ <a
416
+ key={i}
417
+ href={file.url}
418
+ download
419
+ onClick={(e) => e.stopPropagation()}
420
+ className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/10 bg-surface/60 hover:bg-surface-2 transition-colors text-[13px] text-text-2 hover:text-text no-underline"
421
+ >
422
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
423
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
424
+ <polyline points="14 2 14 8 20 8" />
425
+ </svg>
426
+ {file.name}
427
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="ml-auto opacity-50">
428
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
429
+ <polyline points="7 10 12 15 17 10" />
430
+ <line x1="12" y1="15" x2="12" y2="3" />
431
+ </svg>
432
+ </a>
433
+ ))}
434
+ </div>
435
+ )}
436
+ </div>
437
+ )
438
+ }
@@ -0,0 +1,103 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { api } from '@/lib/api-client'
6
+
7
+ const TOOL_LABELS: Record<string, string> = {
8
+ shell: 'Shell', files: 'Files', edit_file: 'Edit File', process: 'Process',
9
+ web_search: 'Web Search', web_fetch: 'Web Fetch', browser: 'Browser', memory: 'Memory',
10
+ claude_code: 'Claude Code', codex_cli: 'Codex CLI', opencode_cli: 'OpenCode CLI',
11
+ orchestrator: 'Orchestrator', manage_agents: 'Agents', manage_tasks: 'Tasks', manage_schedules: 'Schedules',
12
+ manage_skills: 'Skills', manage_documents: 'Documents', manage_webhooks: 'Webhooks',
13
+ manage_connectors: 'Connectors', manage_sessions: 'Sessions', manage_secrets: 'Secrets',
14
+ }
15
+
16
+ interface Props {
17
+ text: string
18
+ toolOutputs?: string[]
19
+ }
20
+
21
+ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
22
+ const loadSessions = useAppStore((s) => s.loadSessions)
23
+ const currentSessionId = useAppStore((s) => s.currentSessionId)
24
+ const sessions = useAppStore((s) => s.sessions)
25
+ const [granted, setGranted] = useState<Set<string>>(new Set())
26
+
27
+ const toolRequests: { toolId: string; reason: string }[] = []
28
+ const seen = new Set<string>()
29
+
30
+ function extractFromText(t: string) {
31
+ try {
32
+ const jsonMatches = t.match(/\{"type"\s*:\s*"tool_request"[^}]*\}/g)
33
+ if (jsonMatches) {
34
+ for (const jm of jsonMatches) {
35
+ const parsed = JSON.parse(jm)
36
+ if (parsed.type === 'tool_request' && parsed.toolId && !seen.has(parsed.toolId)) {
37
+ seen.add(parsed.toolId)
38
+ toolRequests.push({ toolId: parsed.toolId, reason: parsed.reason || '' })
39
+ }
40
+ }
41
+ }
42
+ } catch { /* ignore */ }
43
+ }
44
+
45
+ // Scan message text and all tool outputs
46
+ extractFromText(text)
47
+ for (const output of toolOutputs) extractFromText(output)
48
+
49
+ if (toolRequests.length === 0) return null
50
+
51
+ const sid = currentSessionId
52
+ const session = sid ? sessions[sid] : null
53
+
54
+ const handleGrant = async (toolId: string) => {
55
+ if (!sid || !session) return
56
+ const currentTools: string[] = session.tools || []
57
+ if (currentTools.includes(toolId)) {
58
+ setGranted((prev) => new Set(prev).add(toolId))
59
+ return
60
+ }
61
+ const updated = [...currentTools, toolId]
62
+ await api('PUT', `/sessions/${sid}`, { tools: updated })
63
+ await loadSessions()
64
+ setGranted((prev) => new Set(prev).add(toolId))
65
+ }
66
+
67
+ return (
68
+ <div className="max-w-[85%] md:max-w-[72%] flex flex-col gap-2 mt-2">
69
+ {toolRequests.map(({ toolId, reason }) => {
70
+ const isGranted = granted.has(toolId) || (session?.tools || []).includes(toolId)
71
+ const label = TOOL_LABELS[toolId] || toolId
72
+ return (
73
+ <div
74
+ key={toolId}
75
+ className="flex items-center gap-3 px-4 py-3 rounded-[12px] border border-amber-500/20 bg-amber-500/[0.06]"
76
+ style={{ animation: 'fade-in 0.2s ease' }}
77
+ >
78
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-amber-400 shrink-0">
79
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
80
+ </svg>
81
+ <div className="flex-1 min-w-0">
82
+ <p className="text-[12px] text-text-2 font-600">
83
+ Requesting access to <span className="text-amber-400">{label}</span>
84
+ </p>
85
+ {reason && <p className="text-[11px] text-text-3/60 mt-0.5 truncate">{reason}</p>}
86
+ </div>
87
+ {isGranted ? (
88
+ <span className="text-[11px] text-emerald-400 font-600 shrink-0">Granted</span>
89
+ ) : (
90
+ <button
91
+ onClick={() => handleGrant(toolId)}
92
+ className="px-3 py-1.5 rounded-[8px] bg-amber-500/20 hover:bg-amber-500/30 text-amber-300 text-[11px] font-600 border-none cursor-pointer transition-colors shrink-0"
93
+ style={{ fontFamily: 'inherit' }}
94
+ >
95
+ Grant
96
+ </button>
97
+ )}
98
+ </div>
99
+ )
100
+ })}
101
+ </div>
102
+ )
103
+ }