Send a message
The composer is the textarea pinned to the bottom of an open session. Type a message and press Enter, and Omniscio delivers it to that session's running Claude process as one operator turn, with the reply streaming into the chat above. Attachments, AI-suggested replies, snippets, the skill menu and Send Later all live here too.
What it is
The composer is the textarea pinned at the bottom of an open session — type a message, press Enter (or Ctrl+Enter, depending on your Submit key setting), and Omniscio delivers the text to the running Claude CLI process for that session. This is the core back-and-forth with the agent: each send is one operator turn, and the agent replies stream into the chat above the composer.
The composer also accepts file attachments (paperclip, drag-drop, or paste — see attach-a-file.md), AI-suggested replies, snippets, and a "Send Later" scheduler. Ctrl+V (Cmd+V on macOS) works from anywhere in the open session — sidebar, toolbar, body — and the paste lands in this composer; you don't have to click into the textarea first. The redirect is skipped when a modal owns input, when your focus is already in some other text field, or when the session's textarea is disabled (ended / archived / paused sessions). While Claude is mid-stream, you can still type — your draft is preserved across session switches and app restarts. Slash commands like /clear or /init are passed through as plain text to the CLI; Omniscio does not parse them itself.
A session has to exist before you can send. If you don't have one open yet, see start-a-new-session.md.
Where to find it
The composer is the rounded textarea pinned to the bottom of an open session’s panel, with the paper-airplane Send button at its right edge. The preference that decides whether Enter or Ctrl+Enter sends lives in Settings → Sessions, next to the list auto-continuation toggle.
How it behaves
How to use it
- Click anywhere in the composer at the bottom of the session panel (the rounded textarea labeled "Send a message..." or one of its status-aware variants like "Type your response..." for
needs_you, "Continue the conversation..." forended, or "Send your first message to begin..." for a fresh session — on a phone the long hints show shorter variants, e.g. "Your first message...", so the empty box stays a single line). - Type your message. Drafts auto-save per session — switching to another session and back keeps your text. Drafts also survive app quit. The textarea grows vertically as you type up to a soft limit; you can drag the small grip handle above the composer to resize it taller.
- Press Enter to send. This is the default. The textarea clears, your message appears in the chat as an operator turn (right-aligned bubble), the agent's status flips to "Running", and Claude's reply streams in from the top.
- Want a newline instead? Hold Shift and press Enter — that inserts a line break and keeps the draft.
- Prefer Ctrl+Enter? Open Settings → Sessions → Submit key and pick "Ctrl+Enter". Plain Enter then becomes a newline and only Ctrl+Enter sends. Useful if you write long multi-paragraph prompts and don't want to accidentally fire half-typed messages.
- One setting, everywhere you launch an agent. Your Submit key choice applies consistently wherever you send an agent message — this composer AND the Start a session dialogs (from an alert, the inbox, Team Chat, or Supermail) — so Enter vs Ctrl+Enter behaves the same across all of them. (Human-to-human chats — Team Chat, support chat, and channel messages — follow their own separate Team Chat submit key under the same Settings section.) A mid-composition key (typing an emoji or another language via an IME) never fires a send.
- List auto-continuation. When you're typing a bulleted (
-,*,+) or numbered (1.,2)) list and press the newline key, the next line is pre-seeded with the next marker so the list keeps going — numbered lists increment automatically (1.→2.→3.), bullets repeat, leading indentation is preserved. Press the newline key on an empty list item (marker present, no content after it) to exit the list — the marker is stripped and you get a plain blank line. With the default submit key (Enter), the trigger is Shift+Enter; with Ctrl+Enter mode, it's plain Enter. Toggle at Settings → Sessions → List auto-continuation (default on). Mobile soft keyboards don't have a Shift key, so on mobile with default-Enter mode this is effectively desktop-only; switch to Ctrl+Enter submit mode to use it on mobile.
- Or click the Send button (paper-airplane icon in the accent color, on the right side of the composer). It's disabled while the textarea is empty AND no attachments are queued. While the session is
running, the same button stays available — its placeholder reads "Send now (interrupts the current step)...", because a plain click is what it does (see step 6). That running-state Send is the SAME split button as the idle one: with text in the composer a ▾ caret sits beside the plane (and press-and-hold works too), so Send Later, the "After the agent finishes" queue, and Send as aside stay reachable in the composer during a run — not only when idle (see step 9 and queue-a-message.md). The caret opens those options WITHOUT interrupting; only a plain click on the plane interrupts. - Send while paused, ended, error, or archived. All four are recoverable: typing and sending auto-resumes the session by re-spawning the CLI with
--resume <cliSessionId>, so multi-turn context is preserved. An archived session is silently un-archived on send and re-appears in the live list; paused / ended / error sessions just respawn the CLI for the new turn and the row flips back torunning. Sending duringstartingalso works — the placeholder says "Type now — will send when session is ready..." and both Enter and the Send button submit: the text is folded into the launching turn (or expedites a recovery-queued session); if the launch handshake isn't far enough along yet, the send briefly waits for the session to finish coming up and then delivers automatically — so a message typed a beat too early heals itself instead of erroring, honoring that "will send when session is ready" promise. You only ever see a "still starting" toast (with your draft restored) in the rare case where a session never finishes starting within a short bounded window — a genuinely stuck spawn. There is no "explicit unpause required" gate today. - While Claude is streaming a reply. Sending a new message during a stream interrupts the current turn: Omniscio stops the in-flight response, records "Your message interrupted the previous turn" in the transcript, and restarts the agent with your message (merged with the original prompt when the session hasn't established its CLI id yet). The partial answer streamed so far is kept in the chat. Want it to wait instead of interrupt? Queue the message to send when the current step finishes — hold Send → Send Later → "After the agent finishes" (see queue-a-message.md); you can line up several. To stop the agent without sending anything, press Esc twice: the first press shows a "Press Esc again to interrupt" hint and the second (within ~1.5s) actually stops the running CLI process, so one stray — or held — Esc can never kill a turn. Esc clears anything else open first: it leaves a focused text box, closes an open file preview, then deselects a non-running session, so interrupt is its last rung. Full ladder: keyboard-shortcuts.md. With no agent turn in progress, Esc just closes the panel. The red ■ Stop button needs no confirm — a click is already deliberate.
- Codex sessions interrupt-and-redirect the same way. Sending mid-turn to a running Codex session now stops its current step and runs your message next, with the conversation context preserved (the partial output so far is kept, and there's no "stopped" line wedged before your message). Previously a mid-turn send to Codex was refused with "still working on your previous message" — now it does what the button promises. The other alternative engines (Gemini, Pi, Cursor, OpenCode, OpenClaw) still ask you to wait for the current turn to finish before sending; press Esc twice (the double-tap confirm above) or click the red ■ Stop button once, to stop them first. Automated sends (away-mode auto-replies, agent-to-agent nudges) never interrupt a live turn — only a message you send does.
- Slash commands & the skill menu. Typing
/clear,/compact,/help,/init, etc. and sending works — Omniscio just forwards the text to the CLI which interprets it natively. Note: stream-JSON mode does not give every slash command rich UI, so some commands print plain output instead of opening a panel. When you type/at the start of a message, an autocomplete menu of your installed skills opens — each row shows the skill's/commandand its description over up to two lines, with the whole description on hover, and it filters as you keep typing. Use ↑/↓ to move the highlight, Enter or Tab (or a mouse click) to insert the chosen/<skill>, and Esc to dismiss. It triggers only on a leading slash — a slash mid-sentence (and/or, a/path/to/file) is left alone — and it lists your~/.claude/skills, installed plugin skills, and the project-local skills that live in the session's own working directory (<workdir>/.claude/skills) — exactly the skills the CLI can actually run in that session. Project-local rows sort first and carry a small Project badge; if one shares a name with a global skill, the project one wins (the CLI resolves it that way too). A plugin skill inserts as/<plugin>:<skill>, e.g./mattpocock-skills:tdd. When a project spans multiple repos, the menu scopes to the specific repo/folder that session is running in, so an inserted/skillalways resolves. Toggle it at Settings → Sessions → Slash-command menu for skills (default on). Built-in commands like/clearand your own custom.claude/commandsaren't in the menu yet — type those yourself. - Quick-action affordances next to the composer. The accent-colored bolts and chips (Alt+1/2/3 AI suggestions, Alt+S snippet picker, Alt+Z Zap button) drop pre-written text into the composer or send it directly. See use-quick-responses.md and quick-replies.md. On a phone, those left-side action buttons (attach, quick replies, etc.) show inline — the same enabled buttons as on desktop, no + menu. Choose which appear — on phone and desktop — at Settings → Sessions → Composer buttons (switch off the ones you don't use to free up room; the Clipboard-history, voice, and screenshot buttons are always hidden on mobile).
- Send later, or as an aside. With text in the composer, press and hold the Send button (~½ s — a small corner fold in its lower-right hints this) to open a small send-options menu: pick Send Later to schedule it (a time picker opens, identical to the snooze picker — see send-later.md), or Send as aside to fire it as a one-shot side question that stays out of the main thread (see asides.md). Send Later is also on the
sendLatershortcut (default Ctrl+Shift+L) and the session's ⋯ overflow menu.
What happens after a successful send: the textarea clears, the draft for this session is dropped, the session row pins to the top of the sidebar (lastActiveAt updates), and the textarea may auto-focus a different session per your Post-send navigation preferences (Settings → Sessions → "After I send a message..."). What happens on failure: the draft is restored, the attachment chips come back, and a toast names the reason (ended session, network error, etc.). That failure toast also carries a View session button that jumps you back to the session that failed — useful because the error often surfaces after post-send navigation has already moved you elsewhere (the "took too long" timeout especially fires long after the send). Empty sends — no text and no attachments — silently no-op.
Replying to a stopped Claude session no longer waits for it to restart (2026-09-26). When your message has to bring a paused, ended, errored or archived Claude session back, Send finishes as soon as the message is saved, and the agent comes back in the background. Before this, a slow restart on a busy machine could hold the send open for three minutes and end in a "took too long" failure — on the phone especially — even though the message went through seconds later. If the restart itself fails, your message stays in the conversation (no draft is restored, because nothing was lost), a note in that session says why it couldn't resume, and the session shows as errored; sending again retries.
Your message never waits in the background restart line (2026-09-29). When many sessions come back together — after a usage-limit reset, or after Omniscio restarts — they start one at a time, spaced by Session restart spacing (Settings → Sessions). If you message a session that is still waiting its turn in that line, your message starts it right away instead of waiting for its place, and a "background" tag an automatic retry left on the session can no longer slow a message you send. Messages from other agents keep the spacing, so the line still protects your computer from a burst.
For agents
How it works
The composer textarea lives in SessionPanel.tsx (look for the <textarea> with aria-label="Response message" near the bottom of the file). Its onKeyDown handler is handleKeyDownOverride which delegates to useSessionPanel.ts — the relevant block computes shouldSubmit from submitKeyMode ('enter' default, 'ctrl_enter' opt-in) and calls handleSendResponse(). handleSendResponse(overrideText?) is the canonical send entry point — every programmatic-send caller (snippet picker, AI suggestion chips, Zap button, away-mode reply) reuses it via the override argument so draft handling and post-send navigation stay consistent. CLAUDE.md "handleSendResponse(overrideText?)" rule.
The send pipeline goes: composer → handleSendResponse → executeSend → useSessionStore.sendResponseAndNavigate → SESSION_SEND_RESPONSE IPC → session-service.ts sendResponse() → processManager.writeToStdin(). sendResponse() does the heavy lifting: auto-unsnooze if snoozedUntil was set, auto-unarchive if status was archived, partition attachments via attachment-partition.ts (images vs PDFs vs text docs — see chat-attachments.md), persist the operator message row, and hand the prompt to the process manager.
The /-skill autocomplete menu is a separate, self-contained layer under skill-slash/: a framework-free leading-slash detector + suggestion filter (skill-slash-detector.ts, unit-tested without a DOM — it also owns mergeProjectSkills, which dedups project rows over the global list by slug), a hook that reads the installed-skills store (useSkillSlashAutocomplete.ts, lazy-loading the list the first time a / appears), and the popover (SkillSlashAutocomplete.tsx, anchored above the composer). It's wired into ComposerInput.tsx's handleKeyDown so an open menu consumes ↑/↓/Enter/Tab/Esc before the send / undo / list-continuation handlers, and inserts through the same applyValue chokepoint. The global skill list comes from the existing skills service (useSkillsStore().installed — see use-skills.md); the session's project-local skills are fetched separately by session id via the skills:list-for-session IPC, which resolves that session's working directory main-side (resolveSessionWorkDir, the same resolver the spawn uses) and scans <workdir>/.claude/skills — so the renderer never hands the backend a filesystem path. This feature only reads both lists.
writeToStdin() in process-manager.ts kills the existing child process, bumps spawnEpoch (race guard), and re-spawns the CLI with --resume <cliSessionId> so the conversation history is preserved. The stale-account invariant kicks in here: if the persistent process was spawned under a different Claude account than the currently-active one, writeToStdin forces a fresh spawn so the new account's credentials get injected. A PERSON's send — isAutomatedWakeInbound says it is not the app's own send or another agent's message — takes the immediate spawn lane at both doors that start a session: UserMessageRouter.writeToStdin marks the next spawn 'interactive' before its final spawn, and runAutoResumeLaunch (session-auto-resume.ts) does the same before processManager.launch, so a leftover 'recovery' tag cannot decide the lane. A person's send that lands while a respawn is in flight rides that respawn (the respawn-window hold) and raises the spawn claim's one-shot wake (ProcessManager.wakeSpawnInFlightForPerson), which ends that spawn's recovery start-gap wait in spawn-concurrency-gate.ts (acquireSpawnSlot's personWaiting). Drafts are persisted in sessionDrafts (an in-memory Map in session-draft-store.ts) plus the SQLite sessions.draft_text column. The submitKeyMode setting flows from AppSettings in types.ts (default 'enter') and the keyboard-shortcut bindings live in keybindings.ts. For the full process lifecycle, see process-management.md.
Related
- start-a-new-session.md — needs to exist before you can send to it
- attach-a-file.md — paperclip, drag-drop, paste-image flow into the same Send
- chat-attachments.md — deep dive on what Claude actually sees for each attachment type
- use-quick-responses.md — Alt+1/2/3 AI suggestions, Alt+S snippets, Alt+Z Zap
- quick-replies.md — managing the snippet library that Alt+S pulls from
- send-later.md — schedule a message for future delivery instead of sending now
- session-stuck-in-needs-you.md — what to do when send appears to succeed but the agent never responds
- notifications-and-silence.md — Post-send navigation lives here, plus per-project mute
Last verified 2026-10-05