diff --git a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts
index 3b1e8cd8..6fddaead 100644
--- a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts
+++ b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts
@@ -187,6 +187,28 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp
// it was created with — `queuedRef` would be right but `slashCommands`/`thinkingLevel` would be stale.
drainQueueRef.current = drainQueue;
+ /**
+ * Deliver the queue INTO the running turn instead of waiting for it to end.
+ *
+ * The agent's input is a streaming iterable, and a message pushed onto it mid-turn is picked up at the
+ * next step boundary — the running turn reads it and carries on with the added context. That was worth
+ * verifying rather than assuming: a probe pushed a sentinel four seconds into a turn busy with `sleep`
+ * calls, and the turn's own final answer quoted the late instruction and obeyed it. One turn, one
+ * result.
+ *
+ * So this needs no interrupt and no "carry on, but…" preamble wrapped around your words. Nothing is
+ * abandoned mid-flight, and the agent is never told to stop something it was part-way through.
+ *
+ * The path is the same `deliverBatch` the drain uses; officer pushes onto the live session rather than
+ * opening a new one whenever `_claudeKill` is set, which is exactly the injection.
+ */
+ const sendQueuedNow = async () => {
+ if (queuedRef.current.length === 0) return;
+ const batch = queuedRef.current;
+ setQueue([]);
+ await deliverBatch(batch);
+ };
+
const handleSend = async () => {
const text = input.trim();
if (!text) return;
@@ -297,6 +319,12 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp
if (ev.key === 'ArrowDown') return recallPrompt(ev, 1);
if (ev.key === 'Enter' && !ev.shiftKey) {
ev.preventDefault();
+ // Enter on an EMPTY composer with something queued means "don't wait — say it now". The keystroke
+ // was free: `handleSend` has always returned immediately on empty input, so nothing is displaced.
+ if (!input.trim() && queuedRef.current.length > 0) {
+ void sendQueuedNow();
+ return;
+ }
handleSend();
}
};
@@ -454,6 +482,7 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp
handleKeyDown,
handleEscape,
queued,
+ sendQueuedNow,
unqueue: (id: string) => setQueue(queuedRef.current.filter((item) => item.id !== id)),
appendToInput,
attachments: attachmentManager.attachments,
diff --git a/src/workspaces/officerdev/src/apps/Chat/components/InputArea.tsx b/src/workspaces/officerdev/src/apps/Chat/components/InputArea.tsx
index edf6a2ad..5d900e03 100644
--- a/src/workspaces/officerdev/src/apps/Chat/components/InputArea.tsx
+++ b/src/workspaces/officerdev/src/apps/Chat/components/InputArea.tsx
@@ -39,6 +39,7 @@ export const InputArea = ({ manager }: InputAreaProps) => {
thinkingLevel,
setThinkingLevel,
queued,
+ sendQueuedNow,
unqueue,
} = manager;
@@ -65,7 +66,7 @@ export const InputArea = ({ manager }: InputAreaProps) => {