@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.
- package/README.md +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- 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  — 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
|
+
}
|