feat: add /new slash command to create chat sessions

Introduces a `/new` slash command that lets users create a fresh chat
session for the current agent without leaving the chat input.

- Add `createNewSession` to `useGateway` that calls `sessions.create`
  on the gateway (with a client-side fallback key when the RPC is
  unavailable).
- Register `/new` in the slash-command menu with i18n descriptions
  across all 8 supported languages.
- Wire `onNewSession` through `Chat` → `ChatInput` so typing `/new`
  triggers session creation.
- Add `extractAgentIdFromKey` and `formatAgentId` helpers to
  `sessionName.ts` to derive a human-readable agent name from the
  session key (e.g. `agent:my-cool-bot:…` → "My Cool Bot").
- Use the new helpers in `Header` and `App` to show per-session agent
  names, especially for sub-agent sessions where the gateway-level
  identity differs from the session agent.

Made-with: Cursor
This commit is contained in:
Yaro
2026-02-28 22:43:20 +02:00
parent a11cebdc55
commit 4e66d7c4bd
9 changed files with 119 additions and 77 deletions

View File

@@ -24,6 +24,7 @@ export interface ReplyContext {
interface Props {
onSend: (text: string, attachments?: Array<{ mimeType: string; fileName: string; content: string }>) => void;
onNewSession?: () => Promise<void>;
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,