Previously mobile dropped the gateway's sudo.request and secret.request events, so any agent turn needing a sudo password or a secret value hung forever with no UI. Now: - EventParser maps sudo.request / secret.request -> WsEvent.SudoRequest / SecretRequest - ChatViewModel surfaces a secure dialog (SudoPromptUi / SecretPromptUi) and replies via sudo.respond / secret.respond with session_id + request_id + payload - ChatWsEventReducer explicitly branches the new events (no-op, VM-owned) - ChatScreen renders password-masked SudoPromptDialog / SecretPromptDialog - 6 unit tests cover prompt-state, RPC params, and prompt-clearing Mirrors desktop/web contract (apps/shared GatewayEventName + tui_gateway). Derives from audit of /opt/hermes-agent desktop app.
This commit is contained in:
8 files changed
+400
No files matched your search
@@ -1035,6 +1035,130 @@ class ChatViewModelTest {
|
||||
assertNull(msgAfter!!.approvalInfo)
|
||||
}
|
||||
|
||||
// ── Sudo / secret prompt flow (issue #524) ───────────────────────────
|
||||
|
||||
@Test
|
||||
fun testSudoRequest_setsPromptState() =
|
||||
runTest {
|
||||
val (viewModel, _) = createViewModelWithSession()
|
||||
advanceUntilIdle()
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.SudoRequest(requestId = "sudo-1", sessionId = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
val prompt = viewModel.uiState.value.sudoPrompt
|
||||
assertNotNull(prompt)
|
||||
assertEquals("sudo-1", prompt?.requestId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondToSudo_sendsRpc() =
|
||||
runTest {
|
||||
val (viewModel, sessionId) = createViewModelWithSession()
|
||||
advanceUntilIdle()
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.SudoRequest(requestId = "sudo-1", sessionId = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
viewModel.respondToSudo("hunter2")
|
||||
advanceUntilIdle()
|
||||
|
||||
verify {
|
||||
HermesWsClient.send(
|
||||
WsMethods.SUDO_RESPOND,
|
||||
withArg { params ->
|
||||
assertEquals(sessionId, params["session_id"])
|
||||
assertEquals("hunter2", params["password"])
|
||||
assertEquals("sudo-1", params["request_id"])
|
||||
},
|
||||
any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondToSudo_clearsPrompt() =
|
||||
runTest {
|
||||
val (viewModel, _) = createViewModelWithSession()
|
||||
advanceUntilIdle()
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.SudoRequest(requestId = "sudo-1", sessionId = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
assertNotNull(viewModel.uiState.value.sudoPrompt)
|
||||
|
||||
viewModel.respondToSudo("hunter2")
|
||||
advanceUntilIdle()
|
||||
|
||||
assertNull(viewModel.uiState.value.sudoPrompt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSecretRequest_setsPromptState() =
|
||||
runTest {
|
||||
val (viewModel, _) = createViewModelWithSession()
|
||||
advanceUntilIdle()
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.SecretRequest(requestId = "secret-1", sessionId = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
val prompt = viewModel.uiState.value.secretPrompt
|
||||
assertNotNull(prompt)
|
||||
assertEquals("secret-1", prompt?.requestId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondToSecret_sendsRpc() =
|
||||
runTest {
|
||||
val (viewModel, sessionId) = createViewModelWithSession()
|
||||
advanceUntilIdle()
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.SecretRequest(requestId = "secret-1", sessionId = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
viewModel.respondToSecret("super-secret-token")
|
||||
advanceUntilIdle()
|
||||
|
||||
verify {
|
||||
HermesWsClient.send(
|
||||
WsMethods.SECRET_RESPOND,
|
||||
withArg { params ->
|
||||
assertEquals(sessionId, params["session_id"])
|
||||
assertEquals("super-secret-token", params["value"])
|
||||
assertEquals("secret-1", params["request_id"])
|
||||
},
|
||||
any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondToSecret_clearsPrompt() =
|
||||
runTest {
|
||||
val (viewModel, _) = createViewModelWithSession()
|
||||
advanceUntilIdle()
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.SecretRequest(requestId = "secret-1", sessionId = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
assertNotNull(viewModel.uiState.value.secretPrompt)
|
||||
|
||||
viewModel.respondToSecret("super-secret-token")
|
||||
advanceUntilIdle()
|
||||
|
||||
assertNull(viewModel.uiState.value.secretPrompt)
|
||||
}
|
||||
|
||||
// ── Settings ─────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user