devops-bot-sdk 1.6.75__tar.gz → 1.6.76__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 (237) hide show
  1. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
  4. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/service.py +121 -6
  6. devops_bot_sdk-1.6.76/tests/test_windows_panel_task.py +135 -0
  7. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_windows_task_paths.py +10 -1
  8. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/README.md +0 -0
  9. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  10. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  11. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/requires.txt +0 -0
  12. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  13. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/pyproject.toml +0 -0
  14. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/__init__.py +0 -0
  15. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/classifier.py +0 -0
  16. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/__init__.py +0 -0
  17. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/common.py +0 -0
  18. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/linux_auditd.py +0 -0
  19. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/macos_eslogger.py +0 -0
  20. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/windows_eventlog.py +0 -0
  21. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/models.py +0 -0
  22. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/os_setup.py +0 -0
  23. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/recorder.py +0 -0
  24. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/redact.py +0 -0
  25. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/settings.py +0 -0
  26. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/shipper.py +0 -0
  27. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/activity/spool.py +0 -0
  28. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agentd.py +0 -0
  29. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/__init__.py +0 -0
  30. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/activity.py +0 -0
  31. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/builder.py +0 -0
  32. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/chat.py +0 -0
  33. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/connectors.py +0 -0
  34. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/engines/__init__.py +0 -0
  35. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/engines/claude_code.py +0 -0
  36. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/engines/http.py +0 -0
  37. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/engines/mcp.py +0 -0
  38. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/engines/n8n.py +0 -0
  39. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/engines/shell.py +0 -0
  40. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/integrations.py +0 -0
  41. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/inventory.py +0 -0
  42. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/library/__init__.py +0 -0
  43. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/manifest.py +0 -0
  44. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_studio.py +0 -0
  45. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_template.py +0 -0
  46. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_vars.py +0 -0
  47. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/run_log.py +0 -0
  48. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/schedules.py +0 -0
  49. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/secrets.py +0 -0
  50. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/ticket_run.py +0 -0
  51. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/usage.py +0 -0
  52. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/agents/varstore.py +0 -0
  53. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/browser_profile.py +0 -0
  54. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/cli.py +0 -0
  55. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/client.py +0 -0
  56. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/collectors/__init__.py +0 -0
  57. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/collectors/files.py +0 -0
  58. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/collectors/process.py +0 -0
  59. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/collectors/screenshot.py +0 -0
  60. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/config.py +0 -0
  61. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/crucial.py +0 -0
  62. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/custom_agents.py +0 -0
  63. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/departments.py +0 -0
  64. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/design_verify/__init__.py +0 -0
  65. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/design_verify/assets.py +0 -0
  66. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/design_verify/bootstrap.py +0 -0
  67. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/design_verify/browser.py +0 -0
  68. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/design_verify/compare.py +0 -0
  69. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/design_verify/loop.py +0 -0
  70. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/estimator.py +0 -0
  71. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/exceptions.py +0 -0
  72. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/firewall.py +0 -0
  73. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/formats.py +0 -0
  74. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/git_ops.py +0 -0
  75. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/github_fetch.py +0 -0
  76. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/github_ingest.py +0 -0
  77. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/graph_recon.py +0 -0
  78. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/graphify.py +0 -0
  79. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/hooks/__init__.py +0 -0
  80. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/hooks/crucial_guard.py +0 -0
  81. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/intake.py +0 -0
  82. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ipc/__init__.py +0 -0
  83. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ipc/electron_bridge.py +0 -0
  84. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ipc/handlers.py +0 -0
  85. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/journal.py +0 -0
  86. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/local_exec.py +0 -0
  87. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/memory_guard.py +0 -0
  88. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/models/__init__.py +0 -0
  89. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/models/envelope.py +0 -0
  90. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/models/requests.py +0 -0
  91. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/models/responses.py +0 -0
  92. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/models/snapshots.py +0 -0
  93. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/phase_timer.py +0 -0
  94. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/plan_run.py +0 -0
  95. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/platform_compat.py +0 -0
  96. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/prd_progress.py +0 -0
  97. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/proof_readers.py +0 -0
  98. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/providers.py +0 -0
  99. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/py.typed +0 -0
  100. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/resume.py +0 -0
  101. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/router.py +0 -0
  102. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/run_auto.py +0 -0
  103. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/schedule_tickets.py +0 -0
  104. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/scheduler.py +0 -0
  105. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/self_report.py +0 -0
  106. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/self_report_local.py +0 -0
  107. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/sse.py +0 -0
  108. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/swagger.py +0 -0
  109. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/task_file.py +0 -0
  110. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/task_types.py +0 -0
  111. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/test.py +0 -0
  112. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/test_pipeline.py +0 -0
  113. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ticket_fields.py +0 -0
  114. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/tickets.py +0 -0
  115. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/tiers/__init__.py +0 -0
  116. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/tiers/mini_agent.py +0 -0
  117. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/__init__.py +0 -0
  118. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/api.py +0 -0
  119. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/dashboard.py +0 -0
  120. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/health.py +0 -0
  121. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/ip_allowlist.py +0 -0
  122. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/jobs.py +0 -0
  123. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/models.py +0 -0
  124. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/runner.py +0 -0
  125. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/security.py +0 -0
  126. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/server.py +0 -0
  127. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/session.py +0 -0
  128. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/setup_manager.py +0 -0
  129. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/agents.html +0 -0
  130. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/app.js +0 -0
  131. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/chat.html +0 -0
  132. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/custom-agents.html +0 -0
  133. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/default-agents.html +0 -0
  134. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/index.html +0 -0
  135. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/report.html +0 -0
  136. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/runs.html +0 -0
  137. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/schedules.html +0 -0
  138. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/setup.html +0 -0
  139. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/styles.css +0 -0
  140. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/static/tickets.html +0 -0
  141. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/ui/swagger.py +0 -0
  142. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/updater.py +0 -0
  143. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/url_access.py +0 -0
  144. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/vps_health.py +0 -0
  145. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/vps_identity.py +0 -0
  146. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/sdk/write_hook.py +0 -0
  147. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/setup.cfg +0 -0
  148. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_activity_claude_runs.py +0 -0
  149. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_activity_collectors.py +0 -0
  150. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_activity_core.py +0 -0
  151. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_activity_live_tracking.py +0 -0
  152. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_activity_macos.py +0 -0
  153. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_activity_os_setup.py +0 -0
  154. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_activity_pipeline.py +0 -0
  155. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_activity.py +0 -0
  156. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_activity_tracking.py +0 -0
  157. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_chat_features.py +0 -0
  158. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_crud_cli.py +0 -0
  159. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_listing.py +0 -0
  160. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_prompt_async.py +0 -0
  161. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_prompt_models.py +0 -0
  162. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_run_ticket.py +0 -0
  163. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_schedules.py +0 -0
  164. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agent_workspace_settings.py +0 -0
  165. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_agents_connectors_builder.py +0 -0
  166. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_allowed_tools_config.py +0 -0
  167. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_auto_agent_dispatch.py +0 -0
  168. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_browser_profile.py +0 -0
  169. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_cheap_tier.py +0 -0
  170. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_client_jira_projects.py +0 -0
  171. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_config_token_diagnostics.py +0 -0
  172. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_configure_token_owner.py +0 -0
  173. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_console_encoding.py +0 -0
  174. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_crash_recovery.py +0 -0
  175. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_departments.py +0 -0
  176. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_done_transition.py +0 -0
  177. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_epic_decompose_run.py +0 -0
  178. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_epic_inherited_agent_id.py +0 -0
  179. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_epic_session_isolation.py +0 -0
  180. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_estimator.py +0 -0
  181. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_fallback_cli.py +0 -0
  182. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_firewall_open_on_update.py +0 -0
  183. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_formats.py +0 -0
  184. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_git_action.py +0 -0
  185. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_github_fetch.py +0 -0
  186. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_github_ingest.py +0 -0
  187. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_graph_recon.py +0 -0
  188. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_graphify_build.py +0 -0
  189. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_graphify_hook_install.py +0 -0
  190. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_health.py +0 -0
  191. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_intake_job_outcome.py +0 -0
  192. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_intake_plan_review.py +0 -0
  193. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_journal.py +0 -0
  194. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_local_exec_provider.py +0 -0
  195. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_mcp_route_contract.py +0 -0
  196. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_memory_guard.py +0 -0
  197. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_phase_timer.py +0 -0
  198. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_plan_new_command.py +0 -0
  199. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_plan_run_activity.py +0 -0
  200. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_pr_identity.py +0 -0
  201. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_prd_artifact.py +0 -0
  202. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_project_path_resolution.py +0 -0
  203. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_proof_readers.py +0 -0
  204. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_providers.py +0 -0
  205. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_recon_session_isolation.py +0 -0
  206. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_resume.py +0 -0
  207. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_router.py +0 -0
  208. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_run_auto_agent_only.py +0 -0
  209. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_run_outputs.py +0 -0
  210. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_run_verdict_is_honest.py +0 -0
  211. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_schedule_api.py +0 -0
  212. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_schedule_outputs_and_plans.py +0 -0
  213. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_schedule_tickets.py +0 -0
  214. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_scheduler.py +0 -0
  215. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_scheduler_transient_retry.py +0 -0
  216. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_self_report.py +0 -0
  217. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_service_token_warning.py +0 -0
  218. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_stale_panel_restart.py +0 -0
  219. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_swagger_command.py +0 -0
  220. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_task_file_and_plan_run.py +0 -0
  221. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_task_file_format_doc.py +0 -0
  222. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_ticket_cli.py +0 -0
  223. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_ticket_fields.py +0 -0
  224. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_ticket_run_resolve_by_id.py +0 -0
  225. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_ticket_speed_fixes.py +0 -0
  226. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_tier_flag_parity.py +0 -0
  227. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_ui_gate.py +0 -0
  228. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_ui_lifespan.py +0 -0
  229. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_update_timer.py +0 -0
  230. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_updater_extras.py +0 -0
  231. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_updater_pep668.py +0 -0
  232. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_updater_reports_the_truth.py +0 -0
  233. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_url_access.py +0 -0
  234. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_vps_health.py +0 -0
  235. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_vps_only_reporting.py +0 -0
  236. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_windows_loop_identity.py +0 -0
  237. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.76}/tests/test_write_hook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.75
3
+ Version: 1.6.76
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.75
3
+ Version: 1.6.76
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -230,5 +230,6 @@ tests/test_url_access.py
230
230
  tests/test_vps_health.py
231
231
  tests/test_vps_only_reporting.py
232
232
  tests/test_windows_loop_identity.py
233
+ tests/test_windows_panel_task.py
233
234
  tests/test_windows_task_paths.py
234
235
  tests/test_write_hook.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.75
3
+ Version: 1.6.76
4
4
 
5
5
  Public surface:
6
6
  BackendClient.from_config() — create client from ~/.agentos/config.toml
@@ -34,7 +34,7 @@ Rules:
34
34
  - All data egress through submit_webhook only
35
35
  """
36
36
 
37
- __version__ = "1.6.75" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.76" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
38
38
  __author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
39
39
 
40
40
  from sdk.client import BackendClient
@@ -410,20 +410,118 @@ def _uninstall_launchd() -> tuple[bool, str]:
410
410
 
411
411
  # ── Windows (Scheduled Task) ────────────────────────────────────────────────
412
412
 
413
+ #: Written when Windows refused the logged-out (S4U) panel task, so ``ensure`` does
414
+ #: not rebuild the task on every update only to be refused again.
415
+ _S4U_REFUSED_MARKER = Path.home() / ".agentos" / "panel_task_s4u_refused"
416
+
417
+
418
+ def _windows_account() -> str:
419
+ """``DOMAIN\\user`` for the task principal — the user whose ~/.agentos holds the token."""
420
+ import getpass
421
+
422
+ user = os.environ.get("USERNAME") or getpass.getuser()
423
+ domain = os.environ.get("USERDOMAIN") or os.environ.get("COMPUTERNAME") or ""
424
+ return f"{domain}\\{user}" if domain else user
425
+
426
+
427
+ def _panel_task_xml(exe: str, args: str, home: str, account: str) -> str:
428
+ """Task Scheduler XML for the panel: runs whether or not anyone is signed in.
429
+
430
+ The old task was ``/SC ONLOGON`` with an interactive token, so the panel — and
431
+ with it the health ping, the activity collector and the shipper — stopped the
432
+ moment the user signed out of RDP. A VPS used all day then reported nothing
433
+ from sign-out until the next sign-in ("VPS not reporting since 8h ago").
434
+
435
+ * ``S4U`` logon: runs without a stored password, signed in or not. It has no
436
+ Windows network credentials, which the panel does not use (HTTPS only).
437
+ * Boot AND logon triggers: up after a reboot with nobody signed in, and started
438
+ again at sign-in if it was down.
439
+ * Restart on failure, no execution time limit (the default is 72 h).
440
+ * ``HighestAvailable``: the collector reads the Security log, which needs an
441
+ elevated token — same as the old ``/RL HIGHEST``.
442
+ """
443
+ from xml.sax.saxutils import escape
444
+
445
+ who = escape(account)
446
+ return (
447
+ '<?xml version="1.0" encoding="UTF-16"?>\n'
448
+ '<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">\n'
449
+ " <RegistrationInfo><Description>AgentOS control panel (health, activity, "
450
+ "ticket runs)</Description></RegistrationInfo>\n"
451
+ " <Triggers>\n"
452
+ " <BootTrigger><Enabled>true</Enabled><Delay>PT1M</Delay></BootTrigger>\n"
453
+ f" <LogonTrigger><Enabled>true</Enabled><UserId>{who}</UserId></LogonTrigger>\n"
454
+ " </Triggers>\n"
455
+ f' <Principals><Principal id="Author"><UserId>{who}</UserId>'
456
+ "<LogonType>S4U</LogonType><RunLevel>HighestAvailable</RunLevel>"
457
+ "</Principal></Principals>\n"
458
+ " <Settings>\n"
459
+ " <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
460
+ " <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n"
461
+ " <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n"
462
+ " <StartWhenAvailable>true</StartWhenAvailable>\n"
463
+ " <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>\n"
464
+ " <RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure>\n"
465
+ " </Settings>\n"
466
+ ' <Actions Context="Author"><Exec>'
467
+ f"<Command>{escape(exe)}</Command><Arguments>{escape(args)}</Arguments>"
468
+ f"<WorkingDirectory>{escape(home)}</WorkingDirectory>"
469
+ "</Exec></Actions>\n"
470
+ "</Task>\n"
471
+ )
472
+
473
+
474
+ def _create_task_from_xml(task: str, xml: str) -> tuple[int, str]:
475
+ import tempfile
476
+
477
+ fd, path = tempfile.mkstemp(suffix=".xml")
478
+ try:
479
+ with os.fdopen(fd, "w", encoding="utf-16") as fh: # schtasks /XML wants UTF-16
480
+ fh.write(xml)
481
+ return _run(["schtasks", "/Create", "/TN", task, "/XML", path, "/F"])
482
+ except OSError as exc:
483
+ return 1, str(exc)
484
+ finally:
485
+ try:
486
+ os.unlink(path)
487
+ except OSError:
488
+ pass
489
+
490
+
413
491
  def _install_schtask(host: str, port: int) -> tuple[bool, str]:
414
492
  exe = _exe()
415
493
  refusal = _require_absolute(exe, WIN_TASK)
416
494
  if refusal:
417
495
  return False, refusal
418
- tr = f'"{exe}" --host {host} --port {port} --no-browser'
419
- rc, out = _run(["schtasks", "/Create", "/TN", WIN_TASK, "/TR", tr,
420
- "/SC", "ONLOGON", "/RL", "HIGHEST", "/F"])
496
+ args = f"--host {host} --port {port} --no-browser"
497
+ rc, out = _create_task_from_xml(
498
+ WIN_TASK, _panel_task_xml(exe, args, str(Path.home()), _windows_account()))
499
+ if rc == 0:
500
+ try:
501
+ _S4U_REFUSED_MARKER.unlink()
502
+ except OSError:
503
+ pass
504
+ _run(["schtasks", "/Run", "/TN", WIN_TASK])
505
+ return True, (f"installed scheduled task '{WIN_TASK}' — runs whether or not anyone "
506
+ f"is signed in, restarts on failure, and is running now.")
507
+
508
+ # Windows refused it (e.g. a policy denying "Log on as a batch job"). Fall back to
509
+ # the at-logon task: worse — it stops at sign-out — but never worse than before.
510
+ try:
511
+ _S4U_REFUSED_MARKER.parent.mkdir(parents=True, exist_ok=True)
512
+ _S4U_REFUSED_MARKER.write_text(out[-2000:], encoding="utf-8")
513
+ except OSError:
514
+ pass
515
+ tr = f'"{exe}" {args}'
516
+ rc, out2 = _run(["schtasks", "/Create", "/TN", WIN_TASK, "/TR", tr,
517
+ "/SC", "ONLOGON", "/RL", "HIGHEST", "/F"])
421
518
  if rc != 0:
422
- return False, f"schtasks create failed:\n{out}"
519
+ return False, f"schtasks create failed:\n{out}\n{out2}"
423
520
  _run(["schtasks", "/Run", "/TN", WIN_TASK])
424
521
  return True, (f"installed scheduled task '{WIN_TASK}' — starts at logon and is "
425
- f"running now.\n For crash-restart and no-login start, mark the task "
426
- f"'Run whether user is logged on' + 'Restart on failure' in Task Scheduler.")
522
+ f"running now. NOTE: Windows refused the run-while-signed-out task, so "
523
+ f"the panel STOPS when you sign out — disconnect from RDP instead of "
524
+ f"signing out. Reason: {out.strip()[-300:]}")
427
525
 
428
526
 
429
527
  def _update_task_xml(exe: str, home: str) -> str:
@@ -527,6 +625,19 @@ def _schtask_points_nowhere(task: str) -> bool:
527
625
  return cmd is not None and not (Path(cmd).is_absolute() and Path(cmd).is_file())
528
626
 
529
627
 
628
+ def _schtask_runs_signed_out(task: str) -> bool | None:
629
+ """True when ``task`` keeps running after sign-out; ``None`` if it cannot be read."""
630
+ rc, out = _run(["schtasks", "/Query", "/TN", task, "/XML"])
631
+ if rc != 0:
632
+ return None
633
+ return bool(re.search(r"<LogonType>\s*(S4U|Password)\s*</LogonType>", out))
634
+
635
+
636
+ def _panel_task_needs_upgrade() -> bool:
637
+ """An at-logon panel task (pre-1.6.76) that Windows has not already refused to replace."""
638
+ return _schtask_runs_signed_out(WIN_TASK) is False and not _S4U_REFUSED_MARKER.exists()
639
+
640
+
530
641
  def _uninstall_schtask() -> tuple[bool, str]:
531
642
  _run(["schtasks", "/Delete", "/TN", WIN_UPDATE_TASK, "/F"])
532
643
  _run(["schtasks", "/End", "/TN", WIN_TASK])
@@ -855,6 +966,10 @@ def ensure(restart_running: bool = False) -> tuple[bool, str]:
855
966
  rok, rmsg = refresh() # rebuilds the task with an absolute path
856
967
  return rok, (_with_token_warning(f"panel task pointed at a missing program — "
857
968
  f"rebuilt; {rmsg}") if rok else rmsg)
969
+ if IS_WINDOWS and _panel_task_needs_upgrade():
970
+ rok, rmsg = refresh() # at-logon task → runs while signed out
971
+ return rok, (_with_token_warning(f"panel task now runs while signed out; {rmsg}")
972
+ if rok else rmsg)
858
973
  if _is_active():
859
974
  stale = _restart_if_stale()
860
975
  if stale is not None:
@@ -0,0 +1,135 @@
1
+ """The Windows panel task keeps running after the user signs out.
2
+
3
+ The incident: the panel task was ``/SC ONLOGON`` with an interactive token, so
4
+ the panel — health ping, activity collector, shipper, ticket runs — stopped the
5
+ moment someone signed out of RDP. A VPS used all day showed "VPS not reporting
6
+ since 8h ago" on the dashboard from sign-out until the next sign-in, and the
7
+ existing task was never upgraded by an update.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+
15
+ from sdk.ui import service
16
+
17
+
18
+ @pytest.fixture
19
+ def windows(monkeypatch, tmp_path):
20
+ exe = tmp_path / "R&D" / "Scripts" / "agentos-ui.exe"
21
+ exe.parent.mkdir(parents=True)
22
+ exe.write_text("")
23
+ monkeypatch.setattr(service, "IS_WINDOWS", True)
24
+ monkeypatch.setattr(service, "_exe", lambda: str(exe))
25
+ monkeypatch.setattr(service, "_S4U_REFUSED_MARKER", tmp_path / ".agentos" / "refused")
26
+ monkeypatch.setenv("USERDOMAIN", "WIN-PND3HEN88VE")
27
+ monkeypatch.setenv("USERNAME", "Administrator")
28
+ return exe
29
+
30
+
31
+ def _recorder(monkeypatch, *, xml_rc=0, xml_out=""):
32
+ calls, xmls = [], []
33
+
34
+ def run(argv):
35
+ calls.append(argv)
36
+ if "/XML" in argv and "/Create" in argv:
37
+ xmls.append(Path(argv[argv.index("/XML") + 1]).read_text(encoding="utf-16"))
38
+ return xml_rc, xml_out
39
+ return 0, ""
40
+ monkeypatch.setattr(service, "_run", run)
41
+ return calls, xmls
42
+
43
+
44
+ class TestTheTaskDefinition:
45
+ def test_runs_signed_out_at_boot_and_logon_and_restarts(self, monkeypatch, windows):
46
+ _, xmls = _recorder(monkeypatch)
47
+ ok, msg = service._install_schtask("0.0.0.0", 8765)
48
+ assert ok is True and "signed in" in msg
49
+ xml = xmls[0]
50
+ assert "<LogonType>S4U</LogonType>" in xml
51
+ assert "<RunLevel>HighestAvailable</RunLevel>" in xml # reads the Security log
52
+ assert "<BootTrigger>" in xml and "<LogonTrigger>" in xml
53
+ assert "<UserId>WIN-PND3HEN88VE\\Administrator</UserId>" in xml
54
+ assert "<RestartOnFailure>" in xml
55
+ assert "<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>" in xml # default would be 72 h
56
+ assert "R&amp;D" in xml # the path is escaped
57
+
58
+ def test_the_task_is_started_after_creation(self, monkeypatch, windows):
59
+ calls, _ = _recorder(monkeypatch)
60
+ service._install_schtask("0.0.0.0", 8765)
61
+ assert ["schtasks", "/Run", "/TN", service.WIN_TASK] in calls
62
+
63
+
64
+ class TestWhenWindowsRefusesIt:
65
+ def test_falls_back_to_the_at_logon_task_and_says_so(self, monkeypatch, windows):
66
+ calls, _ = _recorder(monkeypatch, xml_rc=1, xml_out="ERROR: Access is denied.")
67
+ ok, msg = service._install_schtask("0.0.0.0", 8765)
68
+ assert ok is True
69
+ fallback = calls[1]
70
+ assert fallback[fallback.index("/SC") + 1] == "ONLOGON"
71
+ assert "disconnect from RDP instead of signing out" in msg
72
+ assert service._S4U_REFUSED_MARKER.exists() # don't retry every update
73
+
74
+ def test_a_later_success_clears_the_refusal(self, monkeypatch, windows):
75
+ service._S4U_REFUSED_MARKER.parent.mkdir(parents=True)
76
+ service._S4U_REFUSED_MARKER.write_text("denied")
77
+ _recorder(monkeypatch)
78
+ service._install_schtask("0.0.0.0", 8765)
79
+ assert not service._S4U_REFUSED_MARKER.exists()
80
+
81
+
82
+ def _query(monkeypatch, rc, logon_type):
83
+ xml = f"<Task><Principals><Principal><LogonType>{logon_type}</LogonType></Principal></Principals></Task>"
84
+ monkeypatch.setattr(service, "_run", lambda argv: (rc, xml))
85
+
86
+
87
+ class TestDetection:
88
+ @pytest.mark.parametrize("logon_type, expected", [
89
+ ("InteractiveToken", False), ("S4U", True), ("Password", True)])
90
+ def test_reads_the_logon_type(self, monkeypatch, logon_type, expected):
91
+ _query(monkeypatch, 0, logon_type)
92
+ assert service._schtask_runs_signed_out(service.WIN_TASK) is expected
93
+
94
+ def test_an_unreadable_task_is_unknown_not_old(self, monkeypatch, windows):
95
+ _query(monkeypatch, 1, "InteractiveToken")
96
+ assert service._schtask_runs_signed_out(service.WIN_TASK) is None
97
+ assert service._panel_task_needs_upgrade() is False
98
+
99
+
100
+ class TestEnsureUpgradesTheExistingTask:
101
+ @pytest.fixture
102
+ def installed(self, monkeypatch, windows):
103
+ monkeypatch.setattr(service, "_service_disabled", lambda: False)
104
+ monkeypatch.setattr(service, "is_installed", lambda: True)
105
+ monkeypatch.setattr(service, "update_timer_installed", lambda: True)
106
+ monkeypatch.setattr(service, "_needs_root_for_existing", lambda: False)
107
+ monkeypatch.setattr(service, "_schtask_points_nowhere", lambda task: False)
108
+ monkeypatch.setattr(service, "_with_token_warning", lambda m: m)
109
+ monkeypatch.setattr(service, "_is_active", lambda: True)
110
+ monkeypatch.setattr(service, "_restart_if_stale", lambda: None)
111
+ seen = {"refresh": 0}
112
+
113
+ def refresh():
114
+ seen["refresh"] += 1
115
+ return True, "service refreshed"
116
+ monkeypatch.setattr(service, "refresh", refresh)
117
+ return seen
118
+
119
+ def test_an_at_logon_task_is_rebuilt_on_the_next_update(self, monkeypatch, installed):
120
+ _query(monkeypatch, 0, "InteractiveToken")
121
+ ok, msg = service.ensure(restart_running=False)
122
+ assert ok is True and "runs while signed out" in msg
123
+ assert installed["refresh"] == 1
124
+
125
+ def test_an_already_upgraded_task_is_left_alone(self, monkeypatch, installed):
126
+ _query(monkeypatch, 0, "S4U")
127
+ service.ensure(restart_running=False)
128
+ assert installed["refresh"] == 0
129
+
130
+ def test_a_box_that_refused_it_is_not_rebuilt_every_update(self, monkeypatch, installed):
131
+ service._S4U_REFUSED_MARKER.parent.mkdir(parents=True)
132
+ service._S4U_REFUSED_MARKER.write_text("denied")
133
+ _query(monkeypatch, 0, "InteractiveToken")
134
+ service.ensure(restart_running=False)
135
+ assert installed["refresh"] == 0
@@ -88,11 +88,20 @@ class TestRefusesABareProgramName:
88
88
  exe.write_text("")
89
89
  calls = []
90
90
  monkeypatch.setattr(service, "_run", lambda argv: (calls.append(argv), (0, ""))[1])
91
+ xml_seen = []
92
+
93
+ def run(argv):
94
+ calls.append(argv)
95
+ if "/XML" in argv:
96
+ xml_seen.append(Path(argv[argv.index("/XML") + 1]).read_text(encoding="utf-16"))
97
+ return 0, ""
98
+ monkeypatch.setattr(service, "_run", run)
91
99
  ok, _ = service._install_schtask("0.0.0.0", 8765)
92
100
  assert ok is True
93
101
  create = calls[0]
94
102
  assert create[:4] == ["schtasks", "/Create", "/TN", service.WIN_TASK]
95
- assert create[create.index("/TR") + 1].startswith(f'"{exe}" --host 0.0.0.0')
103
+ assert f"<Command>{exe}</Command>" in xml_seen[0]
104
+ assert "<Arguments>--host 0.0.0.0 --port 8765 --no-browser</Arguments>" in xml_seen[0]
96
105
 
97
106
 
98
107
  def _task_xml(command: str) -> str: