gridfleet-agent 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/CHANGELOG.md +14 -0
  2. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/PKG-INFO +1 -1
  3. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/appium_process.py +36 -1
  4. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/pyproject.toml +1 -1
  5. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/installer/test_plan.py +3 -3
  6. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_appium_process.py +54 -0
  7. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/uv.lock +1 -1
  8. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/.gitignore +0 -0
  9. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/README.md +0 -0
  10. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/__init__.py +0 -0
  11. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/capabilities.py +0 -0
  12. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/cli.py +0 -0
  13. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/config.py +0 -0
  14. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/driver_doctor.py +0 -0
  15. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/host_telemetry.py +0 -0
  16. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/installer/__init__.py +0 -0
  17. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/installer/install.py +0 -0
  18. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/installer/plan.py +2 -2
  19. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/installer/status.py +0 -0
  20. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/installer/uninstall.py +0 -0
  21. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/installer/update.py +0 -0
  22. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/main.py +0 -0
  23. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/observability.py +0 -0
  24. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/__init__.py +0 -0
  25. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/adapter_dispatch.py +0 -0
  26. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/adapter_loader.py +0 -0
  27. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/adapter_registry.py +0 -0
  28. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/adapter_types.py +0 -0
  29. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/adapter_utils.py +0 -0
  30. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/discovery.py +0 -0
  31. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/dispatch.py +0 -0
  32. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/host_identity.py +0 -0
  33. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/manifest.py +0 -0
  34. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/runtime.py +0 -0
  35. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/runtime_policy.py +0 -0
  36. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/runtime_registry.py +0 -0
  37. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/sidecar_supervisor.py +0 -0
  38. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/state.py +0 -0
  39. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/tarball_fetch.py +0 -0
  40. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/pack/version_catalog.py +0 -0
  41. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/plugin_manager.py +0 -0
  42. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/py.typed +0 -0
  43. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/registration.py +0 -0
  44. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/terminal_pty.py +0 -0
  45. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/terminal_ws.py +0 -0
  46. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/tool_paths.py +0 -0
  47. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/tool_utils.py +0 -0
  48. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/tools_manager.py +0 -0
  49. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/agent_app/version_guidance.py +0 -0
  50. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/__init__.py +0 -0
  51. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/installer/test_cli_install.py +0 -0
  52. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/installer/test_install.py +0 -0
  53. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/installer/test_status.py +0 -0
  54. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/installer/test_uninstall.py +0 -0
  55. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/installer/test_update.py +0 -0
  56. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/__init__.py +0 -0
  57. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/conftest.py +0 -0
  58. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_adapter_dispatch.py +0 -0
  59. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_adapter_loader.py +0 -0
  60. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_adapter_loader_cancel_path_leak.py +0 -0
  61. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_adapter_loader_concurrent_load.py +0 -0
  62. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_adapter_tarball_auth.py +0 -0
  63. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_adapter_utils.py +0 -0
  64. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_adapter_wiring.py +0 -0
  65. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_appium_process_integration.py +0 -0
  66. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_desired_manifest_features.py +0 -0
  67. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_feature_action_routes.py +0 -0
  68. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_host_identity.py +0 -0
  69. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_manifest_parser.py +0 -0
  70. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_pack_discovery_endpoint.py +0 -0
  71. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_pack_state.py +0 -0
  72. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_pack_state_client_auth.py +0 -0
  73. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_pack_state_sidecar_reconcile.py +0 -0
  74. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_runtime_github.py +0 -0
  75. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_runtime_isolated_failures.py +0 -0
  76. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_runtime_manager.py +0 -0
  77. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_runtime_plugins.py +0 -0
  78. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_runtime_policy.py +0 -0
  79. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_sidecar_supervisor.py +0 -0
  80. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_sidecar_supervisor_poll_stop_race.py +0 -0
  81. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_state_loop.py +0 -0
  82. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_state_loop_wired.py +0 -0
  83. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_tarball_fetch.py +0 -0
  84. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_tarball_fetch_concurrent_dedup.py +0 -0
  85. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/pack/test_version_catalog.py +0 -0
  86. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_agent_api.py +0 -0
  87. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_agent_api_more.py +0 -0
  88. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_appium_process_port_alloc_race.py +0 -0
  89. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_appium_process_restart_stop_race.py +0 -0
  90. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_appium_process_stop_start_lock_race.py +0 -0
  91. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_appium_process_watch_stop_race.py +0 -0
  92. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_capabilities.py +0 -0
  93. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_capabilities_more.py +0 -0
  94. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_cli.py +0 -0
  95. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_config.py +0 -0
  96. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_config_runtime_root.py +0 -0
  97. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_driver_doctor.py +0 -0
  98. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_host_telemetry.py +0 -0
  99. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_install_script.py +0 -0
  100. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_no_driver_imports.py +0 -0
  101. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_observability.py +0 -0
  102. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_package_metadata.py +0 -0
  103. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_plugin_manager.py +0 -0
  104. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_plugin_manager_more.py +0 -0
  105. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_registration.py +0 -0
  106. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_terminal_pty.py +0 -0
  107. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_terminal_ws.py +0 -0
  108. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_tools_and_utilities_more.py +0 -0
  109. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_tools_manager.py +0 -0
  110. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_tools_manager_extra.py +0 -0
  111. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/tests/test_version_guidance.py +0 -0
  112. {gridfleet_agent-0.2.2 → gridfleet_agent-0.2.4}/uninstall.sh +0 -0
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to the GridFleet host agent (`gridfleet-agent` on PyPI) are documented here.
4
4
 
5
+ ## [0.2.4](https://github.com/quidow/gridfleet/compare/gridfleet-agent-v0.2.3...gridfleet-agent-v0.2.4) (2026-05-03)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **agent:** trigger release for port conflict cleanup ([6a561ca](https://github.com/quidow/gridfleet/commit/6a561ca480c62b9abb2d5141fa98fc4e1a7696b6))
11
+
12
+ ## [0.2.3](https://github.com/quidow/gridfleet/compare/gridfleet-agent-v0.2.2...gridfleet-agent-v0.2.3) (2026-05-03)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **agent:** prioritize node in service path ([c1d2b72](https://github.com/quidow/gridfleet/commit/c1d2b728d5d01a4a0b76f53ca48be8740de17918))
18
+
5
19
  ## [0.2.2](https://github.com/quidow/gridfleet/compare/gridfleet-agent-v0.2.1...gridfleet-agent-v0.2.2) (2026-05-03)
6
20
 
7
21
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridfleet-agent
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: GridFleet agent — runs on each host
5
5
  Project-URL: Homepage, https://github.com/quidow/gridfleet
6
6
  Project-URL: Repository, https://github.com/quidow/gridfleet
@@ -88,6 +88,10 @@ class RuntimeNotInstalledError(RuntimeError):
88
88
  """Raised when no runtime is installed for the requested pack."""
89
89
 
90
90
 
91
+ class AppiumPortOccupiedError(RuntimeError):
92
+ """Raised when a managed Appium port is now owned by another listener."""
93
+
94
+
91
95
  def resolve_appium_invocation_for_pack(
92
96
  pack_id: str,
93
97
  registry: RuntimeRegistry | None,
@@ -617,6 +621,24 @@ class AppiumProcessManager:
617
621
  )
618
622
  try:
619
623
  restarted = await self._restart_from_launch_spec(port)
624
+ except AppiumPortOccupiedError:
625
+ self._record_restart_event(
626
+ process="appium",
627
+ kind="port_conflict",
628
+ port=port,
629
+ pid=info.pid if info is not None else None,
630
+ attempt=attempt_number,
631
+ delay_sec=None,
632
+ exit_code=last_exit_code,
633
+ will_retry=False,
634
+ )
635
+ logger.error(
636
+ "Appium auto-restart stopped for connection_target=%s port=%d because the port is occupied",
637
+ info.connection_target if info is not None else "unknown",
638
+ port,
639
+ )
640
+ await self._drop_failed_managed_port(port)
641
+ return
620
642
  except Exception:
621
643
  self._advance_restart_backoff(self._appium_restart_backoff_steps, port)
622
644
  logger.exception("Appium auto-restart failed for port %d on attempt %d", port, attempt_number)
@@ -859,7 +881,7 @@ class AppiumProcessManager:
859
881
  appium_cmd.extend(["--allow-insecure", ",".join(spec.insecure_features)])
860
882
 
861
883
  if await self._can_connect_to_appium(spec.port):
862
- raise RuntimeError(
884
+ raise AppiumPortOccupiedError(
863
885
  f"Port {spec.port} is already in use by another Appium listener; "
864
886
  "stop the existing process before starting a new managed node"
865
887
  )
@@ -1128,6 +1150,19 @@ class AppiumProcessManager:
1128
1150
  with contextlib.suppress(OSError):
1129
1151
  os.unlink(toml_path)
1130
1152
 
1153
+ async def _drop_failed_managed_port(self, port: int) -> None:
1154
+ """Forget stale ownership for a crashed Appium process without touching an unmanaged listener."""
1155
+ self._cancel_task(self._appium_restart_tasks, port)
1156
+ self._cancel_task(self._appium_watch_tasks, port)
1157
+ await self._stop_grid_node_process(port)
1158
+ self._appium_procs.pop(port, None)
1159
+ self._info.pop(port, None)
1160
+ self._launch_specs.pop(port, None)
1161
+ self._appium_restart_attempts.pop(port, None)
1162
+ self._grid_node_restart_attempts.pop(port, None)
1163
+ self._appium_restart_backoff_steps.pop(port, None)
1164
+ self._grid_node_restart_backoff_steps.pop(port, None)
1165
+
1131
1166
  async def stop(self, port: int) -> None:
1132
1167
  async with self._start_lock:
1133
1168
  self._intentional_stop_ports.add(port)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gridfleet-agent"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "GridFleet agent — runs on each host"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -54,7 +54,7 @@ def test_render_config_env_includes_detected_paths_and_optional_auth() -> None:
54
54
  assert "AGENT_MANAGER_AUTH_PASSWORD=secret" in rendered
55
55
  assert "AGENT_ENABLE_WEB_TERMINAL=true" in rendered
56
56
  assert "AGENT_TERMINAL_TOKEN=terminal-token" in rendered
57
- assert "PATH=/usr/bin:/opt/node/bin:" in rendered
57
+ assert "PATH=/opt/node/bin:/usr/bin:" in rendered
58
58
 
59
59
 
60
60
  def test_load_installed_config_reads_persisted_agent_env(tmp_path: Path) -> None:
@@ -175,10 +175,10 @@ def test_dry_run_output_names_generated_artifacts_and_warnings() -> None:
175
175
  assert "ExecStart=/opt/gridfleet-agent/venv/bin/gridfleet-agent serve" in output
176
176
 
177
177
 
178
- def test_build_service_path_prepends_discovered_tool_dirs() -> None:
178
+ def test_build_service_path_prioritizes_node_before_system_dirs() -> None:
179
179
  discovery = ToolDiscovery(java_bin="/usr/lib/jvm/bin/java", node_bin_dir="/opt/node/bin", android_home="/opt/sdk")
180
180
 
181
- assert build_service_path(discovery).startswith("/usr/lib/jvm/bin:/opt/node/bin:/opt/sdk/platform-tools:")
181
+ assert build_service_path(discovery).startswith("/opt/node/bin:/usr/lib/jvm/bin:/opt/sdk/platform-tools:")
182
182
 
183
183
 
184
184
  def test_find_node_bin_dir_prefers_home_nvm_over_system_node(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
@@ -705,6 +705,60 @@ async def test_auto_restart_cap_stops_retrying_after_threshold() -> None:
705
705
  assert snapshot["recent_restart_events"][0]["will_retry"] is False
706
706
 
707
707
 
708
+ async def test_auto_restart_drops_managed_state_when_port_is_taken_by_unmanaged_listener() -> None:
709
+ manager = AppiumProcessManager()
710
+ old_appium_proc = FakeProcess(pid=1111, returncode=1)
711
+ old_grid_proc = FakeProcess(pid=2222)
712
+ manager._appium_procs[4723] = cast("asyncio.subprocess.Process", old_appium_proc)
713
+ manager._node_procs[4723] = cast("asyncio.subprocess.Process", old_grid_proc)
714
+ manager._launch_specs[4723] = AppiumLaunchSpec(
715
+ connection_target="device-conflict",
716
+ port=4723,
717
+ plugins=None,
718
+ extra_caps=None,
719
+ stereotype_caps=None,
720
+ session_override=True,
721
+ device_type=None,
722
+ ip_address=None,
723
+ manage_grid_node=True,
724
+ pack_id="appium-uiautomator2",
725
+ platform_id="android_mobile",
726
+ )
727
+ manager._info[4723] = AppiumProcessInfo(
728
+ port=4723,
729
+ pid=1111,
730
+ connection_target="device-conflict",
731
+ platform_id="android_mobile",
732
+ )
733
+
734
+ real_sleep = asyncio.sleep
735
+
736
+ async def fake_sleep(_delay: float) -> None:
737
+ await real_sleep(0)
738
+
739
+ with (
740
+ patch("agent_app.appium_process.resolve_appium_invocation_for_pack", return_value=_STUB_INVOCATION),
741
+ patch("agent_app.appium_process._build_env", return_value={"PATH": "/usr/bin"}),
742
+ patch.object(manager, "_can_connect_to_appium", new_callable=AsyncMock, return_value=True),
743
+ patch("agent_app.appium_process.asyncio.create_subprocess_exec", new_callable=AsyncMock) as create_proc,
744
+ patch("agent_app.appium_process.asyncio.sleep", side_effect=fake_sleep),
745
+ ):
746
+ await manager._auto_restart_appium(4723, exit_code=1)
747
+
748
+ create_proc.assert_not_awaited()
749
+ assert manager.list_running() == []
750
+ assert 4723 not in manager._launch_specs
751
+ assert 4723 not in manager._info
752
+ assert 4723 not in manager._node_procs
753
+ assert old_grid_proc.sent_signals
754
+ snapshot = manager.process_snapshot()
755
+ assert [event["kind"] for event in snapshot["recent_restart_events"]] == [
756
+ "crash_detected",
757
+ "port_conflict",
758
+ ]
759
+ assert snapshot["recent_restart_events"][-1]["process"] == "appium"
760
+
761
+
708
762
  async def test_successful_restart_resets_backoff_step_for_next_crash() -> None:
709
763
  manager = AppiumProcessManager()
710
764
  first_proc = FakeProcess(pid=1001)
@@ -169,7 +169,7 @@ wheels = [
169
169
 
170
170
  [[package]]
171
171
  name = "gridfleet-agent"
172
- version = "0.2.2"
172
+ version = "0.2.4"
173
173
  source = { editable = "." }
174
174
  dependencies = [
175
175
  { name = "fastapi" },
@@ -221,10 +221,10 @@ def discover_tools(
221
221
 
222
222
  def build_service_path(discovery: ToolDiscovery) -> str:
223
223
  prefixes: list[str] = []
224
- if discovery.java_bin:
225
- prefixes.append(str(Path(discovery.java_bin).parent))
226
224
  if discovery.node_bin_dir:
227
225
  prefixes.append(discovery.node_bin_dir)
226
+ if discovery.java_bin:
227
+ prefixes.append(str(Path(discovery.java_bin).parent))
228
228
  if discovery.android_home:
229
229
  prefixes.append(f"{discovery.android_home}/platform-tools")
230
230
  return ":".join([*prefixes, DEFAULT_SERVICE_PATH])