devops-bot-sdk 1.6.76__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.76 → devops_bot_sdk-1.6.79}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
  4. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/cli.py +6 -3
  6. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/local_exec.py +135 -7
  7. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/run_auto.py +9 -9
  8. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/updater.py +26 -4
  9. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/vps_identity.py +7 -1
  10. devops_bot_sdk-1.6.79/tests/test_fallback_cli_upgrade.py +147 -0
  11. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_updater_reports_the_truth.py +57 -0
  12. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_vps_only_reporting.py +27 -0
  13. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/README.md +0 -0
  14. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  15. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  16. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/requires.txt +0 -0
  17. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  18. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/pyproject.toml +0 -0
  19. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/__init__.py +0 -0
  20. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/classifier.py +0 -0
  21. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/__init__.py +0 -0
  22. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/common.py +0 -0
  23. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/linux_auditd.py +0 -0
  24. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/macos_eslogger.py +0 -0
  25. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/windows_eventlog.py +0 -0
  26. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/models.py +0 -0
  27. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/os_setup.py +0 -0
  28. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/recorder.py +0 -0
  29. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/redact.py +0 -0
  30. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/settings.py +0 -0
  31. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/shipper.py +0 -0
  32. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/activity/spool.py +0 -0
  33. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agentd.py +0 -0
  34. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/__init__.py +0 -0
  35. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/activity.py +0 -0
  36. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/builder.py +0 -0
  37. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/chat.py +0 -0
  38. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/connectors.py +0 -0
  39. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/engines/__init__.py +0 -0
  40. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/engines/claude_code.py +0 -0
  41. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/engines/http.py +0 -0
  42. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/engines/mcp.py +0 -0
  43. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/engines/n8n.py +0 -0
  44. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/engines/shell.py +0 -0
  45. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/integrations.py +0 -0
  46. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/inventory.py +0 -0
  47. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/library/__init__.py +0 -0
  48. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/manifest.py +0 -0
  49. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_studio.py +0 -0
  50. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_template.py +0 -0
  51. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_vars.py +0 -0
  52. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/run_log.py +0 -0
  53. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/schedules.py +0 -0
  54. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/secrets.py +0 -0
  55. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/ticket_run.py +0 -0
  56. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/usage.py +0 -0
  57. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/agents/varstore.py +0 -0
  58. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/browser_profile.py +0 -0
  59. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/client.py +0 -0
  60. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/collectors/__init__.py +0 -0
  61. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/collectors/files.py +0 -0
  62. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/collectors/process.py +0 -0
  63. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/collectors/screenshot.py +0 -0
  64. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/config.py +0 -0
  65. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/crucial.py +0 -0
  66. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/custom_agents.py +0 -0
  67. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/departments.py +0 -0
  68. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/design_verify/__init__.py +0 -0
  69. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/design_verify/assets.py +0 -0
  70. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/design_verify/bootstrap.py +0 -0
  71. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/design_verify/browser.py +0 -0
  72. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/design_verify/compare.py +0 -0
  73. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/design_verify/loop.py +0 -0
  74. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/estimator.py +0 -0
  75. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/exceptions.py +0 -0
  76. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/firewall.py +0 -0
  77. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/formats.py +0 -0
  78. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/git_ops.py +0 -0
  79. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/github_fetch.py +0 -0
  80. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/github_ingest.py +0 -0
  81. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/graph_recon.py +0 -0
  82. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/graphify.py +0 -0
  83. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/hooks/__init__.py +0 -0
  84. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/hooks/crucial_guard.py +0 -0
  85. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/intake.py +0 -0
  86. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ipc/__init__.py +0 -0
  87. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ipc/electron_bridge.py +0 -0
  88. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ipc/handlers.py +0 -0
  89. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/journal.py +0 -0
  90. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/memory_guard.py +0 -0
  91. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/models/__init__.py +0 -0
  92. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/models/envelope.py +0 -0
  93. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/models/requests.py +0 -0
  94. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/models/responses.py +0 -0
  95. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/models/snapshots.py +0 -0
  96. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/phase_timer.py +0 -0
  97. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/plan_run.py +0 -0
  98. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/platform_compat.py +0 -0
  99. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/prd_progress.py +0 -0
  100. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/proof_readers.py +0 -0
  101. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/providers.py +0 -0
  102. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/py.typed +0 -0
  103. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/resume.py +0 -0
  104. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/router.py +0 -0
  105. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/schedule_tickets.py +0 -0
  106. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/scheduler.py +0 -0
  107. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/self_report.py +0 -0
  108. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/self_report_local.py +0 -0
  109. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/sse.py +0 -0
  110. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/swagger.py +0 -0
  111. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/task_file.py +0 -0
  112. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/task_types.py +0 -0
  113. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/test.py +0 -0
  114. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/test_pipeline.py +0 -0
  115. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ticket_fields.py +0 -0
  116. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/tickets.py +0 -0
  117. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/tiers/__init__.py +0 -0
  118. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/tiers/mini_agent.py +0 -0
  119. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/__init__.py +0 -0
  120. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/api.py +0 -0
  121. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/dashboard.py +0 -0
  122. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/health.py +0 -0
  123. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/ip_allowlist.py +0 -0
  124. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/jobs.py +0 -0
  125. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/models.py +0 -0
  126. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/runner.py +0 -0
  127. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/security.py +0 -0
  128. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/server.py +0 -0
  129. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/service.py +0 -0
  130. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/session.py +0 -0
  131. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/setup_manager.py +0 -0
  132. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/agents.html +0 -0
  133. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/app.js +0 -0
  134. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/chat.html +0 -0
  135. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/custom-agents.html +0 -0
  136. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/default-agents.html +0 -0
  137. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/index.html +0 -0
  138. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/report.html +0 -0
  139. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/runs.html +0 -0
  140. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/schedules.html +0 -0
  141. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/setup.html +0 -0
  142. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/styles.css +0 -0
  143. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/static/tickets.html +0 -0
  144. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/ui/swagger.py +0 -0
  145. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/url_access.py +0 -0
  146. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/vps_health.py +0 -0
  147. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/sdk/write_hook.py +0 -0
  148. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/setup.cfg +0 -0
  149. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_activity_claude_runs.py +0 -0
  150. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_activity_collectors.py +0 -0
  151. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_activity_core.py +0 -0
  152. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_activity_live_tracking.py +0 -0
  153. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_activity_macos.py +0 -0
  154. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_activity_os_setup.py +0 -0
  155. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_activity_pipeline.py +0 -0
  156. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_activity.py +0 -0
  157. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_activity_tracking.py +0 -0
  158. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_chat_features.py +0 -0
  159. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_crud_cli.py +0 -0
  160. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_listing.py +0 -0
  161. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_prompt_async.py +0 -0
  162. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_prompt_models.py +0 -0
  163. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_run_ticket.py +0 -0
  164. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_schedules.py +0 -0
  165. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agent_workspace_settings.py +0 -0
  166. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_agents_connectors_builder.py +0 -0
  167. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_allowed_tools_config.py +0 -0
  168. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_auto_agent_dispatch.py +0 -0
  169. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_browser_profile.py +0 -0
  170. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_cheap_tier.py +0 -0
  171. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_client_jira_projects.py +0 -0
  172. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_config_token_diagnostics.py +0 -0
  173. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_configure_token_owner.py +0 -0
  174. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_console_encoding.py +0 -0
  175. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_crash_recovery.py +0 -0
  176. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_departments.py +0 -0
  177. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_done_transition.py +0 -0
  178. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_epic_decompose_run.py +0 -0
  179. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_epic_inherited_agent_id.py +0 -0
  180. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_epic_session_isolation.py +0 -0
  181. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_estimator.py +0 -0
  182. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_fallback_cli.py +0 -0
  183. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_firewall_open_on_update.py +0 -0
  184. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_formats.py +0 -0
  185. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_git_action.py +0 -0
  186. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_github_fetch.py +0 -0
  187. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_github_ingest.py +0 -0
  188. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_graph_recon.py +0 -0
  189. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_graphify_build.py +0 -0
  190. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_graphify_hook_install.py +0 -0
  191. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_health.py +0 -0
  192. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_intake_job_outcome.py +0 -0
  193. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_intake_plan_review.py +0 -0
  194. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_journal.py +0 -0
  195. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_local_exec_provider.py +0 -0
  196. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_mcp_route_contract.py +0 -0
  197. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_memory_guard.py +0 -0
  198. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_phase_timer.py +0 -0
  199. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_plan_new_command.py +0 -0
  200. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_plan_run_activity.py +0 -0
  201. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_pr_identity.py +0 -0
  202. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_prd_artifact.py +0 -0
  203. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_project_path_resolution.py +0 -0
  204. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_proof_readers.py +0 -0
  205. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_providers.py +0 -0
  206. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_recon_session_isolation.py +0 -0
  207. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_resume.py +0 -0
  208. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_router.py +0 -0
  209. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_run_auto_agent_only.py +0 -0
  210. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_run_outputs.py +0 -0
  211. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_run_verdict_is_honest.py +0 -0
  212. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_schedule_api.py +0 -0
  213. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_schedule_outputs_and_plans.py +0 -0
  214. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_schedule_tickets.py +0 -0
  215. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_scheduler.py +0 -0
  216. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_scheduler_transient_retry.py +0 -0
  217. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_self_report.py +0 -0
  218. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_service_token_warning.py +0 -0
  219. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_stale_panel_restart.py +0 -0
  220. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_swagger_command.py +0 -0
  221. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_task_file_and_plan_run.py +0 -0
  222. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_task_file_format_doc.py +0 -0
  223. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_ticket_cli.py +0 -0
  224. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_ticket_fields.py +0 -0
  225. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_ticket_run_resolve_by_id.py +0 -0
  226. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_ticket_speed_fixes.py +0 -0
  227. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_tier_flag_parity.py +0 -0
  228. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_ui_gate.py +0 -0
  229. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_ui_lifespan.py +0 -0
  230. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_update_timer.py +0 -0
  231. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_updater_extras.py +0 -0
  232. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_updater_pep668.py +0 -0
  233. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_url_access.py +0 -0
  234. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_vps_health.py +0 -0
  235. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_windows_loop_identity.py +0 -0
  236. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_windows_panel_task.py +0 -0
  237. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.79}/tests/test_windows_task_paths.py +0 -0
  238. {devops_bot_sdk-1.6.76 → 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.76
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.76
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
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.76
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.76" # 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
@@ -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:
@@ -0,0 +1,147 @@
1
+ """An outdated AI Draftsman CLI is replaced, not left to fail every ticket.
2
+
3
+ Before 1.1.1 `ad` sent a tool schema Gemini rejects (an integer `enum` in
4
+ create_artifact), so every fallback run died on its first request with
5
+ "400 INVALID_ARGUMENT … function_declarations[5]". The fix shipped under a new npm
6
+ name, and machines kept the old CLI: the SDK used whatever was installed.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import json
12
+ import subprocess
13
+ import sys
14
+
15
+ import pytest
16
+
17
+ from sdk import local_exec, updater
18
+
19
+ _STALE = ("API error: INVALID_REQUEST: 400 INVALID_ARGUMENT Invalid value at "
20
+ "'tools[0].function_declarations[5].parameters.properties[8].value' (TYPE_STRING), 1")
21
+
22
+
23
+ @pytest.fixture()
24
+ def npm(monkeypatch):
25
+ """A scripted npm: `installed` is what `npm ls -g` reports; calls are recorded."""
26
+ state = {"installed": {}, "calls": [], "install_rc": 0}
27
+ monkeypatch.delenv("AGENTOS_FALLBACK_CLI", raising=False)
28
+ monkeypatch.setattr(local_exec, "_setting", lambda key: "")
29
+ monkeypatch.setattr(local_exec, "_npm_cmd", lambda *a: ["npm", *a])
30
+
31
+ def run(cmd, **kw):
32
+ args = cmd[1:]
33
+ state["calls"].append(args)
34
+ out, rc = "", 0
35
+ if args[:2] == ["ls", "-g"]:
36
+ out = json.dumps({"dependencies": {k: {"version": v} for k, v in state["installed"].items()}})
37
+ elif args[:2] == ["uninstall", "-g"]:
38
+ state["installed"].pop(args[2], None)
39
+ elif args[:2] == ["install", "-g"]:
40
+ rc = state["install_rc"]
41
+ if rc == 0:
42
+ state["installed"][local_exec.FALLBACK_PACKAGE] = "1.1.1"
43
+ return subprocess.CompletedProcess(cmd, rc, out, "EACCES" if rc else "")
44
+
45
+ monkeypatch.setattr(local_exec.subprocess, "run", run)
46
+ return state
47
+
48
+
49
+ def _changes(state):
50
+ return [c for c in state["calls"] if c[0] in ("install", "uninstall")]
51
+
52
+
53
+ class TestUpgrade:
54
+ def test_old_package_name_is_replaced(self, npm):
55
+ npm["installed"] = {"@aidraftsman/cli": "1.0.1"}
56
+ msg = local_exec.upgrade_fallback_cli()
57
+ assert _changes(npm) == [["uninstall", "-g", "@aidraftsman/cli"],
58
+ ["install", "-g", f"{local_exec.FALLBACK_PACKAGE}@latest"]]
59
+ assert msg == "AI Draftsman CLI updated 1.0.1 → 1.1.1"
60
+
61
+ def test_an_old_release_is_upgraded(self, npm):
62
+ npm["installed"] = {local_exec.FALLBACK_PACKAGE: "1.1.0"}
63
+ assert local_exec.upgrade_fallback_cli().startswith("AI Draftsman CLI updated 1.1.0")
64
+
65
+ def test_a_current_release_is_left_alone(self, npm):
66
+ npm["installed"] = {local_exec.FALLBACK_PACKAGE: "1.2.0"}
67
+ assert local_exec.upgrade_fallback_cli() == ""
68
+ assert _changes(npm) == []
69
+
70
+ def test_never_installs_unasked(self, npm):
71
+ assert local_exec.upgrade_fallback_cli() == ""
72
+ assert _changes(npm) == []
73
+
74
+ def test_install_missing_installs(self, npm):
75
+ assert local_exec.upgrade_fallback_cli(install_missing=True).endswith("→ 1.1.1")
76
+
77
+ def test_a_failed_npm_says_how_to_do_it_by_hand(self, npm):
78
+ npm["installed"] = {"@aidraftsman/cli": "1.0.1"}
79
+ npm["install_rc"] = 1
80
+ msg = local_exec.upgrade_fallback_cli()
81
+ assert "could not update" in msg and local_exec.FALLBACK_INSTALL_HINT in msg
82
+
83
+ def test_an_owner_managed_cli_is_not_touched(self, npm, monkeypatch):
84
+ npm["installed"] = {"@aidraftsman/cli": "1.0.1"}
85
+ monkeypatch.setattr(local_exec, "_setting",
86
+ lambda key: "/opt/ad" if key == "AGENTOS_FALLBACK_CLI" else "")
87
+ assert local_exec.upgrade_fallback_cli() == ""
88
+ assert npm["calls"] == []
89
+
90
+ def test_start_up_check_runs_at_most_every_few_hours(self, npm, monkeypatch, tmp_path):
91
+ monkeypatch.setattr(local_exec.Path, "home", lambda: tmp_path)
92
+ npm["installed"] = {local_exec.FALLBACK_PACKAGE: "1.2.0"}
93
+ local_exec.maybe_upgrade_fallback_cli()
94
+ local_exec.maybe_upgrade_fallback_cli()
95
+ assert sum(1 for c in npm["calls"] if c[0] == "ls") == 1
96
+
97
+ def test_hint_names_the_new_package(self):
98
+ assert "@consultancy-outfit/ai-draftsman-cli" in local_exec.FALLBACK_INSTALL_HINT
99
+
100
+
101
+ @pytest.mark.skipif(sys.platform == "win32", reason="POSIX script stand-in")
102
+ class TestSelfHeal:
103
+ def _cli(self, tmp_path):
104
+ """Fails like an outdated `ad` on its first run, succeeds on the second."""
105
+ count = tmp_path / "runs"
106
+ script = tmp_path / "ad"
107
+ script.write_text(f"""#!{sys.executable}
108
+ import json, sys, pathlib
109
+ sys.stdin.read()
110
+ p = pathlib.Path({str(count)!r}); n = int(p.read_text()) if p.exists() else 0; p.write_text(str(n + 1))
111
+ if n == 0:
112
+ sys.stderr.write({_STALE!r}); sys.exit(1)
113
+ print(json.dumps({{"result": "done", "usage": {{}}}}))
114
+ """)
115
+ script.chmod(0o755)
116
+ return script, count
117
+
118
+ def test_upgrades_once_and_runs_again(self, tmp_path, monkeypatch):
119
+ cli, count = self._cli(tmp_path)
120
+ monkeypatch.setattr(local_exec, "fallback_cli_path", lambda: str(cli))
121
+ monkeypatch.setattr(local_exec, "upgrade_fallback_cli",
122
+ lambda **k: "AI Draftsman CLI updated 1.0.1 → 1.1.1")
123
+ res = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
124
+ assert res["ok"] is True and count.read_text() == "2"
125
+
126
+ def test_no_upgrade_means_no_retry_and_a_clear_error(self, tmp_path, monkeypatch):
127
+ cli, count = self._cli(tmp_path)
128
+ monkeypatch.setattr(local_exec, "fallback_cli_path", lambda: str(cli))
129
+ monkeypatch.setattr(local_exec, "upgrade_fallback_cli", lambda **k: "")
130
+ res = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
131
+ assert res["ok"] is False and count.read_text() == "1"
132
+ assert "outdated" in res["error"] and local_exec.FALLBACK_INSTALL_HINT in res["error"]
133
+
134
+
135
+ def test_ctrl_c_while_waiting_for_the_restarted_run_exits_quietly(monkeypatch):
136
+ monkeypatch.delenv("AGENTOS_NO_UPDATE", raising=False)
137
+ monkeypatch.delenv("_AGENTOS_REEXEC", raising=False)
138
+ monkeypatch.setattr(updater, "auto_update", lambda quiet=False: (True, "1", "2"))
139
+ monkeypatch.setattr(updater, "_verify_install", lambda: True)
140
+ monkeypatch.setattr(local_exec, "maybe_upgrade_fallback_cli", lambda: "")
141
+
142
+ def interrupted(*a, **k):
143
+ raise KeyboardInterrupt
144
+ monkeypatch.setattr(updater.subprocess, "run", interrupted)
145
+ with pytest.raises(SystemExit) as exc:
146
+ updater.auto_update_and_reexec("sdk.run_auto", ["ticket", "WEL-9"])
147
+ assert exc.value.code == 130
@@ -257,6 +257,29 @@ class TestReporting:
257
257
  assert "could not check" in text
258
258
  assert "publickey" in text
259
259
 
260
+ def test_a_current_pypi_install_says_so(self):
261
+ _updated, said = self._run(installed="1.6.75", pypi="1.6.75", result=None,
262
+ is_github=False)
263
+ assert "already up to date (v1.6.75)" in "\n".join(said)
264
+
265
+ def test_unreachable_pypi_is_not_called_up_to_date(self):
266
+ """A PyPI install that cannot reach PyPI never found out — say that."""
267
+ _updated, said = self._run(installed="1.6.73", pypi=None, result=None,
268
+ is_github=False)
269
+ text = "\n".join(said)
270
+ assert "already up to date" not in text
271
+ assert "could not reach PyPI" in text
272
+
273
+ def test_updates_switched_off_is_said_out_loud(self, monkeypatch):
274
+ """AGENTOS_NO_UPDATE used to return silently, which the update command
275
+ then printed as "already up to date"."""
276
+ monkeypatch.setenv("AGENTOS_NO_UPDATE", "1")
277
+ said: list[str] = []
278
+ with patch.object(updater, "current_version", return_value="1.6.51"), \
279
+ patch.object(updater, "_say", said.append):
280
+ updater.auto_update(quiet=False)
281
+ assert "AGENTOS_NO_UPDATE" in "\n".join(said)
282
+
260
283
  def test_quiet_says_nothing_at_all(self):
261
284
  result = updater.UpgradeResult(False, "1.4.0", "1.4.0", True, "no-op")
262
285
  said: list[str] = []
@@ -326,3 +349,37 @@ class TestPipInstall:
326
349
  ])
327
350
  def test_failure_labels_name_the_source_without_leaking_it(url, label):
328
351
  assert updater._source_label(url) == label
352
+
353
+
354
+ # ── `agentos-auto update`: the command must not override the report ───
355
+ class TestUpdateCommand:
356
+ """The command called ``auto_update(quiet=True)`` and then printed its own
357
+ "already up to date" whenever the version had not moved — so a failed pip
358
+ (Mubashar's 1.6.73 box with 1.6.75 published, Tauqeer's 1.6.51 box) read as
359
+ current, and the report above was never shown to anyone."""
360
+
361
+ def _run_update(self, monkeypatch, capsys, result):
362
+ from sdk import run_auto
363
+
364
+ calls: list[bool] = []
365
+
366
+ def fake_auto_update(quiet=False):
367
+ calls.append(quiet)
368
+ return result
369
+ monkeypatch.setattr(updater, "auto_update", fake_auto_update)
370
+ monkeypatch.setattr(sys, "argv", ["agentos-auto", "update"])
371
+ monkeypatch.setattr(run_auto, "_daemon_running", lambda: None)
372
+ import sdk.github_ingest as gi
373
+ monkeypatch.setattr(gi, "run_backfill_once", lambda: {"ran": False})
374
+ import sdk.vps_health as vh
375
+ monkeypatch.setattr(vh, "enabled", lambda: False)
376
+ run_auto.main()
377
+ return calls, capsys.readouterr().out
378
+
379
+ def test_it_asks_for_the_full_report(self, monkeypatch, capsys):
380
+ calls, _ = self._run_update(monkeypatch, capsys, (False, "1.6.73", None))
381
+ assert calls == [False]
382
+
383
+ def test_it_never_prints_its_own_up_to_date(self, monkeypatch, capsys):
384
+ _, out = self._run_update(monkeypatch, capsys, (False, "1.6.73", None))
385
+ assert "already up to date" not in out
@@ -91,6 +91,33 @@ class TestIsAssignedVps:
91
91
  assert asyncio.run(vps_identity.is_assigned_vps(client)) is True
92
92
  assert client.profile_calls == 1
93
93
 
94
+ def test_a_newly_assigned_vps_is_picked_up_within_minutes(self, monkeypatch):
95
+ """agentos installed BEFORE the allocation existed: the first answer is
96
+ "no VPS assigned". Cached for 6 h, that kept a freshly assigned VPS silent
97
+ — no health, activity discarded — for the rest of the day."""
98
+ _holds(monkeypatch, "217.160.157.148")
99
+ clock = {"t": 1000.0}
100
+ monkeypatch.setattr(vps_identity.time, "monotonic", lambda: clock["t"])
101
+
102
+ before = _Client(assigned="")
103
+ assert asyncio.run(vps_identity.is_assigned_vps(before)) is False
104
+ after = _Client(assigned="217.160.157.148")
105
+ clock["t"] += 60
106
+ assert asyncio.run(vps_identity.is_assigned_vps(after)) is False # still cached
107
+ clock["t"] += vps_identity.NEGATIVE_CACHE_TTL_S
108
+ assert asyncio.run(vps_identity.is_assigned_vps(after)) is True # re-asked
109
+ assert vps_identity.NEGATIVE_CACHE_TTL_S <= 15 * 60
110
+
111
+ def test_a_yes_is_still_cached_for_hours(self, monkeypatch):
112
+ _holds(monkeypatch, "77.68.23.23")
113
+ clock = {"t": 1000.0}
114
+ monkeypatch.setattr(vps_identity.time, "monotonic", lambda: clock["t"])
115
+ client = _Client()
116
+ asyncio.run(vps_identity.is_assigned_vps(client))
117
+ clock["t"] += vps_identity.NEGATIVE_CACHE_TTL_S * 3
118
+ asyncio.run(vps_identity.is_assigned_vps(client))
119
+ assert client.profile_calls == 1
120
+
94
121
  @pytest.mark.parametrize("raw, want", [("1", True), ("true", True), ("0", False), ("off", False)])
95
122
  def test_override_wins_without_a_profile_call(self, monkeypatch, raw, want):
96
123
  monkeypatch.setenv("AGENTOS_IS_VPS", raw)