Add setTimer action to androidMobile agent - #2783
Open
jebrans wants to merge 2 commits into
Open
Conversation
The Android sample rendered every appendDisplay chunk into one growing
bubble, so a request like "create grocery list" showed the dispatcher's
routing annotation ("routed to list - recent topic") glued in front of
the answer, while the Electron shell shows only "Created list: grocery".
Display pipeline
- Add AgentDisplayThread, a Kotlin port of the shell's
AgentMessageContainer.setMessage accumulation rules: content for a
request is kept as ordered chunks, honouring DisplayAppendMode
(inline / block / temporary / step). A trailing "temporary" chunk is
flushed when the next update arrives, and "step" seals the bubble so
the next update starts a new one - matching chatPanel.ts.
- Message now carries a list of MessageSegment, each with its own
DisplayMessageKind, so info/status annotations render de-emphasised
instead of being concatenated into the answer (the shell's
chat-message-kind-* styling).
- Drop AgentMessageKind "toast"/"inline" messages, which the shell
renders outside the chat bubble.
- Clear the per-request display state on disconnect as well as connect.
set-timer client action
- Add TimerActionParser for the takeAction("set-timer", ...) payload
emitted by the androidMobile setTimer action. The duration is
re-validated client side (floor, positive, <= 86400s per the
documented AlarmClock.EXTRA_LENGTH range) because takeAction is
fire-and-forget and carries no schema guarantee over the wire.
Out-of-range values are rejected rather than clamped so a
mistranslated duration surfaces in the log.
- Launch ACTION_SET_TIMER with EXTRA_SKIP_UI = true so the countdown
starts in the background and a chat request never pulls the user out
of the conversation; a toast confirms. Alarm and timer now share one
intent-launch helper.
- Declare the SET_TIMER <queries> entry required by Android 11+ package
visibility. No new permission is needed: the existing
com.android.alarm.permission.SET_ALARM covers both.
- Unknown client actions stay logged-and-ignored, so this is compatible
with servers that do not emit set-timer.
Server-side setTimer support is in PR microsoft#2780; until that lands the client
path is simply never exercised.
Tests: 44 unit tests (14 new for AgentDisplayThread, 9 for
TimerActionParser); assembleDebug and lintDebug clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Functional / security fixes from a pre-merge review of the display and
set-timer work:
- launchClockIntent claimed success unconditionally. Android 10+ refuses
background activity starts *silently* - it throws neither
ActivityNotFoundException nor SecurityException - so a set-timer or
set-alarm request arriving while the activity was stopped showed a
"Timer set" toast with no timer. Because EXTRA_SKIP_UI hides the clock
app's own confirmation, that toast is the user's only feedback. Now the
intent target and the lifecycle state are both checked up front and the
failure is reported honestly.
- originalRequest was an unbounded network-controlled string copied into
AlarmClock.EXTRA_MESSAGE. A large enough value pushes the startActivity
binder transaction past its ~1 MB limit and raises
TransactionTooLargeException, a RuntimeException that is not caught -
a remote input crashing the process. Cap it at 256 characters.
- Read originalRequest with opt(), not optString(). Android's org.json
renders a JSON null as the literal string "null", so
{"originalRequest": null} labelled the timer "null" on device. The unit
tests use JSON-java, which returns "" instead, so this never showed up
in test.
- AgentDisplayThread.setMessage flushed the temporary tail before
checking for empty content. DisplayContentParser deliberately renders
suppressed reasoning traces as empty text, so such an update emptied
the thread; WebSocketManager then removed the bubble and re-appended it
at the end of the transcript on the next update, losing its
chronological position. Empty content is now a no-op for every mode
except REPLACE.
- The raw-text fallback for unparseable frames accumulated into the
single shared no-requestId thread, which nothing ever completes, so the
bubble grew without bound and showed "Responding..." forever. Emit it
as a sealed step instead.
- Finalize, rather than discard, open display threads on socket close,
failure and disconnect, so a request in flight when the connection
drops does not strand a bubble in the "Responding..." state.
Tests: 56 unit tests (up from 44), covering empty block/replace content,
step after temporary, inline after a temporary flush, newline-only
content, mixed markdown formats, JSON-null and oversized
originalRequest, and Long durations. assembleDebug and lintDebug clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add setTimer action to androidMobile agent