diff --git a/package-lock.json b/package-lock.json index 2eb4e62..43e4a31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -560,7 +560,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -577,7 +576,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -594,7 +592,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -611,7 +608,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -628,7 +624,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -645,7 +640,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -662,7 +656,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -679,7 +672,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -696,7 +688,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -713,7 +704,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -730,7 +720,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -747,7 +736,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -764,7 +752,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -781,7 +768,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -798,7 +784,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -815,7 +800,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -832,7 +816,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -849,7 +832,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -866,7 +848,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -883,7 +864,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -900,7 +880,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -917,7 +896,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -934,7 +912,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -951,7 +928,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -968,7 +944,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -985,7 +960,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1281,7 +1255,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1295,7 +1268,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1309,7 +1281,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1323,7 +1294,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1337,7 +1307,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1351,7 +1320,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1365,7 +1333,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1379,7 +1346,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1393,7 +1359,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1407,7 +1372,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1421,7 +1385,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1435,7 +1398,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1449,7 +1411,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1463,7 +1424,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1477,7 +1437,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1491,7 +1450,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1505,7 +1463,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1519,7 +1476,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1533,7 +1489,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1547,7 +1502,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1561,7 +1515,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1575,7 +1528,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1589,7 +1541,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1603,7 +1554,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1617,7 +1567,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2065,7 +2014,7 @@ "version": "24.10.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -2075,7 +2024,6 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2959,7 +2907,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/data-urls": { @@ -3102,7 +3049,6 @@ "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -3408,7 +3354,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3477,7 +3422,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5284,7 +5228,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -5456,14 +5399,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5476,7 +5417,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5746,7 +5686,6 @@ "version": "4.57.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -5994,7 +5933,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -6161,7 +6099,7 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unified": { @@ -6338,7 +6276,6 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.27.0", diff --git a/src/App.tsx b/src/App.tsx index 283abb5..e46e701 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import { LoginScreen } from './components/LoginScreen'; import { ConnectionBanner } from './components/ConnectionBanner'; import { KeyboardShortcuts } from './components/KeyboardShortcuts'; import { ToolCollapseProvider } from './contexts/ToolCollapseContext'; -import { sessionDisplayName } from './lib/sessionName'; +import { sessionDisplayName, extractAgentIdFromKey, formatAgentId } from './lib/sessionName'; import { X } from 'lucide-react'; import { useT } from './hooks/useLocale'; import { useSwipeSidebar } from './hooks/useSwipeSidebar'; @@ -29,7 +29,7 @@ function getSavedSplitRatio(): number { export default function App() { const { status, messages, sessions, activeSession, isGenerating, isLoadingHistory, - sendMessage, abort, switchSession, deleteSession, + sendMessage, abort, switchSession, deleteSession, createNewSession, authenticated, login, logout, connectError, isConnecting, agentIdentity, getClient, addEventListener, } = useGateway(); @@ -40,6 +40,19 @@ export default function App() { const splitRatioRef = useRef(splitRatio); const secondary = useSecondarySession(getClient, addEventListener, splitSession); const t = useT(); + const resolveAgentDisplayName = useCallback((sessionKey: string | null | undefined): string | undefined => { + if (!sessionKey) return agentIdentity?.name; + const session = sessions.find((s) => s.key === sessionKey); + const sessionAgentId = session?.agentId || extractAgentIdFromKey(sessionKey); + const connectedAgentId = agentIdentity?.agentId; + + // agent.identity.get is gateway-level (typically main agent), not per-session. + // For sub-agent sessions, prefer the session agent id to avoid showing the main agent name. + if (sessionAgentId && connectedAgentId && sessionAgentId !== connectedAgentId) { + return formatAgentId(sessionAgentId) || sessionAgentId; + } + return agentIdentity?.name || (sessionAgentId && formatAgentId(sessionAgentId)) || sessionAgentId; + }, [agentIdentity?.name, agentIdentity?.agentId, sessions]); const handleSplit = useCallback((key: string) => { setSplitSession(prev => prev === key ? null : key); }, []); @@ -181,10 +194,10 @@ export default function App() {
{/* Primary pane */}
-
setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} soundEnabled={soundEnabled} onToggleSound={toggleSound} messages={messages} agentAvatarUrl={agentIdentity?.avatar} agentName={agentIdentity?.name} onCompact={handleCompact} /> +
setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} soundEnabled={soundEnabled} onToggleSound={toggleSound} messages={messages} agentAvatarUrl={agentIdentity?.avatar} agentName={resolveAgentDisplayName(activeSession)} onCompact={handleCompact} />
Loading…
}> - + {/* Split divider + secondary pane */} @@ -212,7 +225,7 @@ export default function App() {
Loading…
}> - +
diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index c369652..c255c5b 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -18,8 +18,10 @@ interface Props { status: ConnectionStatus; sessionKey?: string; onSend: (text: string, attachments?: Array<{ mimeType: string; fileName: string; content: string }>) => void; + onNewSession?: () => Promise; onAbort: () => void; agentAvatarUrl?: string; + agentName?: string; } function isNoReply(msg: ChatMessage): boolean { @@ -70,7 +72,7 @@ function getDateKey(ts: number): string { /** Threshold in pixels — if the user is within this distance of the bottom, auto-scroll */ const SCROLL_THRESHOLD = 150; -export function Chat({ messages, isGenerating, isLoadingHistory, status, sessionKey, onSend, onAbort, agentAvatarUrl }: Props) { +export function Chat({ messages, isGenerating, isLoadingHistory, status, sessionKey, onSend, onNewSession, onAbort, agentAvatarUrl, agentName }: Props) { const t = useT(); const bottomRef = useRef(null); const scrollContainerRef = useRef(null); @@ -206,6 +208,8 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session }, [messages]); const showTyping = isGenerating && !hasStreamedText(messages); + const sessionAgentId = sessionKey?.match(/^agent:([^:]+):/)?.[1]; + const welcomeTitle = agentName || sessionAgentId || t('chat.welcome'); const { globalState, collapseAll, expandAll } = useToolCollapse(); const { toggle: toggleBookmark, isBookmarked, getForSession: getBookmarks } = useBookmarks(); @@ -291,7 +295,7 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session -
{t('chat.welcome')}
+
{welcomeTitle}
{t('chat.welcomeSub')}
@@ -422,7 +426,7 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
)}
- setReplyTo(null)} /> + setReplyTo(null)} /> ); } diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 50196c1..f40a62d 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -24,6 +24,7 @@ export interface ReplyContext { interface Props { onSend: (text: string, attachments?: Array<{ mimeType: string; fileName: string; content: string }>) => void; + onNewSession?: () => Promise; onAbort: () => void; isGenerating: boolean; disabled: boolean; @@ -94,7 +95,7 @@ function formatSize(bytes: number): string { return `${(bytes / (1024 * 1024)).toFixed(1)}MB`; } -export function ChatInput({ onSend, onAbort, isGenerating, disabled, sessionKey, replyTo, onCancelReply }: Props) { +export function ChatInput({ onSend, onNewSession, onAbort, isGenerating, disabled, sessionKey, replyTo, onCancelReply }: Props) { const t = useT(); const { sendOnEnter, toggle: toggleSendShortcut } = useSendShortcut(); const [text, setText] = useState(''); @@ -177,6 +178,17 @@ export function ChatInput({ onSend, onAbort, isGenerating, disabled, sessionKey, const handleSubmit = () => { const trimmed = text.trim(); if ((!trimmed && files.length === 0) || disabled) return; + + if ((trimmed === '/new' || trimmed.startsWith('/new ')) && onNewSession) { + void onNewSession(); + setText(''); + setFiles([]); + setShowSlash(false); + onCancelReply?.(); + if (sessionKey) draftsRef.current.delete(sessionKey); + return; + } + const attachments = files.length > 0 ? files.map(f => ({ mimeType: f.mimeType, fileName: f.file.name, diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 66d6bf6..3beb8cd 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -4,7 +4,7 @@ import type { ConnectionStatus, Session, ChatMessage } from '../types'; import { useT } from '../hooks/useLocale'; import { SettingsModal } from './SettingsModal'; import { copyToClipboard } from '../lib/clipboard'; -import { sessionDisplayName } from '../lib/sessionName'; +import { sessionDisplayName, extractAgentIdFromKey, formatAgentId } from '../lib/sessionName'; import { messagesToMarkdown, downloadFile } from '../lib/exportChat'; interface Props { @@ -24,6 +24,8 @@ interface Props { export function Header({ status, sessionKey, onToggleSidebar, activeSessionData, onLogout, soundEnabled, onToggleSound, messages, agentAvatarUrl, agentName, onCompact }: Props) { const t = useT(); const sessionLabel = activeSessionData ? sessionDisplayName(activeSessionData) : (sessionKey.split(':').pop() || sessionKey); + const sessionAgentId = activeSessionData?.agentId || extractAgentIdFromKey(sessionKey); + const headerAgentName = agentName || (sessionAgentId && formatAgentId(sessionAgentId)) || t('header.title'); const [showSessionInfo, setShowSessionInfo] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false); const sessionInfoRef = useRef(null); @@ -64,7 +66,7 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData, PinchChat { const img = e.target as HTMLImageElement; if (img.src !== window.location.origin + '/logo.png') { img.src = '/logo.png'; } else { img.style.display = 'none'; } }} />