Clean up Android question handoff lifecycle

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Iliyan Malchev
2026-04-02 08:24:59 -07:00
parent 2daa343711
commit b20afa97b4
3 changed files with 47 additions and 23 deletions

View File

@@ -205,7 +205,9 @@ class CodexAgentService : AgentService() {
val manager = agentManager ?: return
val events = manager.getSessionEvents(session.sessionId)
val question = findLatestQuestion(events) ?: return
updateQuestionNotification(session, question)
if (!isBridgeQuestion(question)) {
AgentQuestionNotifier.cancel(this, session.sessionId)
}
maybeAutoAnswerGenieQuestion(session, question)
}
@@ -234,26 +236,6 @@ class CodexAgentService : AgentService() {
}
}
private fun updateQuestionNotification(session: AgentSessionInfo, question: String) {
if (question.isBlank()) {
AgentQuestionNotifier.cancel(this, session.sessionId)
return
}
if (isBridgeQuestion(question)) {
AgentQuestionNotifier.cancel(this, session.sessionId)
return
}
if (pendingGenieQuestions.contains(genieQuestionKey(session.sessionId, question))) {
return
}
AgentQuestionNotifier.showQuestion(
context = this,
sessionId = session.sessionId,
targetPackage = session.targetPackage,
question = question,
)
}
private fun findLatestQuestion(events: List<AgentSessionEvent>): String? {
return events.lastOrNull { event ->
event.type == AgentSessionEvent.TYPE_QUESTION &&

View File

@@ -6,6 +6,7 @@ import android.app.agent.GenieService
import android.content.Context
import android.util.Log
import com.openai.codex.bridge.DesktopSessionBootstrap
import com.openai.codex.bridge.DetachedTargetCompat
import com.openai.codex.bridge.FrameworkEventBridge
import com.openai.codex.bridge.HostedCodexConfig
import com.openai.codex.bridge.SessionExecutionSettings
@@ -628,6 +629,13 @@ class CodexAppServerHost(
val questions = params.optJSONArray("questions") ?: JSONArray()
val renderedQuestion = renderAgentQuestion(questions)
Log.i(TAG, "Requesting Agent input for ${request.sessionId}: $renderedQuestion")
if (request.isDetachedModeAllowed) {
runCatching {
showDetachedTargetForUserQuestion()
}.onFailure { err ->
recordNonFatalObserverFailure("request_user_input/showDetachedTarget", err)
}
}
publishFrameworkQuestion(renderedQuestion)
updateFrameworkState(AgentSessionInfo.STATE_WAITING_FOR_USER)
val answer = control.waitForUserResponse()
@@ -999,10 +1007,34 @@ class CodexAppServerHost(
JSONObject()
.put("code", code)
.put("message", message),
),
),
)
}
private fun showDetachedTargetForUserQuestion() {
var result = DetachedTargetCompat.showDetachedTarget(
callback = callback,
sessionId = request.sessionId,
)
if (result.needsRecovery()) {
publishFrameworkTrace(result.summary("show for question"))
val recovery = DetachedTargetCompat.ensureDetachedTargetHidden(
callback = callback,
sessionId = request.sessionId,
)
publishFrameworkTrace(recovery.summary("ensure hidden for question"))
if (recovery.isOk()) {
result = DetachedTargetCompat.showDetachedTarget(
callback = callback,
sessionId = request.sessionId,
)
} else {
return
}
}
publishFrameworkTrace(result.summary("show for question"))
}
private fun sendMessage(message: JSONObject) {
synchronized(writerLock) {
writer.write(message.toString())

View File

@@ -68,6 +68,10 @@ The current repo now contains these implementation slices:
`request_user_input` in Default mode so ambiguous delegated tasks can pause in
real framework `WAITING_FOR_USER` state instead of guessing or emitting a
plain-text question as a normal completion.
- In detached mode, Genie question handoff now recovers a missing target task by
calling `ensureDetachedTargetHidden` before retrying `showDetachedTarget`, so a
`WAITING_FOR_USER` session keeps launcher icon/badge behavior aligned with the
still-live framework session.
- The Agent now auto-answers only internal bridge questions. User-facing Genie
questions remain user-facing and are surfaced through the normal framework
question flow, notifications, and desktop/UI answer surfaces.
@@ -218,7 +222,11 @@ the Android Agent/Genie flow.
- detached target ensure-hidden/show/hide/attach/close
- typed detached frame capture with runtime-aware recovery
- `request_user_input` bridged from hosted Codex back into AgentSDK questions
- Agent-owned question notifications for Genie questions that need user input
- Framework-owned question notifications for Genie questions that need user
input; the Agent app suppresses its former duplicate notification mirror
- Detached-mode Genie sessions now request `showDetachedTarget` before entering
`WAITING_FOR_USER`, so HOME can keep a visible live icon while the user
answers the question
- Agent-mediated answers only for internal bridge questions that the user
should never see as product-facing prompts
- Agent planning can now use `request_user_input` to ask the user clarifying
@@ -248,6 +256,8 @@ the Android Agent/Genie flow.
- hosted Agent bridge for framework session APIs
- `android/app/src/main/java/com/openai/codex/agent/MainActivity.kt`
- Agent session UI, Agent clarification dialogs, and Agent-native auth controls
- `android/app/src/main/java/com/openai/codex/agent/SessionDetailActivity.kt`
- HOME/AGENT session inspection and question answering UI
- `android/app/src/main/java/com/openai/codex/agent/AgentUserInputPrompter.kt`
- Android dialog bridge for hosted Agent `request_user_input` calls
- `android/genie/src/main/java/com/openai/codex/genie/CodexGenieService.kt`