@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,491 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useMemo } from 'react'
|
|
4
|
+
import type { Session } from '@/types'
|
|
5
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
|
+
import { IconButton } from '@/components/shared/icon-button'
|
|
8
|
+
import { UsageBadge } from '@/components/shared/usage-badge'
|
|
9
|
+
import { ChatToolToggles } from './chat-tool-toggles'
|
|
10
|
+
import { api } from '@/lib/api-client'
|
|
11
|
+
import {
|
|
12
|
+
ConnectorPlatformIcon,
|
|
13
|
+
CONNECTOR_PLATFORM_META,
|
|
14
|
+
getSessionConnector,
|
|
15
|
+
} from '@/components/shared/connector-platform-icon'
|
|
16
|
+
|
|
17
|
+
function shortPath(p: string): string {
|
|
18
|
+
return (p || '').replace(/^\/Users\/\w+/, '~')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PROVIDER_LABELS: Record<string, string> = {
|
|
22
|
+
'claude-cli': 'CLI',
|
|
23
|
+
openai: 'OpenAI',
|
|
24
|
+
ollama: 'Ollama',
|
|
25
|
+
anthropic: 'Anthropic',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
session: Session
|
|
30
|
+
streaming: boolean
|
|
31
|
+
onStop: () => void
|
|
32
|
+
onMenuToggle: () => void
|
|
33
|
+
onBack?: () => void
|
|
34
|
+
mobile?: boolean
|
|
35
|
+
browserActive?: boolean
|
|
36
|
+
onStopBrowser?: () => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser }: Props) {
|
|
40
|
+
const ttsEnabled = useChatStore((s) => s.ttsEnabled)
|
|
41
|
+
const toggleTts = useChatStore((s) => s.toggleTts)
|
|
42
|
+
const debugOpen = useChatStore((s) => s.debugOpen)
|
|
43
|
+
const setDebugOpen = useChatStore((s) => s.setDebugOpen)
|
|
44
|
+
const lastUsage = useChatStore((s) => s.lastUsage)
|
|
45
|
+
const agents = useAppStore((s) => s.agents)
|
|
46
|
+
const tasks = useAppStore((s) => s.tasks)
|
|
47
|
+
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
48
|
+
const setMemoryAgentFilter = useAppStore((s) => s.setMemoryAgentFilter)
|
|
49
|
+
const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
|
|
50
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
51
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
52
|
+
const connectors = useAppStore((s) => s.connectors)
|
|
53
|
+
const loadConnectors = useAppStore((s) => s.loadConnectors)
|
|
54
|
+
const providerLabel = PROVIDER_LABELS[session.provider] || session.provider
|
|
55
|
+
const agent = session.agentId ? agents[session.agentId] : null
|
|
56
|
+
const connector = getSessionConnector(session, connectors)
|
|
57
|
+
const connectorMeta = connector ? CONNECTOR_PLATFORM_META[connector.platform] : null
|
|
58
|
+
const modelName = session.model || agent?.model || ''
|
|
59
|
+
const [copied, setCopied] = useState(false)
|
|
60
|
+
const [heartbeatSaving, setHeartbeatSaving] = useState(false)
|
|
61
|
+
const [mainLoopSaving, setMainLoopSaving] = useState(false)
|
|
62
|
+
const [mainLoopError, setMainLoopError] = useState('')
|
|
63
|
+
const [mainLoopNotice, setMainLoopNotice] = useState('')
|
|
64
|
+
|
|
65
|
+
// Find linked task for this session
|
|
66
|
+
const linkedTask = useMemo(() => {
|
|
67
|
+
return Object.values(tasks).find((t) => t.sessionId === session.id)
|
|
68
|
+
}, [tasks, session.id])
|
|
69
|
+
|
|
70
|
+
const resumeHandle = useMemo(() => {
|
|
71
|
+
const fromSessionClaude = session.claudeSessionId
|
|
72
|
+
? { label: 'Claude', id: session.claudeSessionId, command: `claude --resume ${session.claudeSessionId}` }
|
|
73
|
+
: null
|
|
74
|
+
const fromSessionCodex = session.codexThreadId
|
|
75
|
+
? { label: 'Codex', id: session.codexThreadId, command: `codex exec resume ${session.codexThreadId}` }
|
|
76
|
+
: null
|
|
77
|
+
const fromSessionOpenCode = session.opencodeSessionId
|
|
78
|
+
? { label: 'OpenCode', id: session.opencodeSessionId, command: `opencode run \"<task>\" --session ${session.opencodeSessionId}` }
|
|
79
|
+
: null
|
|
80
|
+
const fromDelegateClaude = session.delegateResumeIds?.claudeCode
|
|
81
|
+
? { label: 'Claude', id: session.delegateResumeIds.claudeCode, command: `claude --resume ${session.delegateResumeIds.claudeCode}` }
|
|
82
|
+
: null
|
|
83
|
+
const fromDelegateCodex = session.delegateResumeIds?.codex
|
|
84
|
+
? { label: 'Codex', id: session.delegateResumeIds.codex, command: `codex exec resume ${session.delegateResumeIds.codex}` }
|
|
85
|
+
: null
|
|
86
|
+
const fromDelegateOpenCode = session.delegateResumeIds?.opencode
|
|
87
|
+
? { label: 'OpenCode', id: session.delegateResumeIds.opencode, command: `opencode run \"<task>\" --session ${session.delegateResumeIds.opencode}` }
|
|
88
|
+
: null
|
|
89
|
+
return fromSessionClaude
|
|
90
|
+
|| fromSessionCodex
|
|
91
|
+
|| fromSessionOpenCode
|
|
92
|
+
|| fromDelegateClaude
|
|
93
|
+
|| fromDelegateCodex
|
|
94
|
+
|| fromDelegateOpenCode
|
|
95
|
+
|| null
|
|
96
|
+
}, [session.claudeSessionId, session.codexThreadId, session.opencodeSessionId, session.delegateResumeIds])
|
|
97
|
+
|
|
98
|
+
const handleCopySessionId = () => {
|
|
99
|
+
if (!resumeHandle) return
|
|
100
|
+
navigator.clipboard.writeText(resumeHandle.command)
|
|
101
|
+
setCopied(true)
|
|
102
|
+
setTimeout(() => setCopied(false), 2000)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const heartbeatEnabled = session.heartbeatEnabled !== false
|
|
106
|
+
const heartbeatSupported = (session.tools?.length ?? 0) > 0
|
|
107
|
+
const loopIsOngoing = appSettings.loopMode === 'ongoing'
|
|
108
|
+
const heartbeatIntervalRaw = session.heartbeatIntervalSec ?? appSettings.heartbeatIntervalSec ?? 120
|
|
109
|
+
const heartbeatIntervalSec = Number.isFinite(Number(heartbeatIntervalRaw)) ? Math.max(0, Math.trunc(Number(heartbeatIntervalRaw))) : 120
|
|
110
|
+
const isMainSession = session.name === '__main__'
|
|
111
|
+
const missionState = session.mainLoopState || {}
|
|
112
|
+
const missionPaused = missionState.paused === true
|
|
113
|
+
const missionMode = missionState.autonomyMode === 'assist' ? 'assist' : 'autonomous'
|
|
114
|
+
const missionStatus = missionState.status || 'idle'
|
|
115
|
+
const missionMomentum = typeof missionState.momentumScore === 'number' ? missionState.momentumScore : null
|
|
116
|
+
const missionEventsCount = missionState.pendingEvents?.length || 0
|
|
117
|
+
|
|
118
|
+
const handleToggleHeartbeat = async () => {
|
|
119
|
+
if (!heartbeatSupported || heartbeatSaving) return
|
|
120
|
+
setHeartbeatSaving(true)
|
|
121
|
+
try {
|
|
122
|
+
await api('PUT', `/sessions/${session.id}`, { heartbeatEnabled: !heartbeatEnabled })
|
|
123
|
+
await loadSessions()
|
|
124
|
+
} finally {
|
|
125
|
+
setHeartbeatSaving(false)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const handleCycleHeartbeatInterval = async () => {
|
|
130
|
+
if (!heartbeatSupported || heartbeatSaving) return
|
|
131
|
+
const presets = [30, 60, 120, 300, 600]
|
|
132
|
+
const current = heartbeatIntervalSec
|
|
133
|
+
const idx = presets.indexOf(current)
|
|
134
|
+
const next = idx === -1 ? 120 : presets[(idx + 1) % presets.length]
|
|
135
|
+
setHeartbeatSaving(true)
|
|
136
|
+
try {
|
|
137
|
+
await api('PUT', `/sessions/${session.id}`, { heartbeatIntervalSec: next, heartbeatEnabled: true })
|
|
138
|
+
await loadSessions()
|
|
139
|
+
} finally {
|
|
140
|
+
setHeartbeatSaving(false)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const postMainLoopAction = async (action: string, extra?: Record<string, unknown>) => {
|
|
145
|
+
if (!isMainSession || mainLoopSaving) return
|
|
146
|
+
setMainLoopSaving(true)
|
|
147
|
+
try {
|
|
148
|
+
const result = await api<{ runId?: string; deduped?: boolean }>('POST', `/sessions/${session.id}/main-loop`, {
|
|
149
|
+
action,
|
|
150
|
+
...(extra || {}),
|
|
151
|
+
})
|
|
152
|
+
setMainLoopError('')
|
|
153
|
+
if (action === 'nudge') {
|
|
154
|
+
setMainLoopNotice(result?.deduped ? 'Nudge already queued.' : 'Nudge queued.')
|
|
155
|
+
} else if (action === 'set_mode') {
|
|
156
|
+
setMainLoopNotice(`Mode set to ${extra?.mode === 'assist' ? 'Assist' : 'Auto'}.`)
|
|
157
|
+
} else {
|
|
158
|
+
setMainLoopNotice('')
|
|
159
|
+
}
|
|
160
|
+
await loadSessions()
|
|
161
|
+
} catch (err: unknown) {
|
|
162
|
+
const message = err instanceof Error ? err.message : 'Failed to update mission controls.'
|
|
163
|
+
setMainLoopError(message)
|
|
164
|
+
} finally {
|
|
165
|
+
setMainLoopSaving(false)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const handleToggleMissionPause = () => {
|
|
170
|
+
void postMainLoopAction(missionPaused ? 'resume' : 'pause')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const handleToggleMissionMode = () => {
|
|
174
|
+
const nextMode = missionMode === 'autonomous' ? 'assist' : 'autonomous'
|
|
175
|
+
void postMainLoopAction('set_mode', { mode: nextMode })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const handleNudgeMission = () => {
|
|
179
|
+
void postMainLoopAction('nudge')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const handleSetMissionGoal = () => {
|
|
183
|
+
if (!isMainSession) return
|
|
184
|
+
const seededGoal = typeof missionState.goal === 'string' ? missionState.goal : ''
|
|
185
|
+
const raw = window.prompt('Set mission goal', seededGoal)
|
|
186
|
+
const goal = (raw || '').trim()
|
|
187
|
+
if (!goal) return
|
|
188
|
+
void postMainLoopAction('set_goal', { goal })
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const handleClearMissionEvents = () => {
|
|
192
|
+
if (!isMainSession || missionEventsCount <= 0) return
|
|
193
|
+
void postMainLoopAction('clear_events')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (session.name.startsWith('connector:')) {
|
|
198
|
+
void loadConnectors()
|
|
199
|
+
}
|
|
200
|
+
}, [session.name, loadConnectors])
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
setMainLoopError('')
|
|
204
|
+
setMainLoopNotice('')
|
|
205
|
+
}, [session.id])
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!mainLoopNotice) return
|
|
209
|
+
const timer = setTimeout(() => setMainLoopNotice(''), 2500)
|
|
210
|
+
return () => clearTimeout(timer)
|
|
211
|
+
}, [mainLoopNotice])
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<header className="relative z-20 flex flex-col border-b border-white/[0.04] bg-bg/80 backdrop-blur-md shrink-0"
|
|
215
|
+
style={mobile ? { paddingTop: 'max(12px, env(safe-area-inset-top))' } : undefined}>
|
|
216
|
+
<div className="flex items-center gap-3 px-5 py-3 min-h-[56px]">
|
|
217
|
+
{onBack && (
|
|
218
|
+
<IconButton onClick={onBack} aria-label="Go back">
|
|
219
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
220
|
+
<polyline points="15 18 9 12 15 6" />
|
|
221
|
+
</svg>
|
|
222
|
+
</IconButton>
|
|
223
|
+
)}
|
|
224
|
+
<div className="flex-1 min-w-0">
|
|
225
|
+
<div className="flex items-center gap-2.5">
|
|
226
|
+
<span className="font-display text-[16px] font-600 block truncate tracking-[-0.02em]">{
|
|
227
|
+
session.name === '__main__' ? 'Main Chat'
|
|
228
|
+
: session.name.startsWith('agent-thread:') ? (agent?.name || session.name)
|
|
229
|
+
: session.name
|
|
230
|
+
}</span>
|
|
231
|
+
{connector && connectorMeta && (
|
|
232
|
+
<span
|
|
233
|
+
className="shrink-0 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-[7px] border text-[10px] font-700 uppercase tracking-wider"
|
|
234
|
+
style={{
|
|
235
|
+
color: connectorMeta.color,
|
|
236
|
+
backgroundColor: `${connectorMeta.color}1A`,
|
|
237
|
+
borderColor: `${connectorMeta.color}33`,
|
|
238
|
+
}}
|
|
239
|
+
title={`${connector.name} connector`}
|
|
240
|
+
>
|
|
241
|
+
<ConnectorPlatformIcon platform={connector.platform} size={11} />
|
|
242
|
+
{connectorMeta.label}
|
|
243
|
+
</span>
|
|
244
|
+
)}
|
|
245
|
+
{session.provider && session.provider !== 'claude-cli' && (
|
|
246
|
+
<span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-accent-soft text-accent-bright text-[10px] font-700 uppercase tracking-wider">
|
|
247
|
+
{providerLabel}
|
|
248
|
+
</span>
|
|
249
|
+
)}
|
|
250
|
+
{agent?.isOrchestrator && (
|
|
251
|
+
<span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-[#F59E0B]/10 text-[#F59E0B] text-[10px] font-700 uppercase tracking-wider">
|
|
252
|
+
Orchestrator
|
|
253
|
+
</span>
|
|
254
|
+
)}
|
|
255
|
+
{session.tools?.length ? (
|
|
256
|
+
<span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-emerald-500/10 text-emerald-400 text-[10px] font-700 uppercase tracking-wider">
|
|
257
|
+
Tools
|
|
258
|
+
</span>
|
|
259
|
+
) : null}
|
|
260
|
+
{streaming && (
|
|
261
|
+
<span className="shrink-0 w-2 h-2 rounded-full bg-accent-bright" style={{ animation: 'pulse 1.5s ease infinite' }} />
|
|
262
|
+
)}
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex items-center gap-2 mt-0.5">
|
|
265
|
+
<span className="text-[11px] text-text-3/60 font-mono block truncate">{shortPath(session.cwd)}</span>
|
|
266
|
+
{modelName && (
|
|
267
|
+
<>
|
|
268
|
+
<span className="text-[11px] text-text-3/60">·</span>
|
|
269
|
+
<span className="text-[11px] text-text-3/50 font-mono truncate shrink-0">{modelName}</span>
|
|
270
|
+
</>
|
|
271
|
+
)}
|
|
272
|
+
{lastUsage && !streaming && (
|
|
273
|
+
<>
|
|
274
|
+
<span className="text-[11px] text-text-3/60">·</span>
|
|
275
|
+
<UsageBadge {...lastUsage} />
|
|
276
|
+
</>
|
|
277
|
+
)}
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
<div className="flex gap-1.5">
|
|
281
|
+
{streaming && (
|
|
282
|
+
<IconButton onClick={onStop} variant="danger" aria-label="Stop generation">
|
|
283
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
|
|
284
|
+
<rect x="6" y="6" width="12" height="12" rx="2" />
|
|
285
|
+
</svg>
|
|
286
|
+
</IconButton>
|
|
287
|
+
)}
|
|
288
|
+
<IconButton onClick={() => setDebugOpen(!debugOpen)} active={debugOpen} aria-label="Toggle debug panel">
|
|
289
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
290
|
+
<path d="M12 20V10" />
|
|
291
|
+
<path d="M18 20V4" />
|
|
292
|
+
<path d="M6 20v-4" />
|
|
293
|
+
</svg>
|
|
294
|
+
</IconButton>
|
|
295
|
+
<IconButton onClick={toggleTts} active={ttsEnabled} aria-label="Toggle text-to-speech">
|
|
296
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
297
|
+
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
|
|
298
|
+
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
|
|
299
|
+
</svg>
|
|
300
|
+
</IconButton>
|
|
301
|
+
<IconButton onClick={(e) => { e.stopPropagation(); onMenuToggle() }} aria-label="Session menu">
|
|
302
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
303
|
+
<circle cx="12" cy="6" r="1" />
|
|
304
|
+
<circle cx="12" cy="12" r="1" />
|
|
305
|
+
<circle cx="12" cy="18" r="1" />
|
|
306
|
+
</svg>
|
|
307
|
+
</IconButton>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{/* Sub-bar: tools toggle + agent memories + task link + CLI session ID + browser */}
|
|
312
|
+
{(agent || linkedTask || resumeHandle || browserActive || session.tools?.length || isMainSession) && (
|
|
313
|
+
<div className="flex items-center gap-3 px-5 pb-2.5 -mt-1">
|
|
314
|
+
{(((agent?.tools?.length ?? 0) > 0) || ((session.tools?.length ?? 0) > 0)) && (
|
|
315
|
+
<ChatToolToggles session={session} />
|
|
316
|
+
)}
|
|
317
|
+
{heartbeatSupported && (
|
|
318
|
+
<>
|
|
319
|
+
<button
|
|
320
|
+
onClick={handleToggleHeartbeat}
|
|
321
|
+
disabled={heartbeatSaving}
|
|
322
|
+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
|
|
323
|
+
${heartbeatEnabled ? 'bg-emerald-500/10 hover:bg-emerald-500/15 text-emerald-400' : 'bg-white/[0.04] hover:bg-white/[0.07] text-text-3'}`}
|
|
324
|
+
title={loopIsOngoing ? 'Toggle heartbeat for this session' : 'Global loop mode is bounded; heartbeats are paused'}
|
|
325
|
+
>
|
|
326
|
+
<span className={`w-1.5 h-1.5 rounded-full ${heartbeatEnabled ? 'bg-emerald-400' : 'bg-text-3/40'}`} />
|
|
327
|
+
<span className="text-[11px] font-600">
|
|
328
|
+
HB {heartbeatEnabled ? 'On' : 'Off'}
|
|
329
|
+
</span>
|
|
330
|
+
{!loopIsOngoing && (
|
|
331
|
+
<span className="text-[10px] text-text-3/50">(bounded)</span>
|
|
332
|
+
)}
|
|
333
|
+
</button>
|
|
334
|
+
<button
|
|
335
|
+
onClick={handleCycleHeartbeatInterval}
|
|
336
|
+
disabled={heartbeatSaving}
|
|
337
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] text-text-3 transition-colors cursor-pointer border-none"
|
|
338
|
+
title="Cycle heartbeat interval for this session"
|
|
339
|
+
>
|
|
340
|
+
<span className="text-[11px] font-600">{heartbeatIntervalSec}s</span>
|
|
341
|
+
</button>
|
|
342
|
+
</>
|
|
343
|
+
)}
|
|
344
|
+
{isMainSession && (
|
|
345
|
+
<>
|
|
346
|
+
<button
|
|
347
|
+
onClick={handleToggleMissionPause}
|
|
348
|
+
disabled={mainLoopSaving}
|
|
349
|
+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
|
|
350
|
+
${missionPaused ? 'bg-amber-500/12 hover:bg-amber-500/20 text-amber-300' : 'bg-emerald-500/10 hover:bg-emerald-500/15 text-emerald-400'}`}
|
|
351
|
+
title={missionPaused ? 'Resume autonomous mission loop' : 'Pause autonomous mission loop'}
|
|
352
|
+
>
|
|
353
|
+
<span className={`w-1.5 h-1.5 rounded-full ${missionPaused ? 'bg-amber-300' : 'bg-emerald-400'}`} />
|
|
354
|
+
<span className="text-[11px] font-600">
|
|
355
|
+
Mission {missionPaused ? 'Paused' : 'Live'}
|
|
356
|
+
</span>
|
|
357
|
+
</button>
|
|
358
|
+
<button
|
|
359
|
+
onClick={handleToggleMissionMode}
|
|
360
|
+
disabled={mainLoopSaving}
|
|
361
|
+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
|
|
362
|
+
${missionMode === 'autonomous'
|
|
363
|
+
? 'bg-indigo-500/15 hover:bg-indigo-500/25 text-indigo-200'
|
|
364
|
+
: 'bg-white/[0.04] hover:bg-white/[0.07] text-text-3'
|
|
365
|
+
}`}
|
|
366
|
+
title="Toggle mission autonomy mode"
|
|
367
|
+
>
|
|
368
|
+
<span className="text-[11px] font-600">
|
|
369
|
+
Mode {missionMode === 'autonomous' ? 'Auto' : 'Assist'}
|
|
370
|
+
</span>
|
|
371
|
+
</button>
|
|
372
|
+
<button
|
|
373
|
+
onClick={handleNudgeMission}
|
|
374
|
+
disabled={mainLoopSaving || missionPaused}
|
|
375
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-[#3B82F6]/10 hover:bg-[#3B82F6]/18 text-[#60A5FA] transition-colors cursor-pointer border-none disabled:opacity-60"
|
|
376
|
+
title="Run one immediate main-loop mission tick"
|
|
377
|
+
>
|
|
378
|
+
<span className="text-[11px] font-600">Nudge</span>
|
|
379
|
+
</button>
|
|
380
|
+
<button
|
|
381
|
+
onClick={handleSetMissionGoal}
|
|
382
|
+
disabled={mainLoopSaving}
|
|
383
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-fuchsia-500/10 hover:bg-fuchsia-500/18 text-fuchsia-300 transition-colors cursor-pointer border-none"
|
|
384
|
+
title="Set an explicit mission goal"
|
|
385
|
+
>
|
|
386
|
+
<span className="text-[11px] font-600">Set Goal</span>
|
|
387
|
+
</button>
|
|
388
|
+
{missionEventsCount > 0 && (
|
|
389
|
+
<button
|
|
390
|
+
onClick={handleClearMissionEvents}
|
|
391
|
+
disabled={mainLoopSaving}
|
|
392
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] text-text-3 transition-colors cursor-pointer border-none"
|
|
393
|
+
title="Clear pending mission events"
|
|
394
|
+
>
|
|
395
|
+
<span className="text-[11px] font-600">Events {missionEventsCount}</span>
|
|
396
|
+
</button>
|
|
397
|
+
)}
|
|
398
|
+
<span className="text-[10px] text-text-3/50 uppercase tracking-wider">
|
|
399
|
+
{`State ${missionStatus}${missionMomentum !== null ? ` · ${missionMomentum}` : ''}`}
|
|
400
|
+
</span>
|
|
401
|
+
{mainLoopError && (
|
|
402
|
+
<span className="text-[10px] text-red-300/90 truncate max-w-[280px]" title={mainLoopError}>
|
|
403
|
+
{mainLoopError}
|
|
404
|
+
</span>
|
|
405
|
+
)}
|
|
406
|
+
{mainLoopNotice && (
|
|
407
|
+
<span className="text-[10px] text-emerald-300/90 truncate max-w-[220px]" title={mainLoopNotice}>
|
|
408
|
+
{mainLoopNotice}
|
|
409
|
+
</span>
|
|
410
|
+
)}
|
|
411
|
+
</>
|
|
412
|
+
)}
|
|
413
|
+
{agent && session.tools?.includes('memory') && (
|
|
414
|
+
<button
|
|
415
|
+
onClick={() => {
|
|
416
|
+
setMemoryAgentFilter(session.agentId!)
|
|
417
|
+
setActiveView('memory')
|
|
418
|
+
setSidebarOpen(true)
|
|
419
|
+
}}
|
|
420
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-accent-soft/50 hover:bg-accent-soft transition-colors cursor-pointer"
|
|
421
|
+
>
|
|
422
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-accent-bright/60">
|
|
423
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" /><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
424
|
+
</svg>
|
|
425
|
+
<span className="text-[11px] font-600 text-accent-bright/60">
|
|
426
|
+
{agent.name} Memories
|
|
427
|
+
</span>
|
|
428
|
+
</button>
|
|
429
|
+
)}
|
|
430
|
+
{linkedTask && (
|
|
431
|
+
<button
|
|
432
|
+
onClick={() => setActiveView('tasks')}
|
|
433
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-[#F59E0B]/10 hover:bg-[#F59E0B]/15 transition-colors cursor-pointer"
|
|
434
|
+
>
|
|
435
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#F59E0B" strokeWidth="2.5" strokeLinecap="round">
|
|
436
|
+
<path d="M9 11l3 3L22 4" />
|
|
437
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
|
|
438
|
+
</svg>
|
|
439
|
+
<span className="text-[11px] font-600 text-[#F59E0B] truncate max-w-[200px]">
|
|
440
|
+
Task: {linkedTask.title}
|
|
441
|
+
</span>
|
|
442
|
+
</button>
|
|
443
|
+
)}
|
|
444
|
+
{resumeHandle && (
|
|
445
|
+
<button
|
|
446
|
+
onClick={handleCopySessionId}
|
|
447
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] transition-colors cursor-pointer group"
|
|
448
|
+
title="Copy resume handle/command to clipboard"
|
|
449
|
+
>
|
|
450
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/50">
|
|
451
|
+
<path d="M4 17l6 0l0 -6" />
|
|
452
|
+
<path d="M20 7l-6 0l0 6" />
|
|
453
|
+
<path d="M4 17l10 -10" />
|
|
454
|
+
</svg>
|
|
455
|
+
<span className="text-[11px] font-mono text-text-3/50 group-hover:text-text-3/70 truncate max-w-[220px]">
|
|
456
|
+
{copied ? 'Copied!' : `${resumeHandle.label}: ${resumeHandle.id}`}
|
|
457
|
+
</span>
|
|
458
|
+
{!copied && (
|
|
459
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/60 shrink-0">
|
|
460
|
+
<rect x="9" y="9" width="13" height="13" rx="2" />
|
|
461
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
462
|
+
</svg>
|
|
463
|
+
)}
|
|
464
|
+
</button>
|
|
465
|
+
)}
|
|
466
|
+
{browserActive && (
|
|
467
|
+
<button
|
|
468
|
+
onClick={onStopBrowser}
|
|
469
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-[#3B82F6]/10 hover:bg-[#F43F5E]/15 transition-colors cursor-pointer group"
|
|
470
|
+
title="Stop browser session"
|
|
471
|
+
>
|
|
472
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-[#3B82F6] group-hover:text-[#F43F5E]">
|
|
473
|
+
<rect x="3" y="3" width="18" height="14" rx="2" />
|
|
474
|
+
<path d="M3 9h18" />
|
|
475
|
+
<circle cx="7" cy="6" r="0.5" fill="currentColor" />
|
|
476
|
+
<circle cx="10" cy="6" r="0.5" fill="currentColor" />
|
|
477
|
+
</svg>
|
|
478
|
+
<span className="text-[11px] font-600 text-[#3B82F6] group-hover:text-[#F43F5E]">
|
|
479
|
+
Browser Active
|
|
480
|
+
</span>
|
|
481
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/60 group-hover:text-[#F43F5E] shrink-0">
|
|
482
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
483
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
484
|
+
</svg>
|
|
485
|
+
</button>
|
|
486
|
+
)}
|
|
487
|
+
</div>
|
|
488
|
+
)}
|
|
489
|
+
</header>
|
|
490
|
+
)
|
|
491
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
import type { Session } from '@/types'
|
|
7
|
+
|
|
8
|
+
const TOOL_GROUPS: { label: string; tools: Record<string, string> }[] = [
|
|
9
|
+
{
|
|
10
|
+
label: 'Tools',
|
|
11
|
+
tools: {
|
|
12
|
+
shell: 'Shell',
|
|
13
|
+
files: 'Files',
|
|
14
|
+
edit_file: 'Edit File',
|
|
15
|
+
process: 'Process',
|
|
16
|
+
web_search: 'Web Search',
|
|
17
|
+
web_fetch: 'Web Fetch',
|
|
18
|
+
browser: 'Browser',
|
|
19
|
+
memory: 'Memory',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: 'Delegation',
|
|
24
|
+
tools: {
|
|
25
|
+
claude_code: 'Claude Code',
|
|
26
|
+
codex_cli: 'Codex CLI',
|
|
27
|
+
opencode_cli: 'OpenCode CLI',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: 'Platform',
|
|
32
|
+
tools: {
|
|
33
|
+
orchestrator: 'Orchestrator',
|
|
34
|
+
manage_agents: 'Agents',
|
|
35
|
+
manage_tasks: 'Tasks',
|
|
36
|
+
manage_schedules: 'Schedules',
|
|
37
|
+
manage_skills: 'Skills',
|
|
38
|
+
manage_documents: 'Documents',
|
|
39
|
+
manage_webhooks: 'Webhooks',
|
|
40
|
+
manage_connectors: 'Connectors',
|
|
41
|
+
manage_sessions: 'Sessions',
|
|
42
|
+
manage_secrets: 'Secrets',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
// Flat lookup for display names
|
|
48
|
+
const ALL_TOOLS: Record<string, string> = {}
|
|
49
|
+
for (const g of TOOL_GROUPS) Object.assign(ALL_TOOLS, g.tools)
|
|
50
|
+
|
|
51
|
+
const TOOL_HINTS: Record<string, string> = {
|
|
52
|
+
orchestrator: 'Can delegate tasks to other agents',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Props {
|
|
56
|
+
session: Session
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ChatToolToggles({ session }: Props) {
|
|
60
|
+
const [open, setOpen] = useState(false)
|
|
61
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
62
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
63
|
+
const agents = useAppStore((s) => s.agents)
|
|
64
|
+
const skills = useAppStore((s) => s.skills)
|
|
65
|
+
|
|
66
|
+
const agent = session.agentId ? agents[session.agentId] : null
|
|
67
|
+
const sessionTools: string[] = session.tools || []
|
|
68
|
+
|
|
69
|
+
// Agent's skill IDs
|
|
70
|
+
const agentSkillIds: string[] = agent?.skillIds || []
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!open) return
|
|
74
|
+
const handler = (e: MouseEvent) => {
|
|
75
|
+
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
|
|
76
|
+
}
|
|
77
|
+
document.addEventListener('mousedown', handler)
|
|
78
|
+
return () => document.removeEventListener('mousedown', handler)
|
|
79
|
+
}, [open])
|
|
80
|
+
|
|
81
|
+
const toggleTool = async (toolId: string) => {
|
|
82
|
+
const updated = sessionTools.includes(toolId)
|
|
83
|
+
? sessionTools.filter((t) => t !== toolId)
|
|
84
|
+
: [...sessionTools, toolId]
|
|
85
|
+
await api('PUT', `/sessions/${session.id}`, { tools: updated })
|
|
86
|
+
loadSessions()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const enabledCount = sessionTools.length
|
|
90
|
+
const totalCount = Object.keys(ALL_TOOLS).length
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="relative" ref={ref}>
|
|
94
|
+
<button
|
|
95
|
+
onClick={() => setOpen(!open)}
|
|
96
|
+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
|
|
97
|
+
${open ? 'bg-accent-soft text-accent-bright' : 'bg-white/[0.04] text-text-3 hover:bg-white/[0.07]'}`}
|
|
98
|
+
>
|
|
99
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
100
|
+
<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" />
|
|
101
|
+
</svg>
|
|
102
|
+
<span className="text-[11px] font-600">
|
|
103
|
+
{enabledCount}/{totalCount}
|
|
104
|
+
</span>
|
|
105
|
+
</button>
|
|
106
|
+
|
|
107
|
+
{open && (
|
|
108
|
+
<div className="absolute top-full left-0 mt-1.5 w-[260px] max-h-[420px] overflow-y-auto rounded-[12px] border border-white/[0.08] shadow-xl z-[120] overflow-hidden"
|
|
109
|
+
style={{ animation: 'fade-in 0.15s ease', backgroundColor: '#171a2b' }}>
|
|
110
|
+
|
|
111
|
+
{TOOL_GROUPS.map((group, gi) => (
|
|
112
|
+
<div key={group.label} className={`px-3 pb-1 ${gi === 0 ? 'pt-3' : 'pt-1 border-t border-white/[0.04]'}`}>
|
|
113
|
+
<p className="text-[10px] font-600 text-text-3/60 uppercase tracking-wider mb-2">{group.label}</p>
|
|
114
|
+
{Object.entries(group.tools).map(([toolId, label]) => {
|
|
115
|
+
const enabled = sessionTools.includes(toolId)
|
|
116
|
+
return (
|
|
117
|
+
<label key={toolId} className="flex items-center gap-2.5 py-1.5 cursor-pointer">
|
|
118
|
+
<div
|
|
119
|
+
onClick={() => toggleTool(toolId)}
|
|
120
|
+
className={`w-8 h-[18px] rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
121
|
+
${enabled ? 'bg-[#6366F1]' : 'bg-white/[0.12]'}`}
|
|
122
|
+
>
|
|
123
|
+
<div className={`absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white transition-all duration-200
|
|
124
|
+
${enabled ? 'left-[16px]' : 'left-[2px]'}`} />
|
|
125
|
+
</div>
|
|
126
|
+
<span className={`text-[12px] ${enabled ? 'text-text-2' : 'text-text-3/70'}`}>
|
|
127
|
+
{label}
|
|
128
|
+
{TOOL_HINTS[toolId] && (
|
|
129
|
+
<span className="ml-2 text-[10px] text-text-3/70 font-400">{TOOL_HINTS[toolId]}</span>
|
|
130
|
+
)}
|
|
131
|
+
</span>
|
|
132
|
+
</label>
|
|
133
|
+
)
|
|
134
|
+
})}
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
|
|
138
|
+
{agentSkillIds.length > 0 && (
|
|
139
|
+
<div className="px-3 pb-2 pt-1 border-t border-white/[0.04]">
|
|
140
|
+
<p className="text-[10px] font-600 text-text-3/60 uppercase tracking-wider mb-2">Skills</p>
|
|
141
|
+
{agentSkillIds.map((skillId) => {
|
|
142
|
+
const skill = skills[skillId]
|
|
143
|
+
if (!skill) return null
|
|
144
|
+
return (
|
|
145
|
+
<div key={skillId} className="flex items-center gap-2.5 py-1.5">
|
|
146
|
+
<span className="w-1.5 h-1.5 rounded-full bg-accent-bright/40 shrink-0" />
|
|
147
|
+
<span className="text-[12px] text-text-2 truncate">{skill.name}</span>
|
|
148
|
+
</div>
|
|
149
|
+
)
|
|
150
|
+
})}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
<div className="px-3 py-2 border-t border-white/[0.04] bg-white/[0.02]">
|
|
155
|
+
<p className="text-[10px] text-text-3/70">Changes apply to the next message</p>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
}
|