devops-bot-sdk 1.6.75__tar.gz → 1.6.79__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 (238) hide show
  1. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/SOURCES.txt +2 -0
  4. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/cli.py +6 -3
  6. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/local_exec.py +135 -7
  7. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/run_auto.py +9 -9
  8. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/service.py +121 -6
  9. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/updater.py +26 -4
  10. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/vps_identity.py +7 -1
  11. devops_bot_sdk-1.6.79/tests/test_fallback_cli_upgrade.py +147 -0
  12. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_updater_reports_the_truth.py +57 -0
  13. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_vps_only_reporting.py +27 -0
  14. devops_bot_sdk-1.6.79/tests/test_windows_panel_task.py +135 -0
  15. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_windows_task_paths.py +10 -1
  16. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/README.md +0 -0
  17. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  18. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  19. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/requires.txt +0 -0
  20. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  21. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/pyproject.toml +0 -0
  22. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/__init__.py +0 -0
  23. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/classifier.py +0 -0
  24. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/__init__.py +0 -0
  25. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/common.py +0 -0
  26. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/linux_auditd.py +0 -0
  27. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/macos_eslogger.py +0 -0
  28. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/windows_eventlog.py +0 -0
  29. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/models.py +0 -0
  30. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/os_setup.py +0 -0
  31. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/recorder.py +0 -0
  32. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/redact.py +0 -0
  33. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/settings.py +0 -0
  34. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/shipper.py +0 -0
  35. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/spool.py +0 -0
  36. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agentd.py +0 -0
  37. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/__init__.py +0 -0
  38. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/activity.py +0 -0
  39. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/builder.py +0 -0
  40. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/chat.py +0 -0
  41. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/connectors.py +0 -0
  42. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/__init__.py +0 -0
  43. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/claude_code.py +0 -0
  44. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/http.py +0 -0
  45. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/mcp.py +0 -0
  46. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/n8n.py +0 -0
  47. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/shell.py +0 -0
  48. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/integrations.py +0 -0
  49. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/inventory.py +0 -0
  50. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/library/__init__.py +0 -0
  51. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/manifest.py +0 -0
  52. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_studio.py +0 -0
  53. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_template.py +0 -0
  54. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_vars.py +0 -0
  55. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/run_log.py +0 -0
  56. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/schedules.py +0 -0
  57. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/secrets.py +0 -0
  58. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/ticket_run.py +0 -0
  59. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/usage.py +0 -0
  60. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/varstore.py +0 -0
  61. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/browser_profile.py +0 -0
  62. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/client.py +0 -0
  63. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/__init__.py +0 -0
  64. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/files.py +0 -0
  65. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/process.py +0 -0
  66. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/screenshot.py +0 -0
  67. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/config.py +0 -0
  68. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/crucial.py +0 -0
  69. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/custom_agents.py +0 -0
  70. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/departments.py +0 -0
  71. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/__init__.py +0 -0
  72. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/assets.py +0 -0
  73. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/bootstrap.py +0 -0
  74. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/browser.py +0 -0
  75. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/compare.py +0 -0
  76. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/loop.py +0 -0
  77. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/estimator.py +0 -0
  78. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/exceptions.py +0 -0
  79. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/firewall.py +0 -0
  80. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/formats.py +0 -0
  81. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/git_ops.py +0 -0
  82. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/github_fetch.py +0 -0
  83. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/github_ingest.py +0 -0
  84. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/graph_recon.py +0 -0
  85. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/graphify.py +0 -0
  86. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/hooks/__init__.py +0 -0
  87. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/hooks/crucial_guard.py +0 -0
  88. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/intake.py +0 -0
  89. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ipc/__init__.py +0 -0
  90. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ipc/electron_bridge.py +0 -0
  91. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ipc/handlers.py +0 -0
  92. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/journal.py +0 -0
  93. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/memory_guard.py +0 -0
  94. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/__init__.py +0 -0
  95. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/envelope.py +0 -0
  96. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/requests.py +0 -0
  97. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/responses.py +0 -0
  98. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/snapshots.py +0 -0
  99. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/phase_timer.py +0 -0
  100. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/plan_run.py +0 -0
  101. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/platform_compat.py +0 -0
  102. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/prd_progress.py +0 -0
  103. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/proof_readers.py +0 -0
  104. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/providers.py +0 -0
  105. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/py.typed +0 -0
  106. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/resume.py +0 -0
  107. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/router.py +0 -0
  108. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/schedule_tickets.py +0 -0
  109. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/scheduler.py +0 -0
  110. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/self_report.py +0 -0
  111. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/self_report_local.py +0 -0
  112. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/sse.py +0 -0
  113. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/swagger.py +0 -0
  114. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/task_file.py +0 -0
  115. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/task_types.py +0 -0
  116. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/test.py +0 -0
  117. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/test_pipeline.py +0 -0
  118. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ticket_fields.py +0 -0
  119. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/tickets.py +0 -0
  120. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/tiers/__init__.py +0 -0
  121. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/tiers/mini_agent.py +0 -0
  122. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/__init__.py +0 -0
  123. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/api.py +0 -0
  124. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/dashboard.py +0 -0
  125. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/health.py +0 -0
  126. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/ip_allowlist.py +0 -0
  127. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/jobs.py +0 -0
  128. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/models.py +0 -0
  129. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/runner.py +0 -0
  130. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/security.py +0 -0
  131. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/server.py +0 -0
  132. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/session.py +0 -0
  133. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/setup_manager.py +0 -0
  134. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/agents.html +0 -0
  135. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/app.js +0 -0
  136. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/chat.html +0 -0
  137. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/custom-agents.html +0 -0
  138. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/default-agents.html +0 -0
  139. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/index.html +0 -0
  140. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/report.html +0 -0
  141. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/runs.html +0 -0
  142. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/schedules.html +0 -0
  143. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/setup.html +0 -0
  144. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/styles.css +0 -0
  145. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/tickets.html +0 -0
  146. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/swagger.py +0 -0
  147. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/url_access.py +0 -0
  148. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/vps_health.py +0 -0
  149. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/write_hook.py +0 -0
  150. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/setup.cfg +0 -0
  151. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_claude_runs.py +0 -0
  152. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_collectors.py +0 -0
  153. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_core.py +0 -0
  154. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_live_tracking.py +0 -0
  155. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_macos.py +0 -0
  156. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_os_setup.py +0 -0
  157. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_pipeline.py +0 -0
  158. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_activity.py +0 -0
  159. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_activity_tracking.py +0 -0
  160. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_chat_features.py +0 -0
  161. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_crud_cli.py +0 -0
  162. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_listing.py +0 -0
  163. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_prompt_async.py +0 -0
  164. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_prompt_models.py +0 -0
  165. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_run_ticket.py +0 -0
  166. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_schedules.py +0 -0
  167. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_workspace_settings.py +0 -0
  168. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agents_connectors_builder.py +0 -0
  169. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_allowed_tools_config.py +0 -0
  170. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_auto_agent_dispatch.py +0 -0
  171. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_browser_profile.py +0 -0
  172. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_cheap_tier.py +0 -0
  173. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_client_jira_projects.py +0 -0
  174. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_config_token_diagnostics.py +0 -0
  175. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_configure_token_owner.py +0 -0
  176. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_console_encoding.py +0 -0
  177. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_crash_recovery.py +0 -0
  178. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_departments.py +0 -0
  179. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_done_transition.py +0 -0
  180. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_epic_decompose_run.py +0 -0
  181. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_epic_inherited_agent_id.py +0 -0
  182. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_epic_session_isolation.py +0 -0
  183. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_estimator.py +0 -0
  184. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_fallback_cli.py +0 -0
  185. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_firewall_open_on_update.py +0 -0
  186. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_formats.py +0 -0
  187. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_git_action.py +0 -0
  188. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_github_fetch.py +0 -0
  189. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_github_ingest.py +0 -0
  190. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_graph_recon.py +0 -0
  191. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_graphify_build.py +0 -0
  192. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_graphify_hook_install.py +0 -0
  193. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_health.py +0 -0
  194. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_intake_job_outcome.py +0 -0
  195. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_intake_plan_review.py +0 -0
  196. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_journal.py +0 -0
  197. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_local_exec_provider.py +0 -0
  198. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_mcp_route_contract.py +0 -0
  199. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_memory_guard.py +0 -0
  200. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_phase_timer.py +0 -0
  201. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_plan_new_command.py +0 -0
  202. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_plan_run_activity.py +0 -0
  203. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_pr_identity.py +0 -0
  204. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_prd_artifact.py +0 -0
  205. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_project_path_resolution.py +0 -0
  206. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_proof_readers.py +0 -0
  207. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_providers.py +0 -0
  208. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_recon_session_isolation.py +0 -0
  209. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_resume.py +0 -0
  210. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_router.py +0 -0
  211. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_run_auto_agent_only.py +0 -0
  212. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_run_outputs.py +0 -0
  213. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_run_verdict_is_honest.py +0 -0
  214. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_schedule_api.py +0 -0
  215. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_schedule_outputs_and_plans.py +0 -0
  216. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_schedule_tickets.py +0 -0
  217. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_scheduler.py +0 -0
  218. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_scheduler_transient_retry.py +0 -0
  219. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_self_report.py +0 -0
  220. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_service_token_warning.py +0 -0
  221. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_stale_panel_restart.py +0 -0
  222. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_swagger_command.py +0 -0
  223. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_task_file_and_plan_run.py +0 -0
  224. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_task_file_format_doc.py +0 -0
  225. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_cli.py +0 -0
  226. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_fields.py +0 -0
  227. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_run_resolve_by_id.py +0 -0
  228. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_speed_fixes.py +0 -0
  229. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_tier_flag_parity.py +0 -0
  230. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ui_gate.py +0 -0
  231. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ui_lifespan.py +0 -0
  232. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_update_timer.py +0 -0
  233. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_updater_extras.py +0 -0
  234. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_updater_pep668.py +0 -0
  235. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_url_access.py +0 -0
  236. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_vps_health.py +0 -0
  237. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_windows_loop_identity.py +0 -0
  238. {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/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.79
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.79
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
@@ -175,6 +175,7 @@ tests/test_epic_inherited_agent_id.py
175
175
  tests/test_epic_session_isolation.py
176
176
  tests/test_estimator.py
177
177
  tests/test_fallback_cli.py
178
+ tests/test_fallback_cli_upgrade.py
178
179
  tests/test_firewall_open_on_update.py
179
180
  tests/test_formats.py
180
181
  tests/test_git_action.py
@@ -230,5 +231,6 @@ tests/test_url_access.py
230
231
  tests/test_vps_health.py
231
232
  tests/test_vps_only_reporting.py
232
233
  tests/test_windows_loop_identity.py
234
+ tests/test_windows_panel_task.py
233
235
  tests/test_windows_task_paths.py
234
236
  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.79
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.79" # 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
@@ -1383,7 +1383,7 @@ def _browser_command(args: list[str]) -> None:
1383
1383
  # ── fallback ─────────────────────────────────────────────────────────────
1384
1384
 
1385
1385
  def _fallback_usage() -> None:
1386
- print("usage: agentos fallback install # where `ad` is, or how to install it")
1386
+ print("usage: agentos fallback install # install `ad`, or update an outdated one")
1387
1387
  print(" agentos fallback login [--browser | --api-key [KEY]] [--base-url URL] [--no-browser]")
1388
1388
  print(" agentos fallback status [--verify] # --verify asks the server too")
1389
1389
  print(" agentos fallback logout")
@@ -1411,8 +1411,11 @@ def _fallback_command(args: list[str]) -> None:
1411
1411
  if le._fallback_disabled():
1412
1412
  print("error: the fallback is disabled (AGENTOS_FALLBACK_CLI=off)")
1413
1413
  sys.exit(1)
1414
- # The SDK no longer installs the CLI: it is updated continuously, so the
1415
- # copy on the machine is the one to use. This only reports where it is.
1414
+ # Installs the CLI when it is missing, and replaces an outdated one (the old
1415
+ # package name, or a release older than FALLBACK_MIN_VERSION).
1416
+ done = le.upgrade_fallback_cli(install_missing=True)
1417
+ if done:
1418
+ print(done)
1416
1419
  cli = le.fallback_cli_path()
1417
1420
  if not cli:
1418
1421
  print(f"error: the AI Draftsman CLI (`ad`) was not found. Install it with:\n"
@@ -24,6 +24,7 @@ import re
24
24
  import shutil
25
25
  import subprocess
26
26
  import sys
27
+ import time
27
28
  from datetime import datetime, timedelta
28
29
  from pathlib import Path
29
30
  from typing import Any, Awaitable, Callable
@@ -1047,7 +1048,7 @@ async def _run_claude_cli(
1047
1048
  # ── Fallback: the AI Draftsman CLI ─────────────────────────────────────────
1048
1049
  # When the `claude` CLI is not AVAILABLE — not installed, logged out, or dying
1049
1050
  # before it even starts a session — the ticket runs on our own AI Draftsman CLI
1050
- # (`ad`, npm package @aidraftsman/cli) instead of failing or parking. It is the
1051
+ # (`ad`, npm package @consultancy-outfit/ai-draftsman-cli) instead of failing or parking. It is the
1051
1052
  # same job against the same project dir: the prompt goes in on stdin,
1052
1053
  # `-p --output-format json` prints one JSON object.
1053
1054
  #
@@ -1056,9 +1057,13 @@ async def _run_claude_cli(
1056
1057
  # model mid-ticket.
1057
1058
  #
1058
1059
  # The CLI is the one already installed on the machine (`ad` on PATH, or an npm
1059
- # global bin dir the service's PATH may not include). The SDK never installs it:
1060
- # it is updated continuously, and a copy pinned inside the SDK went stale the
1061
- # day after it shipped. It needs its own login on the box, as the user the SDK
1060
+ # global bin dir the service's PATH may not include). The SDK does not pin a copy
1061
+ # (one pinned inside the SDK went stale the day after it shipped), but it does keep
1062
+ # an installed one current: an `ad` from the old package name, or older than
1063
+ # FALLBACK_MIN_VERSION, is replaced with the latest release (`upgrade_fallback_cli`).
1064
+ # Before 1.1.1 every request carried a tool schema Gemini rejects (an integer
1065
+ # `enum`), so an old `ad` failed every ticket with "400 INVALID_ARGUMENT …
1066
+ # function_declarations". It needs its own login on the box, as the user the SDK
1062
1067
  # runs as: `agentos fallback login` (browser sign-in code or an API key), or
1063
1068
  # AD_API_KEY in the env. `agentos fallback status --verify` checks it against
1064
1069
  # the server.
@@ -1089,8 +1094,15 @@ def _setting(key: str) -> str:
1089
1094
  return ""
1090
1095
 
1091
1096
 
1097
+ #: The npm package that ships `ad`. It was published as @aidraftsman/cli before 1.1.1.
1098
+ FALLBACK_PACKAGE = "@consultancy-outfit/ai-draftsman-cli"
1099
+ _LEGACY_FALLBACK_PACKAGES = ("@aidraftsman/cli",)
1100
+ #: The oldest `ad` that works against the gateway's Gemini models (see above).
1101
+ FALLBACK_MIN_VERSION = (1, 1, 1)
1092
1102
  #: How to get the CLI when it is missing — shown in every "not found" message.
1093
- FALLBACK_INSTALL_HINT = "npm install -g @aidraftsman/cli"
1103
+ FALLBACK_INSTALL_HINT = f"npm install -g {FALLBACK_PACKAGE}"
1104
+ #: `upgrade_fallback_cli` runs at most this often from the start-up path.
1105
+ _FALLBACK_UPGRADE_EVERY_S = 6 * 3600
1094
1106
 
1095
1107
 
1096
1108
  def _fallback_disabled() -> bool:
@@ -1135,10 +1147,110 @@ def fallback_cli_path() -> str | None:
1135
1147
 
1136
1148
 
1137
1149
  async def ensure_fallback_cli() -> str | None:
1138
- """:func:`fallback_cli_path`. Kept for callers; it no longer installs anything."""
1150
+ """:func:`fallback_cli_path`. Kept for callers; it installs nothing itself."""
1139
1151
  return fallback_cli_path()
1140
1152
 
1141
1153
 
1154
+ def _version_tuple(version: str) -> tuple[int, ...]:
1155
+ parts = []
1156
+ for piece in str(version).split("-")[0].split("."):
1157
+ try:
1158
+ parts.append(int(piece))
1159
+ except ValueError:
1160
+ break
1161
+ return tuple(parts)
1162
+
1163
+
1164
+ def _npm_cmd(*args: str) -> list[str] | None:
1165
+ """An npm command line, or None without npm. npm is a .cmd shim on Windows."""
1166
+ npm = shutil.which("npm")
1167
+ if not npm:
1168
+ return None
1169
+ cmd = [npm, *args]
1170
+ if sys.platform == "win32" and npm.lower().endswith((".cmd", ".bat")):
1171
+ cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
1172
+ return cmd
1173
+
1174
+
1175
+ def fallback_packages() -> dict[str, str]:
1176
+ """The AI Draftsman CLI packages npm has installed globally, ``{name: version}``.
1177
+
1178
+ Read from npm rather than ``ad --version``: 1.1.1 still prints "1.1.0" there,
1179
+ so the version flag cannot tell the fixed release from the broken one.
1180
+ """
1181
+ cmd = _npm_cmd("ls", "-g", "--depth=0", "--json")
1182
+ if not cmd:
1183
+ return {}
1184
+ try:
1185
+ proc = subprocess.run(cmd, capture_output=True, text=True, errors="replace",
1186
+ timeout=60, check=False)
1187
+ deps = (json.loads(proc.stdout or "{}") or {}).get("dependencies") or {}
1188
+ except Exception: # noqa: BLE001 — npm missing/slow/odd output: nothing known
1189
+ return {}
1190
+ wanted = (FALLBACK_PACKAGE, *_LEGACY_FALLBACK_PACKAGES)
1191
+ return {name: str((deps.get(name) or {}).get("version") or "")
1192
+ for name in wanted if name in deps}
1193
+
1194
+
1195
+ def upgrade_fallback_cli(*, install_missing: bool = False) -> str:
1196
+ """Bring the AI Draftsman CLI up to date. Returns what was done, ``""`` if nothing.
1197
+
1198
+ Replaces an `ad` from the old package name and upgrades one older than
1199
+ FALLBACK_MIN_VERSION to the latest release. A machine without the CLI is left
1200
+ alone unless ``install_missing`` (``agentos fallback install``). Skipped when the
1201
+ fallback is off or AGENTOS_FALLBACK_CLI points at a CLI the owner manages. Never raises.
1202
+ """
1203
+ if _fallback_disabled() or _setting("AGENTOS_FALLBACK_CLI"):
1204
+ return ""
1205
+ if not _npm_cmd("--version"):
1206
+ return ""
1207
+ installed = fallback_packages()
1208
+ legacy = [p for p in _LEGACY_FALLBACK_PACKAGES if p in installed]
1209
+ current = installed.get(FALLBACK_PACKAGE)
1210
+ if not legacy and current and _version_tuple(current) >= FALLBACK_MIN_VERSION:
1211
+ return ""
1212
+ if not legacy and not current and not install_missing:
1213
+ return ""
1214
+ try:
1215
+ for pkg in legacy: # same `ad` bin: the old package must go first
1216
+ subprocess.run(_npm_cmd("uninstall", "-g", pkg), capture_output=True,
1217
+ text=True, errors="replace", timeout=300, check=False)
1218
+ proc = subprocess.run(_npm_cmd("install", "-g", f"{FALLBACK_PACKAGE}@latest"),
1219
+ capture_output=True, text=True, errors="replace",
1220
+ timeout=600, check=False)
1221
+ except Exception as exc: # noqa: BLE001
1222
+ return f"could not update the AI Draftsman CLI: {exc} — run `{FALLBACK_INSTALL_HINT}`"
1223
+ if proc.returncode != 0:
1224
+ tail = (proc.stderr or proc.stdout or "").strip()[-300:]
1225
+ return f"could not update the AI Draftsman CLI ({tail}) — run `{FALLBACK_INSTALL_HINT}`"
1226
+ now = fallback_packages().get(FALLBACK_PACKAGE, "?")
1227
+ before = current or (installed.get(legacy[0]) if legacy else "") or "not installed"
1228
+ return f"AI Draftsman CLI updated {before} → {now}"
1229
+
1230
+
1231
+ def maybe_upgrade_fallback_cli() -> str:
1232
+ """:func:`upgrade_fallback_cli`, at most every few hours (start-up path)."""
1233
+ stamp = Path.home() / ".agentos" / "fallback-cli-checked"
1234
+ try:
1235
+ if time.time() - stamp.stat().st_mtime < _FALLBACK_UPGRADE_EVERY_S:
1236
+ return ""
1237
+ except OSError:
1238
+ pass
1239
+ message = upgrade_fallback_cli()
1240
+ try:
1241
+ stamp.parent.mkdir(parents=True, exist_ok=True)
1242
+ stamp.touch()
1243
+ except OSError:
1244
+ pass
1245
+ return message
1246
+
1247
+
1248
+ def _is_stale_fallback_error(text: str | None) -> bool:
1249
+ """The gateway rejected `ad`'s own tool schema — an outdated CLI, not the task."""
1250
+ t = text or ""
1251
+ return "INVALID_ARGUMENT" in t and "function_declarations" in t
1252
+
1253
+
1142
1254
  def _fallback_argv(cli: str, *args: str) -> list[str]:
1143
1255
  # npm installs a .cmd shim on Windows — same reason as the claude spawn.
1144
1256
  cmd = [cli, *args]
@@ -1325,8 +1437,13 @@ async def run_fallback_cli(
1325
1437
  extra_env: dict[str, str] | None = None,
1326
1438
  tier: str | None = None,
1327
1439
  provider: str | None = None,
1440
+ _upgraded: bool = False,
1328
1441
  ) -> dict[str, Any]:
1329
- """Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run."""
1442
+ """Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run.
1443
+
1444
+ An outdated `ad` fails its first request (its tool schema is rejected) before
1445
+ touching anything, so that one failure upgrades the CLI and runs again, once.
1446
+ """
1330
1447
  cli = await ensure_fallback_cli()
1331
1448
  if not cli:
1332
1449
  return _result(ok=False, error="AI Draftsman CLI (`ad`) not found on this "
@@ -1392,6 +1509,17 @@ async def run_fallback_cli(
1392
1509
  continue
1393
1510
  if proc.returncode != 0 or not isinstance(data, dict) or "result" not in data:
1394
1511
  err = err_text[-500:] or f"aidraftsman exited with code {proc.returncode}"
1512
+ if _is_stale_fallback_error(err) and not _upgraded:
1513
+ print("aidraftsman rejected its own tool list — the CLI is outdated; updating it",
1514
+ flush=True)
1515
+ done = await asyncio.to_thread(upgrade_fallback_cli)
1516
+ if done:
1517
+ print(done, flush=True)
1518
+ if done.startswith("AI Draftsman CLI updated"):
1519
+ return await run_fallback_cli(
1520
+ prompt, project_path, timeout=timeout, github_token=github_token,
1521
+ extra_env=extra_env, tier=tier, provider=provider, _upgraded=True)
1522
+ err += f" — the AI Draftsman CLI is outdated; run `{FALLBACK_INSTALL_HINT}`"
1395
1523
  auth_expired = proc.returncode == 2 or _is_auth_error(err)
1396
1524
  if auth_expired:
1397
1525
  err += " — log the fallback in with `agentos fallback login`"
@@ -1310,13 +1310,15 @@ def main() -> None:
1310
1310
  # report before → after. Does NOT start, stop, or touch the running loop —
1311
1311
  # a running daemon keeps its current code until it is restarted. Honours
1312
1312
  # AGENTOS_NO_UPDATE=1 (auto_update no-ops).
1313
- from sdk.updater import auto_update, current_version
1314
- before = current_version()
1315
- print("checking for SDK updates …")
1316
- auto_update(quiet=True)
1317
- after = current_version()
1318
- if after != before:
1319
- print(f"✓ updated {before} → {after}")
1313
+ from sdk.updater import auto_update
1314
+ # NOT quiet: auto_update's own report is the only honest answer. Quiet, a
1315
+ # failed pip (a locked .exe on Windows, no network, …) left the version
1316
+ # unchanged and this command printed "already up to date" — so boxes sat
1317
+ # on 1.6.51 / 1.6.73 for days while PyPI had newer, and nobody could tell.
1318
+ # The report says "still on vX — vY is published", quotes pip's error and
1319
+ # prints the manual fix command.
1320
+ changed, before, after = auto_update(quiet=False)
1321
+ if changed:
1320
1322
  # Reload the control-panel server so newly-added /api/ui/* routes take
1321
1323
  # effect — a running server keeps the code it imported at boot. Only
1322
1324
  # restarts a managed (systemd/launchd/schtask) service; a manually-run
@@ -1338,8 +1340,6 @@ def main() -> None:
1338
1340
  if pid:
1339
1341
  print("note: a background loop is running the OLD version — "
1340
1342
  "`agentos-auto stop` then `agentos-auto` to run the new one.")
1341
- else:
1342
- print(f"already up to date (v{after})")
1343
1343
 
1344
1344
  # One-time GitHub history backfill — the last 50 days of THIS user's PRs.
1345
1345
  # Fired from `update` because that is the moment every machine passes
@@ -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:
@@ -564,6 +564,9 @@ def auto_update(quiet: bool = False) -> tuple[bool, str, str | None]:
564
564
  """
565
565
  cur = current_version()
566
566
  if os.getenv("AGENTOS_NO_UPDATE"):
567
+ if not quiet:
568
+ _say(f"⚠ updates are switched off (AGENTOS_NO_UPDATE is set) — staying on v{cur}. "
569
+ "Remove it from the environment / ~/.agentos/.env to update.")
567
570
  return (False, cur, None)
568
571
 
569
572
  latest = latest_pypi_version()
@@ -573,6 +576,13 @@ def auto_update(quiet: bool = False) -> tuple[bool, str, str | None]:
573
576
  # Nothing to do on a PyPI install that is already at (or past) the published
574
577
  # version — a git install still checks its branch, which PyPI cannot answer for.
575
578
  if not is_github and not behind_pypi:
579
+ if not quiet:
580
+ if latest:
581
+ _say(f"✓ already up to date (v{cur}).")
582
+ else:
583
+ # Not "up to date": we never found out. Saying so is the difference
584
+ # between a box that is current and one that cannot reach PyPI.
585
+ _say(f"⚠ could not reach PyPI to check for updates — staying on v{cur}.")
576
586
  audit_changed = _ensure_activity_audit(quiet)
577
587
  # Already current — just self-heal the service, restarting only if the
578
588
  # audit setup changed something the running panel must pick up.
@@ -637,6 +647,13 @@ def auto_update_and_reexec(module: str, argv: list[str]) -> None:
637
647
  if os.getenv("AGENTOS_NO_UPDATE") or os.getenv("_AGENTOS_REEXEC"):
638
648
  return
639
649
  updated, _cur, _latest = auto_update(quiet=False)
650
+ try:
651
+ from sdk.local_exec import maybe_upgrade_fallback_cli
652
+ fallback_note = maybe_upgrade_fallback_cli()
653
+ if fallback_note:
654
+ _say(("✓ " if fallback_note.startswith("AI Draftsman CLI updated") else "⚠ ") + fallback_note)
655
+ except Exception: # noqa: BLE001 — the fallback CLI must never block a run
656
+ pass
640
657
 
641
658
  # A self-upgrade of a RUNNING package can land broken on Windows (the active
642
659
  # agentos-auto.exe / loaded files can't be replaced mid-pip — pip uninstalls
@@ -659,8 +676,13 @@ def auto_update_and_reexec(module: str, argv: list[str]) -> None:
659
676
  if not updated:
660
677
  return
661
678
  _say("restarting on the updated version …\n")
662
- result = subprocess.run(
663
- [sys.executable, "-m", module, *argv],
664
- env={**os.environ, "_AGENTOS_REEXEC": "1"},
665
- )
679
+ try:
680
+ result = subprocess.run(
681
+ [sys.executable, "-m", module, *argv],
682
+ env={**os.environ, "_AGENTOS_REEXEC": "1"},
683
+ )
684
+ except KeyboardInterrupt:
685
+ # Ctrl+C reaches the child too, which stops cleanly and says how to resume;
686
+ # the parent only waits for it, so it leaves without a traceback.
687
+ sys.exit(130)
666
688
  sys.exit(result.returncode)
@@ -40,6 +40,11 @@ logger = logging.getLogger("sdk.vps_identity")
40
40
  #: A machine does not change into (or out of) a VPS often; a definite answer is
41
41
  #: reused this long so a 5-minute shipper does not fetch the profile every tick.
42
42
  CACHE_TTL_S = 6 * 3600.0
43
+ #: A "no" is re-asked much sooner. The usual "no" is "no VPS is assigned to this
44
+ #: user" on a box set up before its allocation existed — and holding that for 6 h
45
+ #: kept a freshly assigned VPS silent (no health, activity discarded) for the rest
46
+ #: of the working day. Re-asking costs one profile GET every few minutes.
47
+ NEGATIVE_CACHE_TTL_S = 10 * 60.0
43
48
 
44
49
  _TRUTHY = {"1", "true", "yes", "on"}
45
50
  _FALSEY = {"0", "false", "no", "off"}
@@ -146,7 +151,8 @@ async def is_assigned_vps(client: "BackendClient | None" = None) -> bool | None:
146
151
  if forced is not None:
147
152
  return forced
148
153
  now = time.monotonic()
149
- if _cache["value"] is not None and now - _cache["at"] < CACHE_TTL_S:
154
+ ttl = CACHE_TTL_S if _cache["value"] else NEGATIVE_CACHE_TTL_S
155
+ if _cache["value"] is not None and now - _cache["at"] < ttl:
150
156
  return _cache["value"]
151
157
  try:
152
158
  if client is None: