feat: queue prompts while the agent is running (#598)
Allow the chat input to be sent while the agent is mid-turn or awaiting
approval. The backend's prompt.submit busy-input policy already queues any
prompt that lands during a running turn (tui_gateway/server.py:_handle_busy_submit)
and drains it as the next turn, so the message is never dropped.
- ChatInputPolicy: pure, testable send/placeholder decision logic
- ChatScreen: canSend stays enabled while typing/approving; placeholder shows
'Queued — sends when agent is free' when text is present during a busy turn
- ChatInputPolicyTest: truth-table covering typing, approval-pending,
disconnected, empty, attachment, and slash-command cases
- strings: chat_input_placeholder_queue
Note: this supersedes the Genesi599 fork's approach (dispatchViaRpc('queue ...')),
which double-submits and relies on a 4018-prone command.dispatch path. The
backend-queued prompt.submit path is the correct, single-submission contract.
Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
1 parent
d1fc477bcd
commit
5376d46683
4 files changed
+135
-4
No files matched your search
@@ -0,0 +1,35 @@
|
||||
package com.m57.hermescontrol.ui.chat
|
||||
|
||||
/**
|
||||
* Pure, testable decision logic for the chat input bar.
|
||||
*
|
||||
* The gateway's `prompt.submit` busy-input policy queues any prompt that lands
|
||||
* while the agent is mid-turn (tui_gateway/server.py:_handle_busy_submit), so a
|
||||
* message sent while the agent is typing or awaiting approval is never dropped —
|
||||
* it runs as the next turn. These helpers keep that contract explicit and
|
||||
* unit-testable without a Compose/Android harness.
|
||||
*/
|
||||
object ChatInputPolicy {
|
||||
/**
|
||||
* Whether the send affordance should be enabled.
|
||||
*
|
||||
* Unlike slash commands (which were always allowed mid-turn), regular
|
||||
* prompts are now allowed too: the backend queues them. The only gates are
|
||||
* a non-empty input (or a pending attachment) and an active connection.
|
||||
*/
|
||||
fun canSend(
|
||||
text: String,
|
||||
pendingAttachments: List<Any>,
|
||||
isConnected: Boolean,
|
||||
): Boolean = (text.isNotBlank() || pendingAttachments.isNotEmpty()) && isConnected
|
||||
|
||||
/**
|
||||
* Whether the input placeholder should read "queued" rather than the plain
|
||||
* "waiting" hint. Shown only while the agent is typing AND the user has
|
||||
* already typed something — i.e. a press of send would enqueue a turn.
|
||||
*/
|
||||
fun showQueuePlaceholder(
|
||||
text: String,
|
||||
isAgentTyping: Boolean,
|
||||
): Boolean = isAgentTyping && text.isNotBlank()
|
||||
}
|
||||
@@ -700,11 +700,12 @@ private fun ChatInputBar(
|
||||
onFileTap: () -> Unit = {},
|
||||
onRemoveAttachment: (Int) -> Unit = {},
|
||||
) {
|
||||
// Allow sending slash commands even while agent is typing
|
||||
// Allow sending while the agent is mid-turn or awaiting approval: the
|
||||
// gateway's prompt.submit busy-input policy queues it as the next turn
|
||||
// (tui_gateway/server.py:_handle_busy_submit), so the message is never
|
||||
// dropped. Slash commands were already allowed; regular prompts now are too.
|
||||
val isSlashCommand = inputText.startsWith("/")
|
||||
val canSend =
|
||||
(inputText.isNotBlank() || pendingAttachments.isNotEmpty()) &&
|
||||
isConnected && (!isAgentTyping || isSlashCommand)
|
||||
val canSend = ChatInputPolicy.canSend(inputText, pendingAttachments, isConnected)
|
||||
|
||||
// Attachment menu state
|
||||
var showAttachmentMenu by remember { mutableStateOf(false) }
|
||||
@@ -916,6 +917,8 @@ private fun ChatInputBar(
|
||||
Text(
|
||||
if (!isConnected) {
|
||||
stringResource(R.string.chat_input_placeholder_not_connected)
|
||||
} else if (ChatInputPolicy.showQueuePlaceholder(inputText, isAgentTyping)) {
|
||||
stringResource(R.string.chat_input_placeholder_queue)
|
||||
} else if (isAgentTyping) {
|
||||
stringResource(R.string.chat_input_placeholder_waiting)
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user