Skip to content

fix(agent): retain reconnect intent across inventory events - #1807

Open
hornej wants to merge 11 commits into
AprilNEA:masterfrom
hornej:josh-reconnect-event-reliability
Open

hornej wants to merge 11 commits into
AprilNEA:masterfrom
hornej:josh-reconnect-event-reliability

Conversation

@hornej

@hornej hornej commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A reconnect can reset keyboard settings while its route still appears unchanged. A queued battery/settings event can hide the reconnect, and a failed or unpublished inventory scan can lose its recovery request. Retain the strongest request until a snapshot reaches the agent.

Changes

  • openlogi-device: give the pending event queue one ReconcileQueueState enum and transition owner, with wake tokens derived only from Idle-to-Pending transitions. Retain receiver reconnects over wireless wakes, and both over all eleven ordinary battery/settings event sources. Preserve the independent latest-value backlight stream and lock-key queue.
  • openlogi-agent-core: retain recovery through failed, discarded and repair scans. Keep the short device-reconnect confirmation budget and full node/resume budget. Treat dead-channel notifications as reconnects, including failed scans and either ordering with system resume. A replacement channel keeps its preceding DeviceWake notification and carries recovery intent in its published snapshot.
  • openlogi-agent: route worker publications through Orchestrator::apply_inventory_event, the same production consumer exercised by the regression. Apply retained intent before refresh and schedule bounded confirmation work there. A structural guard rejects the previous agent-side implementation.
  • Desktop tests: identify the canonical keyboard and publish its measured controls so app-profile deep links are independent of cached device-render assets. Verify the keyboard Actions Ring profile and retain the camera negative case. Preserve the callout-count assertion when iterating layout cards.
  • Rust 1.100 CI compatibility: replace mapped Option/Result defaults with equivalent map_or_else calls in application and xtask code. Remove three obsolete async-trait lint allowances only after CI's Linux, macOS and Windows sweeps all reported them silent. Keep the Rust 1.99 compilation floor and existing default/error behavior.

Merged upstream 9ca7ef0c into the existing branch. Accepted capture recovery from #1746 and receiver-selection/pairing fixes from #1614 remain upstream. This is the reconnect successor to closed #939; #1806 supplies per-keyboard native layout policy. Configuration and IPC definitions remain upstream's.

Testing

Review follow-up head: f06d7d5e. Windows x86_64, Rust 1.100.0, warnings denied:

  • cargo xtask ci — 10 passed, 0 failed, 5 skipped. Formatting, typos, ast-grep, publish closure, full-workspace/all-target Clippy, non-GUI private-item rustdoc, Windows tests, dependency policy and portable Wasm checks passed.
  • Windows workspace tests — 3,728 passed, four existing tests/doctests ignored.
  • cargo test -p openlogi-desktop — 844 passed.
  • cargo test -p openlogi-agent-core receiver_reconnect_survives_failed_scan_to_refresh — passed. It enters at the decoded-event boundary, exercises the real capacity-one queue and settle drain, injects an enumeration error at the worker completion boundary, then runs successful replay enumeration and the actual agent consumer with unchanged route/online state. Both a reconnect queued behind battery and one drained during settling recover; a later battery-only event does not replay settings.
  • Counter-proof: removing settle_trigger's drained-event observe_trigger call fails the retained-reason assertion. Removing the consumer's reapply_volatile match fails the unchanged-device recovery assertion. Both mutations were run separately, failed by assertion, and restored before the full gate.
  • cargo test -p openlogi-device queue_transitions_refuse_idle_takes_and_duplicate_wakes — passed. Idle takes are refused; upgrades emit no second token; pending data drains before sender closure.
  • The new ast-grep guard rejects the previous implementation in the agent crate; the current owner, consumer and unrelated code pass the full scan. git diff --check and staged checks passed.

Local skips: shellcheck/shfmt, Linux/macOS MSRV, Linux/macOS tests and macOS Clippy. Current-head GitHub validation passed: 25 successful checks, including Greptile, and five skipped installer/package jobs. See CI results and three-platform lint sweep. Rust 1.99 remains the compilation floor; no toolchain or wire/config format changed in this follow-up. Installer packaging and signing were not run locally.

Regression counter-proof for the recovery review

Both mutations below were actually executed separately with cargo +1.100.0 test -p openlogi-agent-core receiver_reconnect_survives -- --nocapture; each compiled successfully and then reported 0 passed, 1 failed. This is observed failure evidence, not a proposed mutation:

  1. Remove the drained-event self.state.observe_trigger(ReconcileTrigger::HidEvent(drained)) call in watchers/inventory.rs (settle_trigger, current line 547). The test fails because the recovered snapshot does not carry Some(ReapplyReason::Volatile) while preserving the original inventory.
  2. Replace match reapply_volatile in orchestrator/inventory_events.rs (current line 25) with let _ = reapply_volatile;, removing the now-unused ReapplyReason import so this remains a runtime test. It fails with “unchanged online devices still need recovery”.

Both mutations were restored. The passing test calls the same Orchestrator::apply_inventory_event used by Running::apply_inventory (lifecycle.rs:518), and the final full gate and current-head CI passed. The older state-only test is now accurately named reconnect_reason_outlives_ordinary_battery_triggers.

This exact head has not been runtime-tested on hardware. Keep the agent running, reconnect a keyboard node and resume the host, then verify saved native layout and bindings after recovery. Physical KVM, Bluetooth/Bolt panel handoffs, sleep/wake and immediate-first-press behavior remain unverified for this head.

Keep reconnect provenance through coalesced events and failed or unpublished scans. Preserve full receiver and resume recovery budgets while keeping wireless device wakes on the short budget; use upstream OS-layout enforcement unchanged.
@davidbudnick davidbudnick added area: hid HID device discovery, permissions, reads, or writes platform: all Cross-platform issue type: bug Something is broken or behaves incorrectly labels Oct 14, 2026
@hornej
hornej marked this pull request as ready for review November 13, 2026 17:25
@greptile-apps

greptile-apps Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The runtime changes reach the agent's device-inventory recovery path.

The code changes appear sound, but the earlier request to state the regression test's counter-proof remains incomplete.

Findings

  1. P1 Recovery tests bypass real wiring ▶

Summary

The PR preserves reconnect requests across event bursts and failed or discarded inventory scans.

  • The agent keeps reconnect recovery requests until a scan reaches it.
  • Keyboard app-profile links are tested with measured device controls.
  • Keyboard layout tests check every callout card.
  • Rust code keeps the same defaults without obsolete lint allowances.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Battery, settings, or reconnect event] --> B[Queue keeps strongest request]
  B --> C[Worker settles and scans]
  C --> D{Snapshot published?}
  D -->|No| E[Keep recovery intent]
  E --> C
  D -->|Yes| F[Snapshot carries recovery intent]
  F --> G[Shared consumer applies intent before refresh]
  G --> H[Request bounded confirmation when needed]
Loading

Reviews (2) · Last reviewed commit: "fix(agent): centralize reconnect recover..." · Reviewed by Greptile

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: hid HID device discovery, permissions, reads, or writes platform: all Cross-platform issue type: bug Something is broken or behaves incorrectly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants