@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- 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/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- 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/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- 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 +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- 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 +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- 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 +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- 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/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -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 +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- 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 +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- 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 +994 -130
- 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 +189 -10
- 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/daemon-state.ts +62 -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/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- 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 +31 -964
- 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 +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- 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 +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- 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 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- 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 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- 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/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
|
|
3
4
|
import { useEffect, useState, useMemo, useRef, useCallback, type ReactNode } from 'react'
|
|
4
5
|
import type { Session } from '@/types'
|
|
5
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
@@ -18,6 +19,7 @@ import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip
|
|
|
18
19
|
import { toast } from 'sonner'
|
|
19
20
|
import type { ProviderType } from '@/types'
|
|
20
21
|
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
22
|
+
import { buildOpenClawMainSessionKey } from '@/lib/openclaw-agent-id'
|
|
21
23
|
import { useWs } from '@/hooks/use-ws'
|
|
22
24
|
|
|
23
25
|
function Tip({ label, children, side = 'bottom' }: { label: string; children: ReactNode; side?: 'top' | 'bottom' | 'left' | 'right' }) {
|
|
@@ -32,6 +34,40 @@ function Tip({ label, children, side = 'bottom' }: { label: string; children: Re
|
|
|
32
34
|
)
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
function HeaderChip({
|
|
38
|
+
children,
|
|
39
|
+
title,
|
|
40
|
+
onClick,
|
|
41
|
+
className = '',
|
|
42
|
+
active = false,
|
|
43
|
+
}: {
|
|
44
|
+
children: ReactNode
|
|
45
|
+
title?: string
|
|
46
|
+
onClick?: () => void
|
|
47
|
+
className?: string
|
|
48
|
+
active?: boolean
|
|
49
|
+
}) {
|
|
50
|
+
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 ${
|
|
51
|
+
active
|
|
52
|
+
? 'border-accent-bright/20 bg-accent-soft/50 text-accent-bright'
|
|
53
|
+
: 'border-white/[0.06] bg-white/[0.03] text-text-3/68'
|
|
54
|
+
} ${onClick ? 'cursor-pointer hover:border-white/[0.1] hover:bg-white/[0.06] hover:text-text-2' : ''} ${className}`
|
|
55
|
+
|
|
56
|
+
if (onClick) {
|
|
57
|
+
return (
|
|
58
|
+
<button type="button" onClick={onClick} title={title} className={baseClass}>
|
|
59
|
+
{children}
|
|
60
|
+
</button>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<span title={title} className={baseClass}>
|
|
66
|
+
{children}
|
|
67
|
+
</span>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
35
71
|
function shortPath(p: string): string {
|
|
36
72
|
return (p || '').replace(/^\/Users\/\w+/, '~')
|
|
37
73
|
}
|
|
@@ -78,8 +114,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
78
114
|
const toggleTts = useChatStore((s) => s.toggleTts)
|
|
79
115
|
const soundEnabled = useChatStore((s) => s.soundEnabled)
|
|
80
116
|
const toggleSound = useChatStore((s) => s.toggleSound)
|
|
81
|
-
const debugOpen = useChatStore((s) => s.debugOpen)
|
|
82
|
-
const setDebugOpen = useChatStore((s) => s.setDebugOpen)
|
|
83
117
|
const agentStatus = useChatStore((s) => s.agentStatus)
|
|
84
118
|
const agents = useAppStore((s) => s.agents)
|
|
85
119
|
const tasks = useAppStore((s) => s.tasks)
|
|
@@ -100,6 +134,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
100
134
|
const providers = useAppStore((s) => s.providers)
|
|
101
135
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
102
136
|
const modelName = session.model || agent?.model || ''
|
|
137
|
+
const providerLabel = PROVIDER_LABELS[session.provider] || session.provider
|
|
103
138
|
const [modelSwitcherOpen, setModelSwitcherOpen] = useState(false)
|
|
104
139
|
const modelSwitcherRef = useRef<HTMLDivElement>(null)
|
|
105
140
|
const [copied, setCopied] = useState(false)
|
|
@@ -108,9 +143,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
108
143
|
const hbDropdownRef = useRef<HTMLDivElement>(null)
|
|
109
144
|
const [sourceDropdownOpen, setSourceDropdownOpen] = useState(false)
|
|
110
145
|
const sourceDropdownRef = useRef<HTMLDivElement>(null)
|
|
111
|
-
const [mainLoopSaving, setMainLoopSaving] = useState(false)
|
|
112
|
-
const [mainLoopError, setMainLoopError] = useState('')
|
|
113
|
-
const [mainLoopNotice, setMainLoopNotice] = useState('')
|
|
114
146
|
const [syncingHistory, setSyncingHistory] = useState(false)
|
|
115
147
|
const [syncResult, setSyncResult] = useState('')
|
|
116
148
|
const [renaming, setRenaming] = useState(false)
|
|
@@ -122,9 +154,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
122
154
|
const setWalletPanelAgentId = useAppStore((s) => s.setWalletPanelAgentId)
|
|
123
155
|
const [walletBalance, setWalletBalance] = useState<number | null>(null)
|
|
124
156
|
const [headerWidgets, setHeaderWidgets] = useState<Array<{ id: string; label: string; icon?: string }>>([])
|
|
125
|
-
const [goalModalOpen, setGoalModalOpen] = useState(false)
|
|
126
|
-
const [goalDraft, setGoalDraft] = useState('')
|
|
127
|
-
const goalInputRef = useRef<HTMLTextAreaElement>(null)
|
|
128
157
|
|
|
129
158
|
useEffect(() => {
|
|
130
159
|
api<Array<{ id: string; label: string; icon?: string }>>('GET', `/plugins/ui?type=header&sessionId=${session.id}`).then(widgets => {
|
|
@@ -150,6 +179,46 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
150
179
|
}, [fetchWalletBalance])
|
|
151
180
|
useWs('wallets', fetchWalletBalance)
|
|
152
181
|
|
|
182
|
+
const workspaceLabel = useMemo(() => shortPath(session.cwd), [session.cwd])
|
|
183
|
+
const liveStatus = agentStatus || null
|
|
184
|
+
const threadContextLabel = useMemo(() => {
|
|
185
|
+
const title = session.connectorContext?.threadTitle?.trim()
|
|
186
|
+
if (title) return title
|
|
187
|
+
const persona = session.connectorContext?.threadPersonaLabel?.trim()
|
|
188
|
+
if (persona) return persona
|
|
189
|
+
return null
|
|
190
|
+
}, [session.connectorContext?.threadPersonaLabel, session.connectorContext?.threadTitle])
|
|
191
|
+
const connectorPresenceMeta = useMemo(() => {
|
|
192
|
+
if (!connector) return null
|
|
193
|
+
const lastAt = connectorPresence?.lastMessageAt
|
|
194
|
+
if (!lastAt) {
|
|
195
|
+
return {
|
|
196
|
+
label: 'Idle',
|
|
197
|
+
dotClass: 'bg-text-3/30',
|
|
198
|
+
textClass: 'text-text-3/45',
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const ago = Date.now() - lastAt
|
|
202
|
+
if (ago < 5 * 60_000) {
|
|
203
|
+
return {
|
|
204
|
+
label: 'Active',
|
|
205
|
+
dotClass: 'bg-emerald-400',
|
|
206
|
+
textClass: 'text-emerald-400',
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (ago < 30 * 60_000) {
|
|
210
|
+
return {
|
|
211
|
+
label: `${Math.floor(ago / 60_000)}m ago`,
|
|
212
|
+
dotClass: 'bg-amber-400',
|
|
213
|
+
textClass: 'text-amber-300',
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
label: 'Idle',
|
|
218
|
+
dotClass: 'bg-text-3/30',
|
|
219
|
+
textClass: 'text-text-3/45',
|
|
220
|
+
}
|
|
221
|
+
}, [connector, connectorPresence?.lastMessageAt])
|
|
153
222
|
|
|
154
223
|
const visibleHeaderWidgets = useMemo(() => {
|
|
155
224
|
const seen = new Set<string>()
|
|
@@ -161,6 +230,25 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
161
230
|
})
|
|
162
231
|
}, [headerWidgets])
|
|
163
232
|
|
|
233
|
+
const walletHeaderMeta = useMemo(() => {
|
|
234
|
+
if (!agent?.id) {
|
|
235
|
+
return {
|
|
236
|
+
label: 'Wallets',
|
|
237
|
+
title: 'Open wallets',
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!agent.walletId) {
|
|
241
|
+
return {
|
|
242
|
+
label: 'Create wallet',
|
|
243
|
+
title: 'Create wallet',
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
label: walletBalance !== null ? `${walletBalance.toFixed(3)} SOL` : 'Wallet',
|
|
248
|
+
title: 'View wallet',
|
|
249
|
+
}
|
|
250
|
+
}, [agent?.id, agent?.walletId, walletBalance])
|
|
251
|
+
|
|
164
252
|
const handleHeaderWidgetClick = (widgetId: string) => {
|
|
165
253
|
if (widgetId === 'wallet-status') {
|
|
166
254
|
if (agent?.id) setWalletPanelAgentId(agent.id)
|
|
@@ -248,7 +336,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
248
336
|
return null
|
|
249
337
|
}
|
|
250
338
|
// Global defaults
|
|
251
|
-
let sec = resolveFrom(appSettings) ??
|
|
339
|
+
let sec = resolveFrom(appSettings) ?? DEFAULT_HEARTBEAT_INTERVAL_SEC
|
|
252
340
|
let enabled = sec > 0
|
|
253
341
|
let explicitOptIn = false
|
|
254
342
|
// Agent layer
|
|
@@ -270,13 +358,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
270
358
|
}
|
|
271
359
|
}, [appSettings, agent, session])
|
|
272
360
|
const heartbeatWillRun = heartbeatEnabled && (loopIsOngoing || heartbeatExplicitOptIn)
|
|
273
|
-
const hasMainLoop = session.id.startsWith('agent-thread-') || session.sessionType === 'orchestrated'
|
|
274
|
-
const missionState = session.mainLoopState || {}
|
|
275
|
-
const missionPaused = missionState.paused === true
|
|
276
|
-
const missionMode = missionState.autonomyMode === 'assist' ? 'assist' : 'autonomous'
|
|
277
|
-
const missionStatus = missionState.status || 'idle'
|
|
278
|
-
const missionMomentum = typeof missionState.momentumScore === 'number' ? missionState.momentumScore : null
|
|
279
|
-
const missionEventsCount = missionState.pendingEvents?.length || 0
|
|
280
361
|
|
|
281
362
|
const handleToggleHeartbeat = async () => {
|
|
282
363
|
if (!heartbeatSupported || heartbeatSaving) return
|
|
@@ -322,68 +403,8 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
322
403
|
}
|
|
323
404
|
}
|
|
324
405
|
|
|
325
|
-
const postMainLoopAction = async (action: string, extra?: Record<string, unknown>) => {
|
|
326
|
-
if (!hasMainLoop || mainLoopSaving) return
|
|
327
|
-
setMainLoopSaving(true)
|
|
328
|
-
try {
|
|
329
|
-
const result = await api<{ runId?: string; deduped?: boolean }>('POST', `/chats/${session.id}/main-loop`, {
|
|
330
|
-
action,
|
|
331
|
-
...(extra || {}),
|
|
332
|
-
})
|
|
333
|
-
setMainLoopError('')
|
|
334
|
-
if (action === 'nudge') {
|
|
335
|
-
setMainLoopNotice(result?.deduped ? 'Nudge already queued.' : 'Nudge queued.')
|
|
336
|
-
} else if (action === 'set_mode') {
|
|
337
|
-
setMainLoopNotice(`Mode set to ${extra?.mode === 'assist' ? 'Assist' : 'Auto'}.`)
|
|
338
|
-
} else {
|
|
339
|
-
setMainLoopNotice('')
|
|
340
|
-
}
|
|
341
|
-
await loadSessions()
|
|
342
|
-
} catch (err: unknown) {
|
|
343
|
-
const message = err instanceof Error ? err.message : 'Failed to update mission controls.'
|
|
344
|
-
setMainLoopError(message)
|
|
345
|
-
} finally {
|
|
346
|
-
setMainLoopSaving(false)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const handleToggleMissionPause = () => {
|
|
351
|
-
void postMainLoopAction(missionPaused ? 'resume' : 'pause')
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const handleToggleMissionMode = () => {
|
|
355
|
-
const nextMode = missionMode === 'autonomous' ? 'assist' : 'autonomous'
|
|
356
|
-
void postMainLoopAction('set_mode', { mode: nextMode })
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const handleNudgeMission = () => {
|
|
360
|
-
void postMainLoopAction('nudge')
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const handleOpenGoalModal = () => {
|
|
364
|
-
if (!hasMainLoop) return
|
|
365
|
-
setGoalDraft(typeof missionState.goal === 'string' ? missionState.goal : '')
|
|
366
|
-
setGoalModalOpen(true)
|
|
367
|
-
requestAnimationFrame(() => goalInputRef.current?.focus())
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const handleSubmitGoal = () => {
|
|
371
|
-
const goal = goalDraft.trim()
|
|
372
|
-
setGoalModalOpen(false)
|
|
373
|
-
if (!goal) return
|
|
374
|
-
void postMainLoopAction('set_goal', { goal })
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const handleClearMissionEvents = () => {
|
|
378
|
-
if (!hasMainLoop || missionEventsCount <= 0) return
|
|
379
|
-
void postMainLoopAction('clear_events')
|
|
380
|
-
}
|
|
381
|
-
|
|
382
406
|
const isOpenClawAgent = agent?.provider === 'openclaw'
|
|
383
|
-
|
|
384
|
-
const openclawSessionKey = isOpenClawAgent && agent
|
|
385
|
-
? `agent:${agent.name.toLowerCase().replace(/\s+/g, '-')}:main`
|
|
386
|
-
: null
|
|
407
|
+
const openclawSessionKey = isOpenClawAgent ? buildOpenClawMainSessionKey(agent?.name) : null
|
|
387
408
|
|
|
388
409
|
const handleSyncHistory = async () => {
|
|
389
410
|
if (!openclawSessionKey || syncingHistory) return
|
|
@@ -511,36 +532,28 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
511
532
|
}, [session.name, loadConnectors])
|
|
512
533
|
|
|
513
534
|
useEffect(() => {
|
|
514
|
-
setMainLoopError('')
|
|
515
|
-
setMainLoopNotice('')
|
|
516
535
|
setModelSwitcherOpen(false)
|
|
517
536
|
}, [session.id])
|
|
518
537
|
|
|
519
|
-
|
|
520
|
-
if (!mainLoopNotice) return
|
|
521
|
-
const timer = setTimeout(() => setMainLoopNotice(''), 2500)
|
|
522
|
-
return () => clearTimeout(timer)
|
|
523
|
-
}, [mainLoopNotice])
|
|
524
|
-
|
|
525
|
-
// Context bar shows for tools, mission controls, memories, source filter, task links, resume handles, browser
|
|
538
|
+
// Context bar shows for tools, memories, source filter, task links, resume handles, browser
|
|
526
539
|
const hasToolToggles = ((agent?.plugins?.length ?? 0) > 0) || ((session.plugins?.length ?? 0) > 0)
|
|
527
540
|
const hasMemoryLink = !!(agent && session.plugins?.includes('memory'))
|
|
528
541
|
const hasSourceFilter = !!hasMultipleSources
|
|
529
|
-
const hasContextBar = !!(
|
|
542
|
+
const hasContextBar = !!(hasMemoryLink || hasSourceFilter || linkedTask || resumeHandle || (isOpenClawAgent && openclawSessionKey) || browserActive)
|
|
530
543
|
|
|
531
544
|
return (
|
|
532
545
|
<>
|
|
533
546
|
<header
|
|
534
547
|
className="relative z-20 border-b border-white/[0.06] shrink-0"
|
|
535
548
|
style={{
|
|
536
|
-
background: 'linear-gradient(180deg, rgba(var(--rgb-bg, 15,15,26), 0.
|
|
549
|
+
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%)',
|
|
537
550
|
backdropFilter: 'blur(20px) saturate(1.4)',
|
|
538
551
|
WebkitBackdropFilter: 'blur(20px) saturate(1.4)',
|
|
539
552
|
...(mobile ? { paddingTop: 'max(12px, env(safe-area-inset-top))' } : {}),
|
|
540
553
|
}}
|
|
541
554
|
>
|
|
542
555
|
{/* Main row */}
|
|
543
|
-
<div className="flex items-
|
|
556
|
+
<div className="flex flex-wrap items-start gap-3 px-4 py-2.5 min-h-[64px]">
|
|
544
557
|
{/* Back button */}
|
|
545
558
|
{onBack && (
|
|
546
559
|
<IconButton onClick={onBack} aria-label="Go back" size="sm">
|
|
@@ -581,10 +594,8 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
581
594
|
)}
|
|
582
595
|
|
|
583
596
|
{/* Identity + metadata — fills center */}
|
|
584
|
-
<div className="
|
|
585
|
-
|
|
586
|
-
<div className="flex flex-col gap-0.5 min-w-0 shrink">
|
|
587
|
-
<div className="flex items-center gap-2 min-w-0">
|
|
597
|
+
<div className="min-w-0 flex-1">
|
|
598
|
+
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
588
599
|
{renaming && agent ? (
|
|
589
600
|
<span ref={renameContainerRef} className="inline-flex items-center gap-2">
|
|
590
601
|
<input
|
|
@@ -602,99 +613,90 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
602
613
|
{renameSaving && <span className="w-3 h-3 rounded-full border-2 border-text-3/30 border-t-accent-bright animate-spin shrink-0" />}
|
|
603
614
|
{renameError && <span className="text-[10px] text-red-400 shrink-0">{renameError}</span>}
|
|
604
615
|
</span>
|
|
616
|
+
) : agent ? (
|
|
617
|
+
<button
|
|
618
|
+
type="button"
|
|
619
|
+
onClick={startRename}
|
|
620
|
+
title="Rename agent"
|
|
621
|
+
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"
|
|
622
|
+
>
|
|
623
|
+
<span className="font-display text-[16px] font-700 truncate tracking-[-0.02em] text-text transition-colors group-hover/title:text-accent-bright">
|
|
624
|
+
{(session.shortcutForAgentId && agent.id === session.shortcutForAgentId) || agent.threadSessionId === session.id
|
|
625
|
+
? agent.name
|
|
626
|
+
: session.name}
|
|
627
|
+
</span>
|
|
628
|
+
<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">
|
|
629
|
+
<path d="M12 20h9" />
|
|
630
|
+
<path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" />
|
|
631
|
+
</svg>
|
|
632
|
+
</button>
|
|
605
633
|
) : (
|
|
606
|
-
<span
|
|
607
|
-
className={`font-display text-[15px] font-700 truncate tracking-[-0.02em] text-text${agent ? ' cursor-pointer hover:text-accent-bright transition-colors duration-200' : ''}`}
|
|
608
|
-
onClick={agent ? startRename : undefined}
|
|
609
|
-
title={agent ? 'Click to rename' : undefined}
|
|
610
|
-
>{
|
|
611
|
-
session.name.startsWith('agent-thread:') ? (agent?.name || session.name)
|
|
612
|
-
: session.name
|
|
613
|
-
}</span>
|
|
634
|
+
<span className="font-display text-[16px] font-700 truncate tracking-[-0.02em] text-text">{session.name}</span>
|
|
614
635
|
)}
|
|
615
636
|
{connector && connectorMeta && (
|
|
616
637
|
<span
|
|
617
|
-
className="inline-flex items-center gap-1 px-
|
|
638
|
+
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"
|
|
618
639
|
style={{
|
|
619
640
|
color: connectorMeta.color,
|
|
620
|
-
backgroundColor: `${connectorMeta.color}
|
|
621
|
-
borderColor: `${connectorMeta.color}
|
|
641
|
+
backgroundColor: `${connectorMeta.color}12`,
|
|
642
|
+
borderColor: `${connectorMeta.color}22`,
|
|
622
643
|
}}
|
|
623
644
|
title={`${connector.name} connector`}
|
|
624
645
|
>
|
|
625
646
|
<ConnectorPlatformIcon platform={connector.platform} size={10} />
|
|
626
|
-
{connectorMeta.label}
|
|
647
|
+
<span className="truncate max-w-[140px]">{connectorMeta.label}</span>
|
|
627
648
|
</span>
|
|
628
649
|
)}
|
|
629
|
-
{
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const ago = Date.now() - lastAt
|
|
638
|
-
const isActive = ago < 5 * 60_000
|
|
639
|
-
const isRecent = ago < 30 * 60_000
|
|
640
|
-
const label = isActive ? 'Active' : isRecent ? `${Math.floor(ago / 60_000)}m ago` : 'Idle'
|
|
641
|
-
const dotColor = isActive ? 'bg-emerald-400' : isRecent ? 'bg-amber-400' : 'bg-text-3/30'
|
|
642
|
-
const textColor = isActive ? 'text-emerald-400' : isRecent ? 'text-amber-300' : 'text-text-3/40'
|
|
643
|
-
return (
|
|
644
|
-
<span className={`shrink-0 inline-flex items-center gap-1 text-[10px] ${textColor}`}>
|
|
645
|
-
<span className={`w-1.5 h-1.5 rounded-full ${dotColor}`} />
|
|
646
|
-
{label}
|
|
647
|
-
</span>
|
|
648
|
-
)
|
|
649
|
-
})()}
|
|
650
|
-
{agent?.isOrchestrator && (
|
|
651
|
-
<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>
|
|
650
|
+
{connectorPresenceMeta && (
|
|
651
|
+
<HeaderChip className={`${connectorPresenceMeta.textClass} shrink-0`}>
|
|
652
|
+
<span className={`w-1.5 h-1.5 rounded-full ${connectorPresenceMeta.dotClass}`} />
|
|
653
|
+
{connectorPresenceMeta.label}
|
|
654
|
+
</HeaderChip>
|
|
655
|
+
)}
|
|
656
|
+
{agent?.platformAssignScope === 'all' && (
|
|
657
|
+
<HeaderChip className="bg-amber-500/10 border-amber-500/15 text-amber-400 shrink-0">Delegates</HeaderChip>
|
|
652
658
|
)}
|
|
653
659
|
{streaming && (
|
|
654
|
-
<
|
|
660
|
+
<HeaderChip className="bg-accent-soft/60 border-accent-bright/20 text-accent-bright shrink-0">
|
|
661
|
+
<span className="w-1.5 h-1.5 rounded-full bg-accent-bright" style={{ animation: 'pulse 1.5s ease infinite' }} />
|
|
662
|
+
Responding
|
|
663
|
+
</HeaderChip>
|
|
655
664
|
)}
|
|
656
665
|
</div>
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
{/* Metadata tray: wallet · model · path · status */}
|
|
661
|
-
<div className="flex items-center gap-1.5 min-w-0 overflow-hidden">
|
|
662
|
-
<span className="text-text-3/10 text-[10px] select-none shrink-0">/</span>
|
|
666
|
+
<div className="mt-1.5 flex min-w-0 flex-wrap items-center gap-1.5">
|
|
667
|
+
{hasToolToggles && <ChatToolToggles session={session} />}
|
|
663
668
|
{visibleHeaderWidgets.map((widget) => {
|
|
664
669
|
const actionable = widget.id === 'wallet-status'
|
|
665
|
-
const walletLabel =
|
|
666
|
-
?
|
|
670
|
+
const walletLabel = actionable
|
|
671
|
+
? walletHeaderMeta.label
|
|
667
672
|
: (widget.label || 'Wallet')
|
|
673
|
+
const widgetTitle = actionable
|
|
674
|
+
? walletHeaderMeta.title
|
|
675
|
+
: widget.label
|
|
668
676
|
return (
|
|
669
|
-
<
|
|
677
|
+
<HeaderChip
|
|
670
678
|
key={widget.id}
|
|
671
|
-
type="button"
|
|
672
679
|
onClick={actionable ? () => handleHeaderWidgetClick(widget.id) : undefined}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}`}
|
|
676
|
-
title={actionable ? 'View wallet' : widget.label}
|
|
680
|
+
title={widgetTitle}
|
|
681
|
+
className={actionable ? 'text-text-3/80' : ''}
|
|
677
682
|
>
|
|
678
683
|
{actionable ? (
|
|
679
684
|
<>
|
|
680
|
-
<svg width="
|
|
685
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
681
686
|
<rect x="2" y="6" width="20" height="14" rx="2" />
|
|
682
687
|
<path d="M22 10H18a2 2 0 0 0 0 4h4" />
|
|
683
688
|
</svg>
|
|
684
|
-
{walletLabel}
|
|
689
|
+
<span className="truncate max-w-[120px]">{walletLabel}</span>
|
|
685
690
|
</>
|
|
686
691
|
) : (
|
|
687
|
-
widget.label
|
|
692
|
+
<span className="truncate max-w-[120px]">{widget.label}</span>
|
|
688
693
|
)}
|
|
689
|
-
</
|
|
694
|
+
</HeaderChip>
|
|
690
695
|
)
|
|
691
696
|
})}
|
|
692
|
-
{visibleHeaderWidgets.length > 0 && (
|
|
693
|
-
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
694
|
-
)}
|
|
695
697
|
{modelName && (
|
|
696
698
|
<div className="relative shrink-0" ref={modelSwitcherRef}>
|
|
697
|
-
<Tip label=
|
|
699
|
+
<Tip label={`Switch model (${providerLabel})`}>
|
|
698
700
|
<button
|
|
699
701
|
type="button"
|
|
700
702
|
onClick={() => {
|
|
@@ -702,16 +704,19 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
702
704
|
setModelSwitcherOpen((o) => { if (!o) void loadProviders(); return !o })
|
|
703
705
|
}}
|
|
704
706
|
disabled={streaming}
|
|
705
|
-
className="inline-flex items-center gap-1
|
|
707
|
+
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"
|
|
706
708
|
>
|
|
707
|
-
|
|
708
|
-
|
|
709
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
710
|
+
<path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3Z" />
|
|
711
|
+
</svg>
|
|
712
|
+
<span className="truncate max-w-[min(42vw,220px)]">{mobile ? modelName : `${providerLabel} · ${modelName}`}</span>
|
|
713
|
+
<svg width="7" height="7" viewBox="0 0 16 16" fill="none" className="shrink-0 opacity-40">
|
|
709
714
|
<path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
710
715
|
</svg>
|
|
711
716
|
</button>
|
|
712
717
|
</Tip>
|
|
713
718
|
{modelSwitcherOpen && (
|
|
714
|
-
<div className="absolute z-50 top-full left-0 mt-2 w-[
|
|
719
|
+
<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">
|
|
715
720
|
<div className="text-[10px] font-600 text-text-3/50 uppercase tracking-wider mb-2">Provider</div>
|
|
716
721
|
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
717
722
|
{providers.map((p) => (
|
|
@@ -733,119 +738,117 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
733
738
|
onChange={(m) => void handleModelSwitch(session.provider, m)}
|
|
734
739
|
models={currentModels}
|
|
735
740
|
defaultModels={currentProviderInfo?.defaultModels}
|
|
741
|
+
credentialId={session.credentialId}
|
|
742
|
+
apiEndpoint={session.apiEndpoint}
|
|
743
|
+
supportsDiscovery={currentProviderInfo?.supportsModelDiscovery}
|
|
736
744
|
className="px-2.5 py-1.5 rounded-[7px] text-[12px] font-mono bg-white/[0.04] hover:bg-white/[0.06] transition-colors"
|
|
737
745
|
/>
|
|
738
746
|
</div>
|
|
739
747
|
)}
|
|
740
748
|
</div>
|
|
741
749
|
)}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
750
|
+
{threadContextLabel && (
|
|
751
|
+
<HeaderChip title={threadContextLabel}>
|
|
752
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
753
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z" />
|
|
754
|
+
</svg>
|
|
755
|
+
<span className="truncate max-w-[min(42vw,220px)]">{threadContextLabel}</span>
|
|
756
|
+
</HeaderChip>
|
|
757
|
+
)}
|
|
758
|
+
<Tip label={`Open working directory: ${workspaceLabel}`}>
|
|
759
|
+
<HeaderChip
|
|
745
760
|
onClick={() => { api('POST', '/files/open', { path: session.cwd }).catch(() => {}) }}
|
|
746
|
-
|
|
761
|
+
title={workspaceLabel}
|
|
762
|
+
className="max-w-[min(44vw,220px)]"
|
|
747
763
|
>
|
|
748
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
764
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
|
|
749
765
|
<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" />
|
|
750
766
|
</svg>
|
|
751
|
-
|
|
767
|
+
<span className="truncate">{mobile ? 'Workspace' : workspaceLabel}</span>
|
|
768
|
+
</HeaderChip>
|
|
752
769
|
</Tip>
|
|
753
|
-
{
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
{liveStatus.status}
|
|
778
|
-
</span>
|
|
779
|
-
)}
|
|
780
|
-
{liveStatus.goal && (
|
|
781
|
-
<span className="text-[10px] text-text-3/40 font-mono truncate max-w-[180px]" title={liveStatus.goal}>
|
|
782
|
-
{liveStatus.goal}
|
|
783
|
-
</span>
|
|
784
|
-
)}
|
|
785
|
-
{liveStatus.nextAction && (
|
|
786
|
-
<>
|
|
787
|
-
<span className="text-[9px] text-text-3/20 shrink-0">→</span>
|
|
788
|
-
<span className="text-[10px] text-text-3/35 font-mono truncate max-w-[140px]" title={liveStatus.nextAction}>
|
|
789
|
-
{liveStatus.nextAction}
|
|
790
|
-
</span>
|
|
791
|
-
</>
|
|
792
|
-
)}
|
|
793
|
-
</>
|
|
794
|
-
)
|
|
795
|
-
})()}
|
|
770
|
+
{liveStatus?.status && (
|
|
771
|
+
<HeaderChip
|
|
772
|
+
className={`${
|
|
773
|
+
liveStatus.status === 'blocked' ? 'bg-amber-400/12 border-amber-400/15 text-amber-300'
|
|
774
|
+
: liveStatus.status === 'ok' ? 'bg-emerald-400/12 border-emerald-400/15 text-emerald-400'
|
|
775
|
+
: liveStatus.status === 'progress' ? 'bg-blue-500/12 border-blue-500/15 text-blue-400'
|
|
776
|
+
: 'text-text-3/60'
|
|
777
|
+
}`}
|
|
778
|
+
title={liveStatus.goal || liveStatus.summary || liveStatus.nextAction || liveStatus.status}
|
|
779
|
+
>
|
|
780
|
+
<span className={`w-1.5 h-1.5 rounded-full ${
|
|
781
|
+
liveStatus.status === 'blocked' ? 'bg-amber-300'
|
|
782
|
+
: liveStatus.status === 'ok' ? 'bg-emerald-400'
|
|
783
|
+
: liveStatus.status === 'progress' ? 'bg-blue-400'
|
|
784
|
+
: 'bg-text-3/30'
|
|
785
|
+
}`} />
|
|
786
|
+
{liveStatus.status}
|
|
787
|
+
</HeaderChip>
|
|
788
|
+
)}
|
|
789
|
+
{!mobile && liveStatus?.nextAction && (
|
|
790
|
+
<span className="text-[10px] text-text-3/45 font-mono truncate max-w-[min(34vw,220px)]" title={liveStatus.nextAction}>
|
|
791
|
+
Next: {liveStatus.nextAction}
|
|
792
|
+
</span>
|
|
793
|
+
)}
|
|
796
794
|
</div>
|
|
797
795
|
</div>
|
|
798
796
|
|
|
799
|
-
{
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
<
|
|
803
|
-
|
|
804
|
-
onClick={handleToggleHeartbeat}
|
|
805
|
-
disabled={heartbeatSaving}
|
|
806
|
-
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
|
|
807
|
-
${heartbeatWillRun ? 'text-emerald-400 hover:bg-emerald-500/10' : 'text-text-3/60 hover:bg-white/[0.04]'}`}
|
|
808
|
-
>
|
|
809
|
-
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${heartbeatWillRun ? 'bg-emerald-400' : 'bg-text-3/30'}`} />
|
|
810
|
-
HB
|
|
811
|
-
{heartbeatEnabled && !loopIsOngoing && !heartbeatExplicitOptIn && (
|
|
812
|
-
<span className="text-[9px] text-text-3/40">(bounded)</span>
|
|
813
|
-
)}
|
|
814
|
-
</button>
|
|
815
|
-
</Tip>
|
|
816
|
-
<div className="relative" ref={hbDropdownRef}>
|
|
817
|
-
<Tip label="Set heartbeat interval">
|
|
797
|
+
<div className={`flex items-center gap-2 shrink-0 ${mobile ? 'w-full justify-between pt-1' : 'ml-auto'}`}>
|
|
798
|
+
{/* Heartbeat compound control */}
|
|
799
|
+
{heartbeatSupported && (
|
|
800
|
+
<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">
|
|
801
|
+
<Tip label={heartbeatWillRun ? 'Disable heartbeat — periodic check-ins' : 'Enable heartbeat — periodic check-ins'}>
|
|
818
802
|
<button
|
|
819
|
-
onClick={
|
|
803
|
+
onClick={handleToggleHeartbeat}
|
|
820
804
|
disabled={heartbeatSaving}
|
|
821
|
-
|
|
805
|
+
aria-pressed={heartbeatWillRun}
|
|
806
|
+
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
|
|
807
|
+
${heartbeatWillRun ? 'text-emerald-400 hover:bg-emerald-500/10' : 'text-text-3/70 hover:bg-white/[0.04]'}`}
|
|
822
808
|
>
|
|
823
|
-
<span className=
|
|
824
|
-
<
|
|
825
|
-
|
|
826
|
-
|
|
809
|
+
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${heartbeatWillRun ? 'bg-emerald-400' : heartbeatEnabled ? 'bg-amber-300' : 'bg-text-3/30'}`} />
|
|
810
|
+
<span className="hidden sm:inline">Heartbeat</span>
|
|
811
|
+
<span className="sm:hidden">HB</span>
|
|
812
|
+
<span className={`hidden md:inline text-[9px] uppercase tracking-wider ${
|
|
813
|
+
heartbeatWillRun ? 'text-emerald-300/80' : heartbeatEnabled ? 'text-amber-300/70' : 'text-text-3/40'
|
|
814
|
+
}`}>
|
|
815
|
+
{heartbeatWillRun ? 'On' : heartbeatEnabled ? 'Bounded' : 'Off'}
|
|
816
|
+
</span>
|
|
827
817
|
</button>
|
|
828
818
|
</Tip>
|
|
829
|
-
|
|
830
|
-
<
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
</
|
|
842
|
-
|
|
819
|
+
<div className="relative" ref={hbDropdownRef}>
|
|
820
|
+
<Tip label="Set heartbeat interval">
|
|
821
|
+
<button
|
|
822
|
+
onClick={() => setHbDropdownOpen((o) => !o)}
|
|
823
|
+
disabled={heartbeatSaving}
|
|
824
|
+
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]"
|
|
825
|
+
>
|
|
826
|
+
<span className="text-[11px] font-600">{formatDuration(heartbeatIntervalSec)}</span>
|
|
827
|
+
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="opacity-40">
|
|
828
|
+
<polyline points="6 9 12 15 18 9" />
|
|
829
|
+
</svg>
|
|
830
|
+
</button>
|
|
831
|
+
</Tip>
|
|
832
|
+
{hbDropdownOpen && (
|
|
833
|
+
<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]">
|
|
834
|
+
{[...(typeof window !== 'undefined' && window.location.hostname === 'localhost' ? [10, 15, 30, 60] : []), 1800, 3600, 7200, 21600, 43200].map((sec) => (
|
|
835
|
+
<button
|
|
836
|
+
key={sec}
|
|
837
|
+
onClick={() => handleSelectHeartbeatInterval(sec)}
|
|
838
|
+
className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none
|
|
839
|
+
${sec === heartbeatIntervalSec ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:bg-white/[0.06]'}`}
|
|
840
|
+
>
|
|
841
|
+
{formatDuration(sec)}
|
|
842
|
+
</button>
|
|
843
|
+
))}
|
|
844
|
+
</div>
|
|
845
|
+
)}
|
|
846
|
+
</div>
|
|
843
847
|
</div>
|
|
844
|
-
|
|
845
|
-
)}
|
|
848
|
+
)}
|
|
846
849
|
|
|
847
|
-
|
|
848
|
-
|
|
850
|
+
{/* Action buttons */}
|
|
851
|
+
<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">
|
|
849
852
|
{streaming && (
|
|
850
853
|
<>
|
|
851
854
|
<IconButton onClick={onStop} variant="danger" tooltip="Stop" aria-label="Stop generation" size="sm">
|
|
@@ -885,18 +888,11 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
885
888
|
</IconButton>
|
|
886
889
|
)}
|
|
887
890
|
<div className="w-px h-3.5 bg-white/[0.06] mx-0.5" />
|
|
888
|
-
<IconButton onClick={() =>
|
|
889
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
890
|
-
<
|
|
891
|
+
<IconButton onClick={(e) => { e.stopPropagation(); onMenuToggle() }} tooltip="More" aria-label="Chat menu" size="sm">
|
|
892
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
893
|
+
<circle cx="12" cy="6" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="12" cy="18" r="1" />
|
|
891
894
|
</svg>
|
|
892
895
|
</IconButton>
|
|
893
|
-
{(!agent || mobile) && (
|
|
894
|
-
<IconButton onClick={(e) => { e.stopPropagation(); onMenuToggle() }} tooltip="Menu" aria-label="Chat menu" size="sm">
|
|
895
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
896
|
-
<circle cx="12" cy="6" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="12" cy="18" r="1" />
|
|
897
|
-
</svg>
|
|
898
|
-
</IconButton>
|
|
899
|
-
)}
|
|
900
896
|
{agent && (
|
|
901
897
|
<IconButton onClick={() => setInspectorOpen(!inspectorOpen)} active={inspectorOpen} tooltip="Settings" aria-label="Toggle inspector" size="sm">
|
|
902
898
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
@@ -906,76 +902,18 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
906
902
|
</IconButton>
|
|
907
903
|
)}
|
|
908
904
|
</div>
|
|
905
|
+
</div>
|
|
909
906
|
</div>
|
|
910
907
|
|
|
911
|
-
{/* Context bar: tools
|
|
908
|
+
{/* Context bar: tools and links */}
|
|
912
909
|
{hasContextBar && (
|
|
913
|
-
<div className="
|
|
914
|
-
|
|
915
|
-
<>
|
|
916
|
-
<Tip label={missionPaused ? 'Resume mission loop' : 'Pause mission loop'}>
|
|
917
|
-
<button
|
|
918
|
-
onClick={handleToggleMissionPause}
|
|
919
|
-
disabled={mainLoopSaving}
|
|
920
|
-
className={`flex items-center gap-1.5 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
|
|
921
|
-
${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'}`}
|
|
922
|
-
>
|
|
923
|
-
<span className={`w-1.5 h-1.5 rounded-full ${missionPaused ? 'bg-amber-300' : 'bg-emerald-400'}`} />
|
|
924
|
-
{missionPaused ? 'Paused' : 'Live'}
|
|
925
|
-
</button>
|
|
926
|
-
</Tip>
|
|
927
|
-
|
|
928
|
-
<Tip label={missionMode === 'autonomous' ? 'Switch to assisted mode' : 'Switch to autonomous mode'}>
|
|
929
|
-
<button
|
|
930
|
-
onClick={handleToggleMissionMode}
|
|
931
|
-
disabled={mainLoopSaving}
|
|
932
|
-
className={`flex items-center gap-1 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
|
|
933
|
-
${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'}`}
|
|
934
|
-
>
|
|
935
|
-
{missionMode === 'autonomous' ? 'Auto' : 'Assist'}
|
|
936
|
-
</button>
|
|
937
|
-
</Tip>
|
|
938
|
-
<Tip label="Run one iteration of the mission loop">
|
|
939
|
-
<button
|
|
940
|
-
onClick={handleNudgeMission}
|
|
941
|
-
disabled={mainLoopSaving || missionPaused}
|
|
942
|
-
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"
|
|
943
|
-
>
|
|
944
|
-
Nudge
|
|
945
|
-
</button>
|
|
946
|
-
</Tip>
|
|
947
|
-
<Tip label="Set or edit the mission goal">
|
|
948
|
-
<button
|
|
949
|
-
onClick={handleOpenGoalModal}
|
|
950
|
-
disabled={mainLoopSaving}
|
|
951
|
-
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"
|
|
952
|
-
>
|
|
953
|
-
Goal
|
|
954
|
-
</button>
|
|
955
|
-
</Tip>
|
|
956
|
-
{missionEventsCount > 0 && (
|
|
957
|
-
<Tip label="Clear queued mission events">
|
|
958
|
-
<button
|
|
959
|
-
onClick={handleClearMissionEvents}
|
|
960
|
-
disabled={mainLoopSaving}
|
|
961
|
-
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"
|
|
962
|
-
>
|
|
963
|
-
Events {missionEventsCount}
|
|
964
|
-
</button>
|
|
965
|
-
</Tip>
|
|
966
|
-
)}
|
|
967
|
-
<span className="text-[9px] text-text-3/40 uppercase tracking-wider shrink-0">
|
|
968
|
-
{missionStatus}{missionMomentum !== null ? ` · ${missionMomentum}` : ''}
|
|
969
|
-
</span>
|
|
970
|
-
{mainLoopError && <span className="text-[9px] text-red-300/80 truncate max-w-[240px]" title={mainLoopError}>{mainLoopError}</span>}
|
|
971
|
-
{mainLoopNotice && <span className="text-[9px] text-emerald-300/80 truncate max-w-[200px]" title={mainLoopNotice}>{mainLoopNotice}</span>}
|
|
972
|
-
</>
|
|
973
|
-
)}
|
|
910
|
+
<div className="border-t border-white/[0.05] bg-black/[0.08] px-4 py-2">
|
|
911
|
+
<div className="flex items-center gap-1.5 flex-wrap">
|
|
974
912
|
{hasMemoryLink && (
|
|
975
913
|
<Tip label="View agent memories">
|
|
976
914
|
<button
|
|
977
915
|
onClick={() => { setMemoryAgentFilter(session.agentId!); setActiveView('memory'); setSidebarOpen(true) }}
|
|
978
|
-
className="flex items-center gap-1 px-2 py-1 rounded-[
|
|
916
|
+
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"
|
|
979
917
|
>
|
|
980
918
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
981
919
|
<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" />
|
|
@@ -1007,7 +945,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1007
945
|
</button>
|
|
1008
946
|
</Tip>
|
|
1009
947
|
{sourceDropdownOpen && (
|
|
1010
|
-
<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-[
|
|
948
|
+
<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)]">
|
|
1011
949
|
<button
|
|
1012
950
|
onClick={() => { onConnectorFilterChange(null); setSourceDropdownOpen(false) }}
|
|
1013
951
|
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 ${
|
|
@@ -1072,12 +1010,12 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1072
1010
|
<Tip label="Copy CLI resume command">
|
|
1073
1011
|
<button
|
|
1074
1012
|
onClick={handleCopySessionId}
|
|
1075
|
-
className="flex items-center gap-1 px-2 py-1 rounded-l-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer"
|
|
1013
|
+
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"
|
|
1076
1014
|
>
|
|
1077
1015
|
<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">
|
|
1078
1016
|
<path d="M4 17l6 0l0 -6" /><path d="M20 7l-6 0l0 6" /><path d="M4 17l10 -10" />
|
|
1079
1017
|
</svg>
|
|
1080
|
-
<span className="text-[10px] font-mono text-text-3/40 group-hover/resume:text-text-3/60 truncate max-w-[
|
|
1018
|
+
<span className="text-[10px] font-mono text-text-3/40 group-hover/resume:text-text-3/60 truncate max-w-[min(46vw,220px)]">
|
|
1081
1019
|
{copied ? 'Copied!' : `${resumeHandle.label}: ${resumeHandle.id}`}
|
|
1082
1020
|
</span>
|
|
1083
1021
|
</button>
|
|
@@ -1085,7 +1023,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1085
1023
|
<Tip label="Dismiss resume handle">
|
|
1086
1024
|
<button
|
|
1087
1025
|
onClick={handleDismissResumeHandle}
|
|
1088
|
-
className="px-1 py-1 rounded-r-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer opacity-0 group-hover/resume:opacity-100"
|
|
1026
|
+
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"
|
|
1089
1027
|
>
|
|
1090
1028
|
<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">
|
|
1091
1029
|
<path d="M4 4l8 8M12 4l-8 8" />
|
|
@@ -1111,51 +1049,10 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
1111
1049
|
</Tip>
|
|
1112
1050
|
)}
|
|
1113
1051
|
</div>
|
|
1052
|
+
</div>
|
|
1114
1053
|
)}
|
|
1115
1054
|
|
|
1116
1055
|
</header>
|
|
1117
|
-
|
|
1118
|
-
{/* Goal modal — fixed to viewport, not constrained by header */}
|
|
1119
|
-
{goalModalOpen && (
|
|
1120
|
-
<div className="fixed inset-0 z-[200] flex items-center justify-center" onClick={() => setGoalModalOpen(false)}>
|
|
1121
|
-
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" />
|
|
1122
|
-
<div
|
|
1123
|
-
className="relative w-[420px] max-w-[90vw] rounded-[16px] border border-white/[0.08] bg-surface shadow-2xl p-6"
|
|
1124
|
-
onClick={(e) => e.stopPropagation()}
|
|
1125
|
-
>
|
|
1126
|
-
<h4 className="font-display text-[15px] font-700 text-text mb-1">Set Mission Goal</h4>
|
|
1127
|
-
<p className="text-[11px] text-text-3/60 mb-4">Define what this agent should work towards.</p>
|
|
1128
|
-
<textarea
|
|
1129
|
-
ref={goalInputRef}
|
|
1130
|
-
value={goalDraft}
|
|
1131
|
-
onChange={(e) => setGoalDraft(e.target.value)}
|
|
1132
|
-
onKeyDown={(e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) handleSubmitGoal(); if (e.key === 'Escape') setGoalModalOpen(false) }}
|
|
1133
|
-
placeholder="Describe the agent's mission..."
|
|
1134
|
-
rows={4}
|
|
1135
|
-
className="w-full py-2.5 px-3 rounded-[10px] text-[13px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/50 outline-none focus:border-accent-bright/30 mb-1 resize-y min-h-[80px]"
|
|
1136
|
-
style={{ fontFamily: 'inherit' }}
|
|
1137
|
-
/>
|
|
1138
|
-
<p className="text-[10px] text-text-3/40 mb-3">Cmd+Enter to submit</p>
|
|
1139
|
-
<div className="flex items-center justify-end gap-2">
|
|
1140
|
-
<button
|
|
1141
|
-
onClick={() => setGoalModalOpen(false)}
|
|
1142
|
-
className="px-4 py-2 rounded-[8px] text-[12px] font-600 text-text-3 bg-white/[0.04] hover:bg-white/[0.08] transition-colors cursor-pointer border-none"
|
|
1143
|
-
style={{ fontFamily: 'inherit' }}
|
|
1144
|
-
>
|
|
1145
|
-
Cancel
|
|
1146
|
-
</button>
|
|
1147
|
-
<button
|
|
1148
|
-
onClick={handleSubmitGoal}
|
|
1149
|
-
disabled={!goalDraft.trim()}
|
|
1150
|
-
className="px-4 py-2 rounded-[8px] text-[12px] font-600 text-accent-bright bg-accent-soft hover:bg-accent-soft/80 transition-colors cursor-pointer border border-accent-bright/20 disabled:opacity-40 disabled:cursor-default"
|
|
1151
|
-
style={{ fontFamily: 'inherit' }}
|
|
1152
|
-
>
|
|
1153
|
-
Set Goal
|
|
1154
|
-
</button>
|
|
1155
|
-
</div>
|
|
1156
|
-
</div>
|
|
1157
|
-
</div>
|
|
1158
|
-
)}
|
|
1159
1056
|
</>
|
|
1160
1057
|
)
|
|
1161
1058
|
}
|