3 Commits
Author SHA1 Message Date
Bailey Dixon 6a39e8dc1a feat(android): add granular bridge capability grants 2026-08-19 19:43:04 -04:00
Bailey DixonandClaude Opus 4.7 fd77da043b feat(bridge): v0.5.0 agent-aware phone status, auto-return, activity log
Three cross-layer additions that close longstanding visibility gaps
between the phone, the relay, and the host-side agent:

  Agent awareness — unattended/screen/credential-lock state
  -----------------------------------------------------------
  PhoneSnapshot gains unattendedEnabled, credentialLockDetected, and
  screenOn fields. PhoneStatusPromptBuilder.buildBridgeLine() now
  appends explicit guidance — e.g. "Unattended access: off — commands
  only land when the screen is already on" or "Unattended access: on,
  but the device has a credential lock — commands will return
  keyguard_blocked and fail."

  BridgeStatusReporter.emitTick() emits a parallel `unattended` group
  in the bridge.status WSS envelope so the host-side `/bridge/status`
  cache (and the `android_phone_status` tool that reads it) sees the
  same state for non-phone frontends like Discord. Push triggers fire
  on toggle flip via ConnectionViewModel so the host cache updates in
  ~1s instead of waiting up to 30s for the periodic tick.

  android_phone_status tool description updated so the LLM proactively
  checks unattended.* fields and warns the user when commands will
  hit keyguard_blocked.

  Auto-return to Hermes-Relay
  ---------------------------
  android_return_to_hermes is an LLM-called tool that the agent
  routinely forgets, leaving the user stranded on Starbucks/Chrome/etc
  after the run. Two safety nets:

    1. Tightened tool descriptions (REQUIRED FINAL STEP, MANDATORY
       CLEANUP framing in android_open_app + android_return_to_hermes).
    2. New BridgeRunTracker singleton — coordinates two completion
       signals: Chat-tab SSE run.completed (fast, phone-only) and a
       12s bridge-idle timer (universal, works for Discord/CLI/web).
       Whichever fires first dispatches a local /return_to_hermes via
       handleLocalCommand. markReturnedToHermes() prevents double-fires
       when the LLM does call return explicitly.

  BridgeCommandHandler tracks foreground-shifting paths (/open_app,
  /send_intent) at respond() and arms/resets the idle timer accordingly.
  Reset hooks at both dispatch start and respond finish so slow-
  executing commands (screenshots, big tree reads) don't eat the idle
  budget.

  Bridge activity log wiring
  --------------------------
  The Activity Log card on the Bridge tab was scaffolded in Phase 3 but
  never wired — recordActivity() existed, the UI rendered the flow, but
  no code ever called it. BridgeCommandHandler now emits a
  BridgeActivityEntry per dispatched command (Success/Failed/Blocked)
  via a new onActivity callback. ConnectionViewModel pipes it through
  to BridgePreferencesRepository.appendEntry. High-frequency polls
  (/ping, /events, /current_app, /screen_hash) suppressed so the log
  shows user-meaningful activity, not noise. Per-route summarizers
  produce natural-looking entries: "tap (540, 1200)", "open_app
  com.starbucks.mobilecard", etc. resultText surfaces error strings
  on Failed/Blocked so users can see WHY without digging through logs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 21:42:23 -04:00
Bailey DixonandClaude Opus 4.6 990870cee3 feat(phase3-status): dynamic phone-status prompt + android_phone_status tool + bridge UI hardening
Three-layer feature so the agent finally knows what the phone can do, plus
follow-up fixes discovered while smoke-testing the merged Phase 3 build.

Layer 1 — phone-side dynamic system prompt (replaces the static one-liner):
- New PhoneStatusPromptBuilder.kt builds a transparent block from real
  bridge/permission state, capped under 100 words, returns null when
  everything is off (no empty system messages)
- 4 new sub-toggles in ConnectionViewModel: bridge state, current app,
  battery, safety status. Privacy-sensitive ones (current_app, battery)
  default OFF
- ChatSettingsScreen gains a live preview Card showing exactly what
  will be sent on the next message
- ChatViewModel deletes APP_CONTEXT_PROMPT, calls the builder via a
  guarded-reflection capturePhoneSnapshot() helper

Layer 2 — relay backend (GET /bridge/status, loopback only):
- BridgeStatusReporter expanded to push the full nested device/bridge/safety
  contract every 30s; new pushNow() method called on master toggle flips
- BridgeHandler caches latest_status + last_seen_at
- New handle_bridge_status route mirrors /pairing/register loopback gate
- 7 new stdlib unittest tests in test_bridge_status.py — all green

Layer 2 — symmetric trio (mirrors the pair feature):
- New plugin/tools/android_phone_status.py — Hermes tool, stdlib only
- New plugin/status.py + hermes-status shim — operator CLI with --json/--port,
  three exit codes (0/1/2 for connected/relay-down/no-phone)
- New skills/devops/hermes-relay-status/SKILL.md — slash command
- install.sh + uninstall.sh updated for the second shim
- 19 new stdlib unittest tests — all green

Bridge UI hardening (master gate, MediaProjection, labels):
- HermesAccessibilityService now feeds cachedMasterEnabled itself via a
  service-scoped DataStore observer. The previous push-from-outside
  pattern was never wired and the cache stayed false forever, 403'ing
  every command except /ping and /current_app
- MainActivity registers an ActivityResultLauncher for MediaProjection;
  new ScreenCaptureRequester process-singleton bridges non-Activity
  callers (BridgeViewModel.requestScreenCapture()). Bridge tab's Screen
  Capture row is now tappable instead of inert
- BridgeViewModel.testNotificationListener() + an onTestNotificationListener
  lambda through BridgePermissionChecklist for parity with the other rows
- Sideload flavor strings: app_name → "Hermes Dev", a11y_service_label →
  "Hermes-Bridge Dev", notification_companion_label → "Hermes Dev …".
  googlePlay's a11y_service_label flipped to "Hermes-Bridge" (with hyphen)
  for consistency. Disambiguates side-by-side installs in launcher /
  recents / Settings → Apps
- BridgeForegroundService: Intent.flags = … → addFlags(…) (the property
  setter form fails because Intent.setFlags returns Intent, not void)
- BridgeViewModel: removed deprecated StateFlow.distinctUntilChanged()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:43:58 -04:00