@swarmclawai/swarmclaw 0.7.1 → 0.7.3
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 +155 -150
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +37 -9
- package/src/app/api/agents/route.ts +13 -2
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
- package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
- package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
- package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
- package/src/app/api/{sessions → chats}/route.ts +21 -7
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/skills/route.ts +11 -3
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +6 -26
- package/src/app/api/plugins/settings/route.ts +40 -0
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/usage/route.ts +30 -0
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +39 -33
- package/src/cli/index.ts +43 -49
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +16 -13
- package/src/components/agents/agent-chat-list.tsx +104 -4
- package/src/components/agents/agent-list.tsx +54 -22
- package/src/components/agents/agent-sheet.tsx +209 -18
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +110 -50
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +39 -27
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
- package/src/components/chat/chat-header.tsx +299 -314
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +5 -3
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +218 -1
- package/src/components/home/home-view.tsx +129 -5
- package/src/components/layout/app-layout.tsx +392 -182
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/plugins/plugin-list.tsx +487 -254
- package/src/components/plugins/plugin-sheet.tsx +236 -13
- package/src/components/projects/project-detail.tsx +183 -0
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -25
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +78 -1
- package/src/components/shared/settings/section-orchestrator.tsx +3 -3
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +244 -56
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +147 -1
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +8 -8
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +36 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
- package/src/lib/server/chat-execution.ts +285 -165
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +67 -2
- package/src/lib/server/chatroom-helpers.ts +48 -8
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +948 -112
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +188 -9
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +61 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/heartbeat-service.ts +14 -40
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +28 -1103
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +5 -6
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +20 -9
- package/src/lib/server/orchestrator.ts +7 -7
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +927 -66
- package/src/lib/server/provider-health.ts +38 -6
- package/src/lib/server/queue.ts +13 -28
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -82
- package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
- package/src/lib/server/session-tools/calendar.ts +366 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +114 -10
- package/src/lib/server/session-tools/context.ts +21 -5
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +74 -28
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +497 -24
- package/src/lib/server/session-tools/discovery.ts +24 -6
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +320 -0
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +241 -25
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +380 -0
- package/src/lib/server/session-tools/index.ts +130 -50
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +172 -3
- package/src/lib/server/session-tools/monitor.ts +151 -8
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +148 -7
- package/src/lib/server/session-tools/plugin-creator.ts +89 -26
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +301 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +24 -12
- package/src/lib/server/session-tools/session-info.ts +43 -7
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +194 -28
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +42 -12
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +926 -91
- package/src/lib/server/storage.ts +255 -16
- package/src/lib/server/stream-agent-chat.ts +116 -268
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -10
- package/src/lib/server/tool-aliases.ts +66 -18
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +38 -27
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +10 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +5 -11
- package/src/stores/use-chat-store.ts +38 -9
- package/src/types/index.ts +352 -47
- package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
- package/src/components/sessions/new-session-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -24
- package/src/lib/server/session-run-manager.test.ts +0 -23
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState, useMemo, useRef, useCallback } from 'react'
|
|
3
|
+
import { useEffect, useState, useMemo, useRef, useCallback, type ReactNode } from 'react'
|
|
4
4
|
import type { Session } from '@/types'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
@@ -14,11 +14,59 @@ import {
|
|
|
14
14
|
} from '@/components/shared/connector-platform-icon'
|
|
15
15
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
16
16
|
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
17
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
|
17
18
|
import { toast } from 'sonner'
|
|
18
19
|
import type { ProviderType } from '@/types'
|
|
19
20
|
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
21
|
+
import { buildOpenClawMainSessionKey } from '@/lib/openclaw-agent-id'
|
|
20
22
|
import { useWs } from '@/hooks/use-ws'
|
|
21
23
|
|
|
24
|
+
function Tip({ label, children, side = 'bottom' }: { label: string; children: ReactNode; side?: 'top' | 'bottom' | 'left' | 'right' }) {
|
|
25
|
+
return (
|
|
26
|
+
<Tooltip>
|
|
27
|
+
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
|
28
|
+
<TooltipContent side={side} sideOffset={6}
|
|
29
|
+
className="bg-raised border border-white/[0.08] text-text shadow-[0_8px_32px_rgba(0,0,0,0.5)] rounded-[8px] px-2.5 py-1.5 text-[11px] z-[100]">
|
|
30
|
+
{label}
|
|
31
|
+
</TooltipContent>
|
|
32
|
+
</Tooltip>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function HeaderChip({
|
|
37
|
+
children,
|
|
38
|
+
title,
|
|
39
|
+
onClick,
|
|
40
|
+
className = '',
|
|
41
|
+
active = false,
|
|
42
|
+
}: {
|
|
43
|
+
children: ReactNode
|
|
44
|
+
title?: string
|
|
45
|
+
onClick?: () => void
|
|
46
|
+
className?: string
|
|
47
|
+
active?: boolean
|
|
48
|
+
}) {
|
|
49
|
+
const baseClass = `inline-flex max-w-full items-center gap-1.5 rounded-[9px] border px-2.5 py-1 text-[10px] font-600 backdrop-blur-sm transition-colors ${
|
|
50
|
+
active
|
|
51
|
+
? 'border-accent-bright/20 bg-accent-soft/50 text-accent-bright'
|
|
52
|
+
: 'border-white/[0.06] bg-white/[0.03] text-text-3/68'
|
|
53
|
+
} ${onClick ? 'cursor-pointer hover:border-white/[0.1] hover:bg-white/[0.06] hover:text-text-2' : ''} ${className}`
|
|
54
|
+
|
|
55
|
+
if (onClick) {
|
|
56
|
+
return (
|
|
57
|
+
<button type="button" onClick={onClick} title={title} className={baseClass}>
|
|
58
|
+
{children}
|
|
59
|
+
</button>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<span title={title} className={baseClass}>
|
|
65
|
+
{children}
|
|
66
|
+
</span>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
22
70
|
function shortPath(p: string): string {
|
|
23
71
|
return (p || '').replace(/^\/Users\/\w+/, '~')
|
|
24
72
|
}
|
|
@@ -87,6 +135,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
87
135
|
const providers = useAppStore((s) => s.providers)
|
|
88
136
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
89
137
|
const modelName = session.model || agent?.model || ''
|
|
138
|
+
const providerLabel = PROVIDER_LABELS[session.provider] || session.provider
|
|
90
139
|
const [modelSwitcherOpen, setModelSwitcherOpen] = useState(false)
|
|
91
140
|
const modelSwitcherRef = useRef<HTMLDivElement>(null)
|
|
92
141
|
const [copied, setCopied] = useState(false)
|
|
@@ -95,9 +144,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
95
144
|
const hbDropdownRef = useRef<HTMLDivElement>(null)
|
|
96
145
|
const [sourceDropdownOpen, setSourceDropdownOpen] = useState(false)
|
|
97
146
|
const sourceDropdownRef = useRef<HTMLDivElement>(null)
|
|
98
|
-
const [mainLoopSaving, setMainLoopSaving] = useState(false)
|
|
99
|
-
const [mainLoopError, setMainLoopError] = useState('')
|
|
100
|
-
const [mainLoopNotice, setMainLoopNotice] = useState('')
|
|
101
147
|
const [syncingHistory, setSyncingHistory] = useState(false)
|
|
102
148
|
const [syncResult, setSyncResult] = useState('')
|
|
103
149
|
const [renaming, setRenaming] = useState(false)
|
|
@@ -134,6 +180,46 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
134
180
|
}, [fetchWalletBalance])
|
|
135
181
|
useWs('wallets', fetchWalletBalance)
|
|
136
182
|
|
|
183
|
+
const workspaceLabel = useMemo(() => shortPath(session.cwd), [session.cwd])
|
|
184
|
+
const liveStatus = agentStatus || null
|
|
185
|
+
const threadContextLabel = useMemo(() => {
|
|
186
|
+
const title = session.connectorContext?.threadTitle?.trim()
|
|
187
|
+
if (title) return title
|
|
188
|
+
const persona = session.connectorContext?.threadPersonaLabel?.trim()
|
|
189
|
+
if (persona) return persona
|
|
190
|
+
return null
|
|
191
|
+
}, [session.connectorContext?.threadPersonaLabel, session.connectorContext?.threadTitle])
|
|
192
|
+
const connectorPresenceMeta = useMemo(() => {
|
|
193
|
+
if (!connector) return null
|
|
194
|
+
const lastAt = connectorPresence?.lastMessageAt
|
|
195
|
+
if (!lastAt) {
|
|
196
|
+
return {
|
|
197
|
+
label: 'Idle',
|
|
198
|
+
dotClass: 'bg-text-3/30',
|
|
199
|
+
textClass: 'text-text-3/45',
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const ago = Date.now() - lastAt
|
|
203
|
+
if (ago < 5 * 60_000) {
|
|
204
|
+
return {
|
|
205
|
+
label: 'Active',
|
|
206
|
+
dotClass: 'bg-emerald-400',
|
|
207
|
+
textClass: 'text-emerald-400',
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (ago < 30 * 60_000) {
|
|
211
|
+
return {
|
|
212
|
+
label: `${Math.floor(ago / 60_000)}m ago`,
|
|
213
|
+
dotClass: 'bg-amber-400',
|
|
214
|
+
textClass: 'text-amber-300',
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
label: 'Idle',
|
|
219
|
+
dotClass: 'bg-text-3/30',
|
|
220
|
+
textClass: 'text-text-3/45',
|
|
221
|
+
}
|
|
222
|
+
}, [connector, connectorPresence?.lastMessageAt])
|
|
137
223
|
|
|
138
224
|
const visibleHeaderWidgets = useMemo(() => {
|
|
139
225
|
const seen = new Set<string>()
|
|
@@ -145,6 +231,25 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
145
231
|
})
|
|
146
232
|
}, [headerWidgets])
|
|
147
233
|
|
|
234
|
+
const walletHeaderMeta = useMemo(() => {
|
|
235
|
+
if (!agent?.id) {
|
|
236
|
+
return {
|
|
237
|
+
label: 'Wallets',
|
|
238
|
+
title: 'Open wallets',
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (!agent.walletId) {
|
|
242
|
+
return {
|
|
243
|
+
label: 'Create wallet',
|
|
244
|
+
title: 'Create wallet',
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
label: walletBalance !== null ? `${walletBalance.toFixed(3)} SOL` : 'Wallet',
|
|
249
|
+
title: 'View wallet',
|
|
250
|
+
}
|
|
251
|
+
}, [agent?.id, agent?.walletId, walletBalance])
|
|
252
|
+
|
|
148
253
|
const handleHeaderWidgetClick = (widgetId: string) => {
|
|
149
254
|
if (widgetId === 'wallet-status') {
|
|
150
255
|
if (agent?.id) setWalletPanelAgentId(agent.id)
|
|
@@ -197,17 +302,17 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
197
302
|
const handleDismissResumeHandle = async (e: React.MouseEvent) => {
|
|
198
303
|
e.stopPropagation()
|
|
199
304
|
try {
|
|
200
|
-
await api('PUT', `/
|
|
305
|
+
await api('PUT', `/chats/${session.id}`, {
|
|
201
306
|
claudeSessionId: null,
|
|
202
307
|
codexThreadId: null,
|
|
203
308
|
opencodeSessionId: null,
|
|
204
|
-
delegateResumeIds: { claudeCode: null, codex: null, opencode: null },
|
|
309
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
|
|
205
310
|
})
|
|
206
311
|
await loadSessions()
|
|
207
312
|
} catch { /* best-effort */ }
|
|
208
313
|
}
|
|
209
314
|
|
|
210
|
-
const heartbeatSupported = (session.
|
|
315
|
+
const heartbeatSupported = (session.plugins?.length ?? 0) > 0
|
|
211
316
|
const loopIsOngoing = appSettings.loopMode === 'ongoing'
|
|
212
317
|
const { heartbeatEnabled, heartbeatIntervalSec, heartbeatExplicitOptIn } = useMemo(() => {
|
|
213
318
|
// Resolve through the same cascade as the backend: settings → agent → session
|
|
@@ -254,13 +359,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
254
359
|
}
|
|
255
360
|
}, [appSettings, agent, session])
|
|
256
361
|
const heartbeatWillRun = heartbeatEnabled && (loopIsOngoing || heartbeatExplicitOptIn)
|
|
257
|
-
const isMainSession = session.name === '__main__'
|
|
258
|
-
const missionState = session.mainLoopState || {}
|
|
259
|
-
const missionPaused = missionState.paused === true
|
|
260
|
-
const missionMode = missionState.autonomyMode === 'assist' ? 'assist' : 'autonomous'
|
|
261
|
-
const missionStatus = missionState.status || 'idle'
|
|
262
|
-
const missionMomentum = typeof missionState.momentumScore === 'number' ? missionState.momentumScore : null
|
|
263
|
-
const missionEventsCount = missionState.pendingEvents?.length || 0
|
|
264
362
|
|
|
265
363
|
const handleToggleHeartbeat = async () => {
|
|
266
364
|
if (!heartbeatSupported || heartbeatSaving) return
|
|
@@ -270,10 +368,10 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
270
368
|
if (session.agentId) {
|
|
271
369
|
await api('PUT', `/agents/${session.agentId}`, { heartbeatEnabled: next })
|
|
272
370
|
// Clear any stale session-level override so the agent value wins
|
|
273
|
-
await api('PUT', `/
|
|
371
|
+
await api('PUT', `/chats/${session.id}`, { heartbeatEnabled: null })
|
|
274
372
|
await Promise.all([loadAgents(), loadSessions()])
|
|
275
373
|
} else {
|
|
276
|
-
await api('PUT', `/
|
|
374
|
+
await api('PUT', `/chats/${session.id}`, { heartbeatEnabled: next })
|
|
277
375
|
await loadSessions()
|
|
278
376
|
}
|
|
279
377
|
toast.success(`Heartbeat ${next ? 'enabled' : 'disabled'}`)
|
|
@@ -295,10 +393,10 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
295
393
|
heartbeatEnabled: true,
|
|
296
394
|
})
|
|
297
395
|
// Clear stale session-level overrides
|
|
298
|
-
await api('PUT', `/
|
|
396
|
+
await api('PUT', `/chats/${session.id}`, { heartbeatIntervalSec: null, heartbeatEnabled: null })
|
|
299
397
|
await Promise.all([loadAgents(), loadSessions()])
|
|
300
398
|
} else {
|
|
301
|
-
await api('PUT', `/
|
|
399
|
+
await api('PUT', `/chats/${session.id}`, { heartbeatIntervalSec: sec, heartbeatEnabled: true })
|
|
302
400
|
await loadSessions()
|
|
303
401
|
}
|
|
304
402
|
} finally {
|
|
@@ -306,63 +404,8 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
306
404
|
}
|
|
307
405
|
}
|
|
308
406
|
|
|
309
|
-
const postMainLoopAction = async (action: string, extra?: Record<string, unknown>) => {
|
|
310
|
-
if (!isMainSession || mainLoopSaving) return
|
|
311
|
-
setMainLoopSaving(true)
|
|
312
|
-
try {
|
|
313
|
-
const result = await api<{ runId?: string; deduped?: boolean }>('POST', `/sessions/${session.id}/main-loop`, {
|
|
314
|
-
action,
|
|
315
|
-
...(extra || {}),
|
|
316
|
-
})
|
|
317
|
-
setMainLoopError('')
|
|
318
|
-
if (action === 'nudge') {
|
|
319
|
-
setMainLoopNotice(result?.deduped ? 'Nudge already queued.' : 'Nudge queued.')
|
|
320
|
-
} else if (action === 'set_mode') {
|
|
321
|
-
setMainLoopNotice(`Mode set to ${extra?.mode === 'assist' ? 'Assist' : 'Auto'}.`)
|
|
322
|
-
} else {
|
|
323
|
-
setMainLoopNotice('')
|
|
324
|
-
}
|
|
325
|
-
await loadSessions()
|
|
326
|
-
} catch (err: unknown) {
|
|
327
|
-
const message = err instanceof Error ? err.message : 'Failed to update mission controls.'
|
|
328
|
-
setMainLoopError(message)
|
|
329
|
-
} finally {
|
|
330
|
-
setMainLoopSaving(false)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const handleToggleMissionPause = () => {
|
|
335
|
-
void postMainLoopAction(missionPaused ? 'resume' : 'pause')
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const handleToggleMissionMode = () => {
|
|
339
|
-
const nextMode = missionMode === 'autonomous' ? 'assist' : 'autonomous'
|
|
340
|
-
void postMainLoopAction('set_mode', { mode: nextMode })
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const handleNudgeMission = () => {
|
|
344
|
-
void postMainLoopAction('nudge')
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const handleSetMissionGoal = () => {
|
|
348
|
-
if (!isMainSession) return
|
|
349
|
-
const seededGoal = typeof missionState.goal === 'string' ? missionState.goal : ''
|
|
350
|
-
const raw = window.prompt('Set mission goal', seededGoal)
|
|
351
|
-
const goal = (raw || '').trim()
|
|
352
|
-
if (!goal) return
|
|
353
|
-
void postMainLoopAction('set_goal', { goal })
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const handleClearMissionEvents = () => {
|
|
357
|
-
if (!isMainSession || missionEventsCount <= 0) return
|
|
358
|
-
void postMainLoopAction('clear_events')
|
|
359
|
-
}
|
|
360
|
-
|
|
361
407
|
const isOpenClawAgent = agent?.provider === 'openclaw'
|
|
362
|
-
|
|
363
|
-
const openclawSessionKey = isOpenClawAgent && agent
|
|
364
|
-
? `agent:${agent.name.toLowerCase().replace(/\s+/g, '-')}:main`
|
|
365
|
-
: null
|
|
408
|
+
const openclawSessionKey = isOpenClawAgent ? buildOpenClawMainSessionKey(agent?.name) : null
|
|
366
409
|
|
|
367
410
|
const handleSyncHistory = async () => {
|
|
368
411
|
if (!openclawSessionKey || syncingHistory) return
|
|
@@ -473,7 +516,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
473
516
|
const handleModelSwitch = async (nextProvider: ProviderType, nextModel: string) => {
|
|
474
517
|
setModelSwitcherOpen(false)
|
|
475
518
|
try {
|
|
476
|
-
await api('PUT', `/
|
|
519
|
+
await api('PUT', `/chats/${session.id}`, { provider: nextProvider, model: nextModel })
|
|
477
520
|
await loadSessions()
|
|
478
521
|
} catch (err: unknown) {
|
|
479
522
|
toast.error(err instanceof Error ? err.message : 'Failed to switch model')
|
|
@@ -490,35 +533,28 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
490
533
|
}, [session.name, loadConnectors])
|
|
491
534
|
|
|
492
535
|
useEffect(() => {
|
|
493
|
-
setMainLoopError('')
|
|
494
|
-
setMainLoopNotice('')
|
|
495
536
|
setModelSwitcherOpen(false)
|
|
496
537
|
}, [session.id])
|
|
497
538
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return () => clearTimeout(timer)
|
|
502
|
-
}, [mainLoopNotice])
|
|
503
|
-
|
|
504
|
-
// Context bar shows for tools, mission controls, memories, source filter, task links, resume handles, browser
|
|
505
|
-
const hasToolToggles = ((agent?.tools?.length ?? 0) > 0) || ((session.tools?.length ?? 0) > 0)
|
|
506
|
-
const hasMemoryLink = !!(agent && session.tools?.includes('memory'))
|
|
539
|
+
// Context bar shows for tools, memories, source filter, task links, resume handles, browser
|
|
540
|
+
const hasToolToggles = ((agent?.plugins?.length ?? 0) > 0) || ((session.plugins?.length ?? 0) > 0)
|
|
541
|
+
const hasMemoryLink = !!(agent && session.plugins?.includes('memory'))
|
|
507
542
|
const hasSourceFilter = !!hasMultipleSources
|
|
508
|
-
const hasContextBar = !!(
|
|
543
|
+
const hasContextBar = !!(hasMemoryLink || hasSourceFilter || linkedTask || resumeHandle || (isOpenClawAgent && openclawSessionKey) || browserActive)
|
|
509
544
|
|
|
510
545
|
return (
|
|
546
|
+
<>
|
|
511
547
|
<header
|
|
512
548
|
className="relative z-20 border-b border-white/[0.06] shrink-0"
|
|
513
549
|
style={{
|
|
514
|
-
background: 'linear-gradient(180deg, rgba(var(--rgb-bg, 15,15,26), 0.
|
|
550
|
+
background: 'radial-gradient(circle at top left, rgba(66, 211, 255, 0.08), transparent 32%), radial-gradient(circle at top right, rgba(255, 190, 92, 0.05), transparent 28%), linear-gradient(180deg, rgba(var(--rgb-bg, 15,15,26), 0.96) 0%, rgba(var(--rgb-bg, 15,15,26), 0.9) 100%)',
|
|
515
551
|
backdropFilter: 'blur(20px) saturate(1.4)',
|
|
516
552
|
WebkitBackdropFilter: 'blur(20px) saturate(1.4)',
|
|
517
553
|
...(mobile ? { paddingTop: 'max(12px, env(safe-area-inset-top))' } : {}),
|
|
518
554
|
}}
|
|
519
555
|
>
|
|
520
556
|
{/* Main row */}
|
|
521
|
-
<div className="flex items-
|
|
557
|
+
<div className="flex flex-wrap items-start gap-3 px-4 py-2.5 min-h-[64px]">
|
|
522
558
|
{/* Back button */}
|
|
523
559
|
{onBack && (
|
|
524
560
|
<IconButton onClick={onBack} aria-label="Go back" size="sm">
|
|
@@ -559,10 +595,8 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
559
595
|
)}
|
|
560
596
|
|
|
561
597
|
{/* Identity + metadata — fills center */}
|
|
562
|
-
<div className="
|
|
563
|
-
|
|
564
|
-
<div className="flex flex-col gap-0.5 min-w-0 shrink">
|
|
565
|
-
<div className="flex items-center gap-2 min-w-0">
|
|
598
|
+
<div className="min-w-0 flex-1">
|
|
599
|
+
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
566
600
|
{renaming && agent ? (
|
|
567
601
|
<span ref={renameContainerRef} className="inline-flex items-center gap-2">
|
|
568
602
|
<input
|
|
@@ -580,99 +614,90 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
580
614
|
{renameSaving && <span className="w-3 h-3 rounded-full border-2 border-text-3/30 border-t-accent-bright animate-spin shrink-0" />}
|
|
581
615
|
{renameError && <span className="text-[10px] text-red-400 shrink-0">{renameError}</span>}
|
|
582
616
|
</span>
|
|
617
|
+
) : agent ? (
|
|
618
|
+
<button
|
|
619
|
+
type="button"
|
|
620
|
+
onClick={startRename}
|
|
621
|
+
title="Rename agent"
|
|
622
|
+
className="group/title inline-flex min-w-0 items-center gap-1.5 rounded-[9px] px-1 py-0.5 text-left transition-colors hover:bg-white/[0.03] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent-bright/40"
|
|
623
|
+
>
|
|
624
|
+
<span className="font-display text-[16px] font-700 truncate tracking-[-0.02em] text-text transition-colors group-hover/title:text-accent-bright">
|
|
625
|
+
{(session.shortcutForAgentId && agent.id === session.shortcutForAgentId) || agent.threadSessionId === session.id
|
|
626
|
+
? agent.name
|
|
627
|
+
: session.name}
|
|
628
|
+
</span>
|
|
629
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0 text-text-3/40 opacity-0 transition-opacity group-hover/title:opacity-100 group-focus-visible/title:opacity-100">
|
|
630
|
+
<path d="M12 20h9" />
|
|
631
|
+
<path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" />
|
|
632
|
+
</svg>
|
|
633
|
+
</button>
|
|
583
634
|
) : (
|
|
584
|
-
<span
|
|
585
|
-
className={`font-display text-[15px] font-700 truncate tracking-[-0.02em] text-text${agent ? ' cursor-pointer hover:text-accent-bright transition-colors duration-200' : ''}`}
|
|
586
|
-
onClick={agent ? startRename : undefined}
|
|
587
|
-
title={agent ? 'Click to rename' : undefined}
|
|
588
|
-
>{
|
|
589
|
-
session.name === '__main__' ? 'Main Chat'
|
|
590
|
-
: session.name.startsWith('agent-thread:') ? (agent?.name || session.name)
|
|
591
|
-
: session.name
|
|
592
|
-
}</span>
|
|
635
|
+
<span className="font-display text-[16px] font-700 truncate tracking-[-0.02em] text-text">{session.name}</span>
|
|
593
636
|
)}
|
|
594
637
|
{connector && connectorMeta && (
|
|
595
638
|
<span
|
|
596
|
-
className="inline-flex items-center gap-1 px-
|
|
639
|
+
className="inline-flex min-w-0 items-center gap-1 px-2 py-1 rounded-[8px] border text-[10px] font-700 uppercase tracking-wider shrink-0"
|
|
597
640
|
style={{
|
|
598
641
|
color: connectorMeta.color,
|
|
599
|
-
backgroundColor: `${connectorMeta.color}
|
|
600
|
-
borderColor: `${connectorMeta.color}
|
|
642
|
+
backgroundColor: `${connectorMeta.color}12`,
|
|
643
|
+
borderColor: `${connectorMeta.color}22`,
|
|
601
644
|
}}
|
|
602
645
|
title={`${connector.name} connector`}
|
|
603
646
|
>
|
|
604
647
|
<ConnectorPlatformIcon platform={connector.platform} size={10} />
|
|
605
|
-
{connectorMeta.label}
|
|
648
|
+
<span className="truncate max-w-[140px]">{connectorMeta.label}</span>
|
|
606
649
|
</span>
|
|
607
650
|
)}
|
|
608
|
-
{
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const ago = Date.now() - lastAt
|
|
617
|
-
const isActive = ago < 5 * 60_000
|
|
618
|
-
const isRecent = ago < 30 * 60_000
|
|
619
|
-
const label = isActive ? 'Active' : isRecent ? `${Math.floor(ago / 60_000)}m ago` : 'Idle'
|
|
620
|
-
const dotColor = isActive ? 'bg-emerald-400' : isRecent ? 'bg-amber-400' : 'bg-text-3/30'
|
|
621
|
-
const textColor = isActive ? 'text-emerald-400' : isRecent ? 'text-amber-300' : 'text-text-3/40'
|
|
622
|
-
return (
|
|
623
|
-
<span className={`shrink-0 inline-flex items-center gap-1 text-[10px] ${textColor}`}>
|
|
624
|
-
<span className={`w-1.5 h-1.5 rounded-full ${dotColor}`} />
|
|
625
|
-
{label}
|
|
626
|
-
</span>
|
|
627
|
-
)
|
|
628
|
-
})()}
|
|
629
|
-
{agent?.isOrchestrator && (
|
|
630
|
-
<span className="px-1.5 py-0.5 rounded-[5px] bg-amber-500/10 text-amber-500 text-[9px] font-700 uppercase tracking-wider shrink-0">Orch</span>
|
|
651
|
+
{connectorPresenceMeta && (
|
|
652
|
+
<HeaderChip className={`${connectorPresenceMeta.textClass} shrink-0`}>
|
|
653
|
+
<span className={`w-1.5 h-1.5 rounded-full ${connectorPresenceMeta.dotClass}`} />
|
|
654
|
+
{connectorPresenceMeta.label}
|
|
655
|
+
</HeaderChip>
|
|
656
|
+
)}
|
|
657
|
+
{agent?.platformAssignScope === 'all' && (
|
|
658
|
+
<HeaderChip className="bg-amber-500/10 border-amber-500/15 text-amber-400 shrink-0">Delegates</HeaderChip>
|
|
631
659
|
)}
|
|
632
660
|
{streaming && (
|
|
633
|
-
<
|
|
661
|
+
<HeaderChip className="bg-accent-soft/60 border-accent-bright/20 text-accent-bright shrink-0">
|
|
662
|
+
<span className="w-1.5 h-1.5 rounded-full bg-accent-bright" style={{ animation: 'pulse 1.5s ease infinite' }} />
|
|
663
|
+
Responding
|
|
664
|
+
</HeaderChip>
|
|
634
665
|
)}
|
|
635
666
|
</div>
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
{/* Metadata tray: wallet · model · path · status */}
|
|
640
|
-
<div className="flex items-center gap-1.5 min-w-0 overflow-hidden">
|
|
641
|
-
<span className="text-text-3/10 text-[10px] select-none shrink-0">/</span>
|
|
667
|
+
<div className="mt-1.5 flex min-w-0 flex-wrap items-center gap-1.5">
|
|
668
|
+
{hasToolToggles && <ChatToolToggles session={session} />}
|
|
642
669
|
{visibleHeaderWidgets.map((widget) => {
|
|
643
670
|
const actionable = widget.id === 'wallet-status'
|
|
644
|
-
const walletLabel =
|
|
645
|
-
?
|
|
671
|
+
const walletLabel = actionable
|
|
672
|
+
? walletHeaderMeta.label
|
|
646
673
|
: (widget.label || 'Wallet')
|
|
674
|
+
const widgetTitle = actionable
|
|
675
|
+
? walletHeaderMeta.title
|
|
676
|
+
: widget.label
|
|
647
677
|
return (
|
|
648
|
-
<
|
|
678
|
+
<HeaderChip
|
|
649
679
|
key={widget.id}
|
|
650
|
-
type="button"
|
|
651
680
|
onClick={actionable ? () => handleHeaderWidgetClick(widget.id) : undefined}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}`}
|
|
655
|
-
title={actionable ? 'View wallet' : widget.label}
|
|
681
|
+
title={widgetTitle}
|
|
682
|
+
className={actionable ? 'text-text-3/80' : ''}
|
|
656
683
|
>
|
|
657
684
|
{actionable ? (
|
|
658
685
|
<>
|
|
659
|
-
<svg width="
|
|
686
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
660
687
|
<rect x="2" y="6" width="20" height="14" rx="2" />
|
|
661
688
|
<path d="M22 10H18a2 2 0 0 0 0 4h4" />
|
|
662
689
|
</svg>
|
|
663
|
-
{walletLabel}
|
|
690
|
+
<span className="truncate max-w-[120px]">{walletLabel}</span>
|
|
664
691
|
</>
|
|
665
692
|
) : (
|
|
666
|
-
widget.label
|
|
693
|
+
<span className="truncate max-w-[120px]">{widget.label}</span>
|
|
667
694
|
)}
|
|
668
|
-
</
|
|
695
|
+
</HeaderChip>
|
|
669
696
|
)
|
|
670
697
|
})}
|
|
671
|
-
{visibleHeaderWidgets.length > 0 && (
|
|
672
|
-
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
673
|
-
)}
|
|
674
698
|
{modelName && (
|
|
675
699
|
<div className="relative shrink-0" ref={modelSwitcherRef}>
|
|
700
|
+
<Tip label={`Switch model (${providerLabel})`}>
|
|
676
701
|
<button
|
|
677
702
|
type="button"
|
|
678
703
|
onClick={() => {
|
|
@@ -680,16 +705,19 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
680
705
|
setModelSwitcherOpen((o) => { if (!o) void loadProviders(); return !o })
|
|
681
706
|
}}
|
|
682
707
|
disabled={streaming}
|
|
683
|
-
className="inline-flex items-center gap-1
|
|
684
|
-
title="Switch model"
|
|
708
|
+
className="inline-flex max-w-full items-center gap-1.5 rounded-[9px] border border-white/[0.06] bg-white/[0.03] px-2.5 py-1 text-[10px] font-600 text-text-3/70 backdrop-blur-sm transition-colors hover:border-white/[0.1] hover:bg-white/[0.06] hover:text-text-2 disabled:cursor-default disabled:opacity-60"
|
|
685
709
|
>
|
|
686
|
-
|
|
687
|
-
|
|
710
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
711
|
+
<path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3Z" />
|
|
712
|
+
</svg>
|
|
713
|
+
<span className="truncate max-w-[min(42vw,220px)]">{mobile ? modelName : `${providerLabel} · ${modelName}`}</span>
|
|
714
|
+
<svg width="7" height="7" viewBox="0 0 16 16" fill="none" className="shrink-0 opacity-40">
|
|
688
715
|
<path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
689
716
|
</svg>
|
|
690
717
|
</button>
|
|
718
|
+
</Tip>
|
|
691
719
|
{modelSwitcherOpen && (
|
|
692
|
-
<div className="absolute z-50 top-full left-0 mt-2 w-[
|
|
720
|
+
<div className="absolute z-50 top-full right-0 sm:left-0 sm:right-auto mt-2 w-[min(320px,calc(100vw-2rem))] max-w-[calc(100vw-2rem)] rounded-[12px] border border-white/[0.08] bg-surface backdrop-blur-md shadow-xl p-3">
|
|
693
721
|
<div className="text-[10px] font-600 text-text-3/50 uppercase tracking-wider mb-2">Provider</div>
|
|
694
722
|
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
695
723
|
{providers.map((p) => (
|
|
@@ -717,110 +745,108 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
717
745
|
)}
|
|
718
746
|
</div>
|
|
719
747
|
)}
|
|
720
|
-
|
|
721
|
-
|
|
748
|
+
{threadContextLabel && (
|
|
749
|
+
<HeaderChip title={threadContextLabel}>
|
|
750
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
751
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z" />
|
|
752
|
+
</svg>
|
|
753
|
+
<span className="truncate max-w-[min(42vw,220px)]">{threadContextLabel}</span>
|
|
754
|
+
</HeaderChip>
|
|
755
|
+
)}
|
|
756
|
+
<Tip label={`Open working directory: ${workspaceLabel}`}>
|
|
757
|
+
<HeaderChip
|
|
722
758
|
onClick={() => { api('POST', '/files/open', { path: session.cwd }).catch(() => {}) }}
|
|
723
|
-
|
|
724
|
-
|
|
759
|
+
title={workspaceLabel}
|
|
760
|
+
className="max-w-[min(44vw,220px)]"
|
|
725
761
|
>
|
|
726
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
762
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
727
763
|
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
|
728
764
|
</svg>
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
)}
|
|
757
|
-
{liveStatus.goal && (
|
|
758
|
-
<span className="text-[10px] text-text-3/40 font-mono truncate max-w-[180px]" title={liveStatus.goal}>
|
|
759
|
-
{liveStatus.goal}
|
|
760
|
-
</span>
|
|
761
|
-
)}
|
|
762
|
-
{liveStatus.nextAction && (
|
|
763
|
-
<>
|
|
764
|
-
<span className="text-[9px] text-text-3/20 shrink-0">→</span>
|
|
765
|
-
<span className="text-[10px] text-text-3/35 font-mono truncate max-w-[140px]" title={liveStatus.nextAction}>
|
|
766
|
-
{liveStatus.nextAction}
|
|
767
|
-
</span>
|
|
768
|
-
</>
|
|
769
|
-
)}
|
|
770
|
-
</>
|
|
771
|
-
)
|
|
772
|
-
})()}
|
|
765
|
+
<span className="truncate">{mobile ? 'Workspace' : workspaceLabel}</span>
|
|
766
|
+
</HeaderChip>
|
|
767
|
+
</Tip>
|
|
768
|
+
{liveStatus?.status && (
|
|
769
|
+
<HeaderChip
|
|
770
|
+
className={`${
|
|
771
|
+
liveStatus.status === 'blocked' ? 'bg-amber-400/12 border-amber-400/15 text-amber-300'
|
|
772
|
+
: liveStatus.status === 'ok' ? 'bg-emerald-400/12 border-emerald-400/15 text-emerald-400'
|
|
773
|
+
: liveStatus.status === 'progress' ? 'bg-blue-500/12 border-blue-500/15 text-blue-400'
|
|
774
|
+
: 'text-text-3/60'
|
|
775
|
+
}`}
|
|
776
|
+
title={liveStatus.goal || liveStatus.summary || liveStatus.nextAction || liveStatus.status}
|
|
777
|
+
>
|
|
778
|
+
<span className={`w-1.5 h-1.5 rounded-full ${
|
|
779
|
+
liveStatus.status === 'blocked' ? 'bg-amber-300'
|
|
780
|
+
: liveStatus.status === 'ok' ? 'bg-emerald-400'
|
|
781
|
+
: liveStatus.status === 'progress' ? 'bg-blue-400'
|
|
782
|
+
: 'bg-text-3/30'
|
|
783
|
+
}`} />
|
|
784
|
+
{liveStatus.status}
|
|
785
|
+
</HeaderChip>
|
|
786
|
+
)}
|
|
787
|
+
{!mobile && liveStatus?.nextAction && (
|
|
788
|
+
<span className="text-[10px] text-text-3/45 font-mono truncate max-w-[min(34vw,220px)]" title={liveStatus.nextAction}>
|
|
789
|
+
Next: {liveStatus.nextAction}
|
|
790
|
+
</span>
|
|
791
|
+
)}
|
|
773
792
|
</div>
|
|
774
793
|
</div>
|
|
775
794
|
|
|
776
|
-
{
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
<
|
|
780
|
-
|
|
781
|
-
disabled={heartbeatSaving}
|
|
782
|
-
className={`flex items-center gap-1.5 pl-2.5 pr-1.5 py-1 transition-colors cursor-pointer border-none text-[11px] font-600
|
|
783
|
-
${heartbeatWillRun ? 'text-emerald-400 hover:bg-emerald-500/10' : 'text-text-3/60 hover:bg-white/[0.04]'}`}
|
|
784
|
-
title={heartbeatWillRun ? 'Disable heartbeat' : 'Enable heartbeat'}
|
|
785
|
-
>
|
|
786
|
-
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${heartbeatWillRun ? 'bg-emerald-400' : 'bg-text-3/30'}`} />
|
|
787
|
-
HB
|
|
788
|
-
{heartbeatEnabled && !loopIsOngoing && !heartbeatExplicitOptIn && (
|
|
789
|
-
<span className="text-[9px] text-text-3/40">(bounded)</span>
|
|
790
|
-
)}
|
|
791
|
-
</button>
|
|
792
|
-
<div className="relative" ref={hbDropdownRef}>
|
|
795
|
+
<div className={`flex items-center gap-2 shrink-0 ${mobile ? 'w-full justify-between pt-1' : 'ml-auto'}`}>
|
|
796
|
+
{/* Heartbeat compound control */}
|
|
797
|
+
{heartbeatSupported && (
|
|
798
|
+
<div className="flex items-center rounded-[12px] border border-white/[0.06] bg-white/[0.03] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] shrink-0">
|
|
799
|
+
<Tip label={heartbeatWillRun ? 'Disable heartbeat — periodic check-ins' : 'Enable heartbeat — periodic check-ins'}>
|
|
793
800
|
<button
|
|
794
|
-
onClick={
|
|
801
|
+
onClick={handleToggleHeartbeat}
|
|
795
802
|
disabled={heartbeatSaving}
|
|
796
|
-
|
|
797
|
-
|
|
803
|
+
aria-pressed={heartbeatWillRun}
|
|
804
|
+
className={`flex items-center gap-1.5 pl-2.5 pr-2 py-1.5 rounded-l-[11px] transition-colors cursor-pointer border-none text-[11px] font-600
|
|
805
|
+
${heartbeatWillRun ? 'text-emerald-400 hover:bg-emerald-500/10' : 'text-text-3/70 hover:bg-white/[0.04]'}`}
|
|
798
806
|
>
|
|
799
|
-
<span className=
|
|
800
|
-
<
|
|
801
|
-
|
|
802
|
-
|
|
807
|
+
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${heartbeatWillRun ? 'bg-emerald-400' : heartbeatEnabled ? 'bg-amber-300' : 'bg-text-3/30'}`} />
|
|
808
|
+
<span className="hidden sm:inline">Heartbeat</span>
|
|
809
|
+
<span className="sm:hidden">HB</span>
|
|
810
|
+
<span className={`hidden md:inline text-[9px] uppercase tracking-wider ${
|
|
811
|
+
heartbeatWillRun ? 'text-emerald-300/80' : heartbeatEnabled ? 'text-amber-300/70' : 'text-text-3/40'
|
|
812
|
+
}`}>
|
|
813
|
+
{heartbeatWillRun ? 'On' : heartbeatEnabled ? 'Bounded' : 'Off'}
|
|
814
|
+
</span>
|
|
803
815
|
</button>
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
</
|
|
817
|
-
|
|
816
|
+
</Tip>
|
|
817
|
+
<div className="relative" ref={hbDropdownRef}>
|
|
818
|
+
<Tip label="Set heartbeat interval">
|
|
819
|
+
<button
|
|
820
|
+
onClick={() => setHbDropdownOpen((o) => !o)}
|
|
821
|
+
disabled={heartbeatSaving}
|
|
822
|
+
className="flex items-center gap-0.5 pl-1 pr-2.5 py-1.5 text-text-3/60 hover:text-text-2 hover:bg-white/[0.04] transition-colors cursor-pointer border-none rounded-r-[11px]"
|
|
823
|
+
>
|
|
824
|
+
<span className="text-[11px] font-600">{formatDuration(heartbeatIntervalSec)}</span>
|
|
825
|
+
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="opacity-40">
|
|
826
|
+
<polyline points="6 9 12 15 18 9" />
|
|
827
|
+
</svg>
|
|
828
|
+
</button>
|
|
829
|
+
</Tip>
|
|
830
|
+
{hbDropdownOpen && (
|
|
831
|
+
<div className="absolute top-full right-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[88px]">
|
|
832
|
+
{[...(typeof window !== 'undefined' && window.location.hostname === 'localhost' ? [10, 15, 30, 60] : []), 1800, 3600, 7200, 21600, 43200].map((sec) => (
|
|
833
|
+
<button
|
|
834
|
+
key={sec}
|
|
835
|
+
onClick={() => handleSelectHeartbeatInterval(sec)}
|
|
836
|
+
className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none
|
|
837
|
+
${sec === heartbeatIntervalSec ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:bg-white/[0.06]'}`}
|
|
838
|
+
>
|
|
839
|
+
{formatDuration(sec)}
|
|
840
|
+
</button>
|
|
841
|
+
))}
|
|
842
|
+
</div>
|
|
843
|
+
)}
|
|
844
|
+
</div>
|
|
818
845
|
</div>
|
|
819
|
-
|
|
820
|
-
)}
|
|
846
|
+
)}
|
|
821
847
|
|
|
822
|
-
|
|
823
|
-
|
|
848
|
+
{/* Action buttons */}
|
|
849
|
+
<div className="flex items-center shrink-0 rounded-[12px] border border-white/[0.06] bg-white/[0.03] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] p-1">
|
|
824
850
|
{streaming && (
|
|
825
851
|
<>
|
|
826
852
|
<IconButton onClick={onStop} variant="danger" tooltip="Stop" aria-label="Stop generation" size="sm">
|
|
@@ -881,79 +907,29 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
881
907
|
</IconButton>
|
|
882
908
|
)}
|
|
883
909
|
</div>
|
|
910
|
+
</div>
|
|
884
911
|
</div>
|
|
885
912
|
|
|
886
|
-
{/* Context bar: tools
|
|
913
|
+
{/* Context bar: tools and links */}
|
|
887
914
|
{hasContextBar && (
|
|
888
|
-
<div className="
|
|
889
|
-
|
|
890
|
-
<>
|
|
891
|
-
<button
|
|
892
|
-
onClick={handleToggleMissionPause}
|
|
893
|
-
disabled={mainLoopSaving}
|
|
894
|
-
className={`flex items-center gap-1.5 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
|
|
895
|
-
${missionPaused ? 'bg-amber-500/10 hover:bg-amber-500/18 text-amber-300' : 'bg-emerald-500/8 hover:bg-emerald-500/12 text-emerald-400'}`}
|
|
896
|
-
title={missionPaused ? 'Resume mission' : 'Pause mission'}
|
|
897
|
-
>
|
|
898
|
-
<span className={`w-1.5 h-1.5 rounded-full ${missionPaused ? 'bg-amber-300' : 'bg-emerald-400'}`} />
|
|
899
|
-
{missionPaused ? 'Paused' : 'Live'}
|
|
900
|
-
</button>
|
|
901
|
-
|
|
902
|
-
<button
|
|
903
|
-
onClick={handleToggleMissionMode}
|
|
904
|
-
disabled={mainLoopSaving}
|
|
905
|
-
className={`flex items-center gap-1 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
|
|
906
|
-
${missionMode === 'autonomous' ? 'bg-indigo-500/12 hover:bg-indigo-500/20 text-indigo-300' : 'bg-white/[0.03] hover:bg-white/[0.06] text-text-3/60'}`}
|
|
907
|
-
title="Toggle autonomy mode"
|
|
908
|
-
>
|
|
909
|
-
{missionMode === 'autonomous' ? 'Auto' : 'Assist'}
|
|
910
|
-
</button>
|
|
911
|
-
<button
|
|
912
|
-
onClick={handleNudgeMission}
|
|
913
|
-
disabled={mainLoopSaving || missionPaused}
|
|
914
|
-
className="px-2 py-1 rounded-[7px] bg-blue-500/8 hover:bg-blue-500/15 text-blue-400 transition-colors cursor-pointer border-none disabled:opacity-50 text-[10px] font-600"
|
|
915
|
-
title="Run one tick"
|
|
916
|
-
>
|
|
917
|
-
Nudge
|
|
918
|
-
</button>
|
|
919
|
-
<button
|
|
920
|
-
onClick={handleSetMissionGoal}
|
|
921
|
-
disabled={mainLoopSaving}
|
|
922
|
-
className="px-2 py-1 rounded-[7px] bg-fuchsia-500/8 hover:bg-fuchsia-500/15 text-fuchsia-300 transition-colors cursor-pointer border-none text-[10px] font-600"
|
|
923
|
-
title="Set mission goal"
|
|
924
|
-
>
|
|
925
|
-
Goal
|
|
926
|
-
</button>
|
|
927
|
-
{missionEventsCount > 0 && (
|
|
928
|
-
<button
|
|
929
|
-
onClick={handleClearMissionEvents}
|
|
930
|
-
disabled={mainLoopSaving}
|
|
931
|
-
className="px-2 py-1 rounded-[7px] bg-white/[0.03] hover:bg-white/[0.06] text-text-3/60 transition-colors cursor-pointer border-none text-[10px] font-600"
|
|
932
|
-
title="Clear pending events"
|
|
933
|
-
>
|
|
934
|
-
Events {missionEventsCount}
|
|
935
|
-
</button>
|
|
936
|
-
)}
|
|
937
|
-
<span className="text-[9px] text-text-3/40 uppercase tracking-wider shrink-0">
|
|
938
|
-
{missionStatus}{missionMomentum !== null ? ` · ${missionMomentum}` : ''}
|
|
939
|
-
</span>
|
|
940
|
-
{mainLoopError && <span className="text-[9px] text-red-300/80 truncate max-w-[240px]" title={mainLoopError}>{mainLoopError}</span>}
|
|
941
|
-
{mainLoopNotice && <span className="text-[9px] text-emerald-300/80 truncate max-w-[200px]" title={mainLoopNotice}>{mainLoopNotice}</span>}
|
|
942
|
-
</>
|
|
943
|
-
)}
|
|
915
|
+
<div className="border-t border-white/[0.05] bg-black/[0.08] px-4 py-2">
|
|
916
|
+
<div className="flex items-center gap-1.5 flex-wrap">
|
|
944
917
|
{hasMemoryLink && (
|
|
918
|
+
<Tip label="View agent memories">
|
|
945
919
|
<button
|
|
946
920
|
onClick={() => { setMemoryAgentFilter(session.agentId!); setActiveView('memory'); setSidebarOpen(true) }}
|
|
947
|
-
className="flex items-center gap-1 px-2 py-1 rounded-[
|
|
921
|
+
className="flex items-center gap-1 px-2.5 py-1 rounded-[8px] bg-accent-soft/40 hover:bg-accent-soft/70 transition-colors cursor-pointer text-[10px] font-600 text-accent-bright/55 hover:text-accent-bright/80 shrink-0"
|
|
948
922
|
>
|
|
949
923
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
950
924
|
<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" />
|
|
951
925
|
</svg>
|
|
952
926
|
Memories
|
|
953
927
|
</button>
|
|
928
|
+
</Tip>
|
|
954
929
|
)}
|
|
955
930
|
{hasSourceFilter && onConnectorFilterChange && connectorSources && (
|
|
956
931
|
<div className="relative shrink-0" ref={sourceDropdownRef}>
|
|
932
|
+
<Tip label="Filter messages by source connector">
|
|
957
933
|
<button
|
|
958
934
|
onClick={() => setSourceDropdownOpen((o) => !o)}
|
|
959
935
|
className={`flex items-center gap-1 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600 shrink-0 ${
|
|
@@ -961,7 +937,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
961
937
|
? 'bg-accent-soft/60 text-accent-bright/80 hover:bg-accent-soft'
|
|
962
938
|
: 'bg-white/[0.03] text-text-3/50 hover:bg-white/[0.06] hover:text-text-3/70'
|
|
963
939
|
}`}
|
|
964
|
-
title="Filter by message source"
|
|
965
940
|
>
|
|
966
941
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
967
942
|
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
|
@@ -973,8 +948,9 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
973
948
|
<path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
974
949
|
</svg>
|
|
975
950
|
</button>
|
|
951
|
+
</Tip>
|
|
976
952
|
{sourceDropdownOpen && (
|
|
977
|
-
<div className="absolute top-full left-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[
|
|
953
|
+
<div className="absolute top-full right-0 sm:left-0 sm:right-auto mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[160px] max-w-[calc(100vw-2rem)]">
|
|
978
954
|
<button
|
|
979
955
|
onClick={() => { onConnectorFilterChange(null); setSourceDropdownOpen(false) }}
|
|
980
956
|
className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none flex items-center gap-2 ${
|
|
@@ -1005,11 +981,11 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1005
981
|
)}
|
|
1006
982
|
{isOpenClawAgent && openclawSessionKey && (
|
|
1007
983
|
<>
|
|
984
|
+
<Tip label="Sync chat history from OpenClaw gateway">
|
|
1008
985
|
<button
|
|
1009
986
|
onClick={handleSyncHistory}
|
|
1010
987
|
disabled={syncingHistory}
|
|
1011
988
|
className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-indigo-500/8 hover:bg-indigo-500/12 transition-colors cursor-pointer border-none disabled:opacity-50 text-[10px] font-600 text-indigo-400 shrink-0"
|
|
1012
|
-
title="Sync from gateway"
|
|
1013
989
|
>
|
|
1014
990
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
1015
991
|
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" />
|
|
@@ -1017,10 +993,12 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1017
993
|
</svg>
|
|
1018
994
|
{syncingHistory ? 'Syncing...' : 'Sync'}
|
|
1019
995
|
</button>
|
|
996
|
+
</Tip>
|
|
1020
997
|
{syncResult && <span className="text-[9px] text-emerald-300/80 shrink-0">{syncResult}</span>}
|
|
1021
998
|
</>
|
|
1022
999
|
)}
|
|
1023
1000
|
{linkedTask && (
|
|
1001
|
+
<Tip label="View linked task">
|
|
1024
1002
|
<button
|
|
1025
1003
|
onClick={() => setActiveView('tasks')}
|
|
1026
1004
|
className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-amber-500/8 hover:bg-amber-500/12 transition-colors cursor-pointer text-[10px] font-600 text-amber-500 shrink-0"
|
|
@@ -1030,37 +1008,40 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1030
1008
|
</svg>
|
|
1031
1009
|
<span className="truncate max-w-[160px]">{linkedTask.title}</span>
|
|
1032
1010
|
</button>
|
|
1011
|
+
</Tip>
|
|
1033
1012
|
)}
|
|
1034
1013
|
{resumeHandle && (
|
|
1035
1014
|
<div className="flex items-center rounded-[7px] bg-white/[0.03] group/resume shrink-0">
|
|
1015
|
+
<Tip label="Copy CLI resume command">
|
|
1036
1016
|
<button
|
|
1037
1017
|
onClick={handleCopySessionId}
|
|
1038
|
-
className="flex items-center gap-1 px-2 py-1 rounded-l-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer"
|
|
1039
|
-
title="Copy resume command"
|
|
1018
|
+
className="flex min-w-0 items-center gap-1 px-2 py-1 rounded-l-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer"
|
|
1040
1019
|
>
|
|
1041
1020
|
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/40 shrink-0">
|
|
1042
1021
|
<path d="M4 17l6 0l0 -6" /><path d="M20 7l-6 0l0 6" /><path d="M4 17l10 -10" />
|
|
1043
1022
|
</svg>
|
|
1044
|
-
<span className="text-[10px] font-mono text-text-3/40 group-hover/resume:text-text-3/60 truncate max-w-[
|
|
1023
|
+
<span className="text-[10px] font-mono text-text-3/40 group-hover/resume:text-text-3/60 truncate max-w-[min(46vw,220px)]">
|
|
1045
1024
|
{copied ? 'Copied!' : `${resumeHandle.label}: ${resumeHandle.id}`}
|
|
1046
1025
|
</span>
|
|
1047
1026
|
</button>
|
|
1027
|
+
</Tip>
|
|
1028
|
+
<Tip label="Dismiss resume handle">
|
|
1048
1029
|
<button
|
|
1049
1030
|
onClick={handleDismissResumeHandle}
|
|
1050
|
-
className="px-1 py-1 rounded-r-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer opacity-0 group-hover/resume:opacity-100"
|
|
1051
|
-
title="Dismiss"
|
|
1031
|
+
className="px-1 py-1 rounded-r-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer opacity-60 md:opacity-0 md:group-hover/resume:opacity-100 group-focus-within/resume:opacity-100"
|
|
1052
1032
|
>
|
|
1053
1033
|
<svg width="8" height="8" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40 hover:text-text-3">
|
|
1054
1034
|
<path d="M4 4l8 8M12 4l-8 8" />
|
|
1055
1035
|
</svg>
|
|
1056
1036
|
</button>
|
|
1037
|
+
</Tip>
|
|
1057
1038
|
</div>
|
|
1058
1039
|
)}
|
|
1059
1040
|
{browserActive && (
|
|
1041
|
+
<Tip label="Close the browser session">
|
|
1060
1042
|
<button
|
|
1061
1043
|
onClick={onStopBrowser}
|
|
1062
1044
|
className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-accent-bright/8 hover:bg-red-500/12 transition-colors cursor-pointer group text-[10px] font-600 shrink-0"
|
|
1063
|
-
title="Stop browser"
|
|
1064
1045
|
>
|
|
1065
1046
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-accent-bright group-hover:text-red-400">
|
|
1066
1047
|
<rect x="3" y="3" width="18" height="14" rx="2" /><path d="M3 9h18" />
|
|
@@ -1070,9 +1051,13 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1070
1051
|
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
1071
1052
|
</svg>
|
|
1072
1053
|
</button>
|
|
1054
|
+
</Tip>
|
|
1073
1055
|
)}
|
|
1074
1056
|
</div>
|
|
1057
|
+
</div>
|
|
1075
1058
|
)}
|
|
1059
|
+
|
|
1076
1060
|
</header>
|
|
1061
|
+
</>
|
|
1077
1062
|
)
|
|
1078
1063
|
}
|