devops-bot-sdk 1.6.76__tar.gz → 1.6.84__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 (250) hide show
  1. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/devops_bot_sdk.egg-info/SOURCES.txt +13 -0
  4. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/pyproject.toml +1 -1
  5. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/__init__.py +2 -2
  6. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agentd.py +5 -7
  7. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/schedules.py +12 -4
  8. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/browser_profile.py +3 -9
  9. devops_bot_sdk-1.6.84/sdk/claude_usage.py +136 -0
  10. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/cli.py +22 -4
  11. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/client.py +94 -4
  12. devops_bot_sdk-1.6.84/sdk/fallback_handoff.py +202 -0
  13. devops_bot_sdk-1.6.84/sdk/helpdesk.py +419 -0
  14. devops_bot_sdk-1.6.84/sdk/helpdesk_kb.md +121 -0
  15. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ipc/handlers.py +110 -3
  16. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/local_exec.py +156 -11
  17. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/run_auto.py +429 -44
  18. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/scheduler.py +54 -10
  19. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/api.py +22 -4
  20. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/models.py +10 -4
  21. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/app.js +55 -11
  22. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/schedules.html +24 -1
  23. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/styles.css +7 -0
  24. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/updater.py +26 -4
  25. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/vps_identity.py +7 -1
  26. devops_bot_sdk-1.6.84/tests/test_auto_ticket_queue.py +150 -0
  27. devops_bot_sdk-1.6.84/tests/test_claude_usage_gate.py +63 -0
  28. devops_bot_sdk-1.6.84/tests/test_create_safe_retry.py +108 -0
  29. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_fallback_cli.py +3 -2
  30. devops_bot_sdk-1.6.84/tests/test_fallback_cli_upgrade.py +147 -0
  31. devops_bot_sdk-1.6.84/tests/test_helpdesk.py +134 -0
  32. devops_bot_sdk-1.6.84/tests/test_limit_fallback_handoff.py +164 -0
  33. devops_bot_sdk-1.6.84/tests/test_panel_ticket_schedules.py +86 -0
  34. devops_bot_sdk-1.6.84/tests/test_ticket_schedule_and_agent.py +183 -0
  35. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_updater_reports_the_truth.py +57 -0
  36. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_vps_only_reporting.py +27 -0
  37. devops_bot_sdk-1.6.84/tests/test_windows_stop_and_pidfile.py +106 -0
  38. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/README.md +0 -0
  39. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  40. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  41. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/devops_bot_sdk.egg-info/requires.txt +0 -0
  42. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  43. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/__init__.py +0 -0
  44. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/classifier.py +0 -0
  45. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/collectors/__init__.py +0 -0
  46. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/collectors/common.py +0 -0
  47. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/collectors/linux_auditd.py +0 -0
  48. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/collectors/macos_eslogger.py +0 -0
  49. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/collectors/windows_eventlog.py +0 -0
  50. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/models.py +0 -0
  51. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/os_setup.py +0 -0
  52. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/recorder.py +0 -0
  53. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/redact.py +0 -0
  54. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/settings.py +0 -0
  55. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/shipper.py +0 -0
  56. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/activity/spool.py +0 -0
  57. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/__init__.py +0 -0
  58. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/activity.py +0 -0
  59. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/builder.py +0 -0
  60. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/chat.py +0 -0
  61. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/connectors.py +0 -0
  62. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/engines/__init__.py +0 -0
  63. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/engines/claude_code.py +0 -0
  64. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/engines/http.py +0 -0
  65. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/engines/mcp.py +0 -0
  66. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/engines/n8n.py +0 -0
  67. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/engines/shell.py +0 -0
  68. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/integrations.py +0 -0
  69. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/inventory.py +0 -0
  70. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/library/__init__.py +0 -0
  71. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/manifest.py +0 -0
  72. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/prompt_studio.py +0 -0
  73. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/prompt_template.py +0 -0
  74. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/prompt_vars.py +0 -0
  75. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/run_log.py +0 -0
  76. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/secrets.py +0 -0
  77. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/ticket_run.py +0 -0
  78. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/usage.py +0 -0
  79. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/agents/varstore.py +0 -0
  80. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/collectors/__init__.py +0 -0
  81. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/collectors/files.py +0 -0
  82. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/collectors/process.py +0 -0
  83. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/collectors/screenshot.py +0 -0
  84. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/config.py +0 -0
  85. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/crucial.py +0 -0
  86. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/custom_agents.py +0 -0
  87. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/departments.py +0 -0
  88. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/design_verify/__init__.py +0 -0
  89. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/design_verify/assets.py +0 -0
  90. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/design_verify/bootstrap.py +0 -0
  91. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/design_verify/browser.py +0 -0
  92. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/design_verify/compare.py +0 -0
  93. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/design_verify/loop.py +0 -0
  94. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/estimator.py +0 -0
  95. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/exceptions.py +0 -0
  96. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/firewall.py +0 -0
  97. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/formats.py +0 -0
  98. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/git_ops.py +0 -0
  99. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/github_fetch.py +0 -0
  100. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/github_ingest.py +0 -0
  101. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/graph_recon.py +0 -0
  102. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/graphify.py +0 -0
  103. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/hooks/__init__.py +0 -0
  104. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/hooks/crucial_guard.py +0 -0
  105. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/intake.py +0 -0
  106. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ipc/__init__.py +0 -0
  107. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ipc/electron_bridge.py +0 -0
  108. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/journal.py +0 -0
  109. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/memory_guard.py +0 -0
  110. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/models/__init__.py +0 -0
  111. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/models/envelope.py +0 -0
  112. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/models/requests.py +0 -0
  113. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/models/responses.py +0 -0
  114. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/models/snapshots.py +0 -0
  115. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/phase_timer.py +0 -0
  116. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/plan_run.py +0 -0
  117. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/platform_compat.py +0 -0
  118. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/prd_progress.py +0 -0
  119. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/proof_readers.py +0 -0
  120. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/providers.py +0 -0
  121. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/py.typed +0 -0
  122. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/resume.py +0 -0
  123. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/router.py +0 -0
  124. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/schedule_tickets.py +0 -0
  125. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/self_report.py +0 -0
  126. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/self_report_local.py +0 -0
  127. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/sse.py +0 -0
  128. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/swagger.py +0 -0
  129. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/task_file.py +0 -0
  130. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/task_types.py +0 -0
  131. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/test.py +0 -0
  132. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/test_pipeline.py +0 -0
  133. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ticket_fields.py +0 -0
  134. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/tickets.py +0 -0
  135. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/tiers/__init__.py +0 -0
  136. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/tiers/mini_agent.py +0 -0
  137. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/__init__.py +0 -0
  138. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/dashboard.py +0 -0
  139. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/health.py +0 -0
  140. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/ip_allowlist.py +0 -0
  141. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/jobs.py +0 -0
  142. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/runner.py +0 -0
  143. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/security.py +0 -0
  144. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/server.py +0 -0
  145. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/service.py +0 -0
  146. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/session.py +0 -0
  147. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/setup_manager.py +0 -0
  148. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/agents.html +0 -0
  149. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/chat.html +0 -0
  150. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/custom-agents.html +0 -0
  151. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/default-agents.html +0 -0
  152. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/index.html +0 -0
  153. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/report.html +0 -0
  154. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/runs.html +0 -0
  155. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/setup.html +0 -0
  156. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/static/tickets.html +0 -0
  157. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/ui/swagger.py +0 -0
  158. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/url_access.py +0 -0
  159. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/vps_health.py +0 -0
  160. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/sdk/write_hook.py +0 -0
  161. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/setup.cfg +0 -0
  162. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_activity_claude_runs.py +0 -0
  163. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_activity_collectors.py +0 -0
  164. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_activity_core.py +0 -0
  165. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_activity_live_tracking.py +0 -0
  166. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_activity_macos.py +0 -0
  167. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_activity_os_setup.py +0 -0
  168. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_activity_pipeline.py +0 -0
  169. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_activity.py +0 -0
  170. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_activity_tracking.py +0 -0
  171. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_chat_features.py +0 -0
  172. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_crud_cli.py +0 -0
  173. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_listing.py +0 -0
  174. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_prompt_async.py +0 -0
  175. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_prompt_models.py +0 -0
  176. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_run_ticket.py +0 -0
  177. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_schedules.py +0 -0
  178. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agent_workspace_settings.py +0 -0
  179. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_agents_connectors_builder.py +0 -0
  180. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_allowed_tools_config.py +0 -0
  181. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_auto_agent_dispatch.py +0 -0
  182. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_browser_profile.py +0 -0
  183. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_cheap_tier.py +0 -0
  184. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_client_jira_projects.py +0 -0
  185. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_config_token_diagnostics.py +0 -0
  186. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_configure_token_owner.py +0 -0
  187. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_console_encoding.py +0 -0
  188. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_crash_recovery.py +0 -0
  189. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_departments.py +0 -0
  190. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_done_transition.py +0 -0
  191. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_epic_decompose_run.py +0 -0
  192. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_epic_inherited_agent_id.py +0 -0
  193. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_epic_session_isolation.py +0 -0
  194. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_estimator.py +0 -0
  195. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_firewall_open_on_update.py +0 -0
  196. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_formats.py +0 -0
  197. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_git_action.py +0 -0
  198. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_github_fetch.py +0 -0
  199. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_github_ingest.py +0 -0
  200. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_graph_recon.py +0 -0
  201. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_graphify_build.py +0 -0
  202. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_graphify_hook_install.py +0 -0
  203. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_health.py +0 -0
  204. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_intake_job_outcome.py +0 -0
  205. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_intake_plan_review.py +0 -0
  206. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_journal.py +0 -0
  207. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_local_exec_provider.py +0 -0
  208. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_mcp_route_contract.py +0 -0
  209. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_memory_guard.py +0 -0
  210. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_phase_timer.py +0 -0
  211. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_plan_new_command.py +0 -0
  212. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_plan_run_activity.py +0 -0
  213. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_pr_identity.py +0 -0
  214. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_prd_artifact.py +0 -0
  215. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_project_path_resolution.py +0 -0
  216. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_proof_readers.py +0 -0
  217. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_providers.py +0 -0
  218. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_recon_session_isolation.py +0 -0
  219. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_resume.py +0 -0
  220. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_router.py +0 -0
  221. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_run_auto_agent_only.py +0 -0
  222. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_run_outputs.py +0 -0
  223. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_run_verdict_is_honest.py +0 -0
  224. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_schedule_api.py +0 -0
  225. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_schedule_outputs_and_plans.py +0 -0
  226. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_schedule_tickets.py +0 -0
  227. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_scheduler.py +0 -0
  228. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_scheduler_transient_retry.py +0 -0
  229. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_self_report.py +0 -0
  230. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_service_token_warning.py +0 -0
  231. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_stale_panel_restart.py +0 -0
  232. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_swagger_command.py +0 -0
  233. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_task_file_and_plan_run.py +0 -0
  234. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_task_file_format_doc.py +0 -0
  235. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_ticket_cli.py +0 -0
  236. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_ticket_fields.py +0 -0
  237. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_ticket_run_resolve_by_id.py +0 -0
  238. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_ticket_speed_fixes.py +0 -0
  239. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_tier_flag_parity.py +0 -0
  240. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_ui_gate.py +0 -0
  241. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_ui_lifespan.py +0 -0
  242. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_update_timer.py +0 -0
  243. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_updater_extras.py +0 -0
  244. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_updater_pep668.py +0 -0
  245. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_url_access.py +0 -0
  246. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_vps_health.py +0 -0
  247. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_windows_loop_identity.py +0 -0
  248. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_windows_panel_task.py +0 -0
  249. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/tests/test_windows_task_paths.py +0 -0
  250. {devops_bot_sdk-1.6.76 → devops_bot_sdk-1.6.84}/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.84
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.84
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
@@ -9,6 +9,7 @@ devops_bot_sdk.egg-info/top_level.txt
9
9
  sdk/__init__.py
10
10
  sdk/agentd.py
11
11
  sdk/browser_profile.py
12
+ sdk/claude_usage.py
12
13
  sdk/cli.py
13
14
  sdk/client.py
14
15
  sdk/config.py
@@ -17,6 +18,7 @@ sdk/custom_agents.py
17
18
  sdk/departments.py
18
19
  sdk/estimator.py
19
20
  sdk/exceptions.py
21
+ sdk/fallback_handoff.py
20
22
  sdk/firewall.py
21
23
  sdk/formats.py
22
24
  sdk/git_ops.py
@@ -24,6 +26,8 @@ sdk/github_fetch.py
24
26
  sdk/github_ingest.py
25
27
  sdk/graph_recon.py
26
28
  sdk/graphify.py
29
+ sdk/helpdesk.py
30
+ sdk/helpdesk_kb.md
27
31
  sdk/intake.py
28
32
  sdk/journal.py
29
33
  sdk/local_exec.py
@@ -161,13 +165,16 @@ tests/test_agent_workspace_settings.py
161
165
  tests/test_agents_connectors_builder.py
162
166
  tests/test_allowed_tools_config.py
163
167
  tests/test_auto_agent_dispatch.py
168
+ tests/test_auto_ticket_queue.py
164
169
  tests/test_browser_profile.py
165
170
  tests/test_cheap_tier.py
171
+ tests/test_claude_usage_gate.py
166
172
  tests/test_client_jira_projects.py
167
173
  tests/test_config_token_diagnostics.py
168
174
  tests/test_configure_token_owner.py
169
175
  tests/test_console_encoding.py
170
176
  tests/test_crash_recovery.py
177
+ tests/test_create_safe_retry.py
171
178
  tests/test_departments.py
172
179
  tests/test_done_transition.py
173
180
  tests/test_epic_decompose_run.py
@@ -175,6 +182,7 @@ tests/test_epic_inherited_agent_id.py
175
182
  tests/test_epic_session_isolation.py
176
183
  tests/test_estimator.py
177
184
  tests/test_fallback_cli.py
185
+ tests/test_fallback_cli_upgrade.py
178
186
  tests/test_firewall_open_on_update.py
179
187
  tests/test_formats.py
180
188
  tests/test_git_action.py
@@ -184,12 +192,15 @@ tests/test_graph_recon.py
184
192
  tests/test_graphify_build.py
185
193
  tests/test_graphify_hook_install.py
186
194
  tests/test_health.py
195
+ tests/test_helpdesk.py
187
196
  tests/test_intake_job_outcome.py
188
197
  tests/test_intake_plan_review.py
189
198
  tests/test_journal.py
199
+ tests/test_limit_fallback_handoff.py
190
200
  tests/test_local_exec_provider.py
191
201
  tests/test_mcp_route_contract.py
192
202
  tests/test_memory_guard.py
203
+ tests/test_panel_ticket_schedules.py
193
204
  tests/test_phase_timer.py
194
205
  tests/test_plan_new_command.py
195
206
  tests/test_plan_run_activity.py
@@ -218,6 +229,7 @@ tests/test_task_file_format_doc.py
218
229
  tests/test_ticket_cli.py
219
230
  tests/test_ticket_fields.py
220
231
  tests/test_ticket_run_resolve_by_id.py
232
+ tests/test_ticket_schedule_and_agent.py
221
233
  tests/test_ticket_speed_fixes.py
222
234
  tests/test_tier_flag_parity.py
223
235
  tests/test_ui_gate.py
@@ -231,5 +243,6 @@ tests/test_vps_health.py
231
243
  tests/test_vps_only_reporting.py
232
244
  tests/test_windows_loop_identity.py
233
245
  tests/test_windows_panel_task.py
246
+ tests/test_windows_stop_and_pidfile.py
234
247
  tests/test_windows_task_paths.py
235
248
  tests/test_write_hook.py
@@ -68,7 +68,7 @@ where = ["."]
68
68
  include = ["sdk*"]
69
69
 
70
70
  [tool.setuptools.package-data]
71
- sdk = ["py.typed", "ui/static/*"]
71
+ sdk = ["py.typed", "ui/static/*", "helpdesk_kb.md"]
72
72
 
73
73
  [tool.ruff]
74
74
  line-length = 100
@@ -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.84
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.84" # 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
@@ -176,13 +176,11 @@ def _read_pid() -> int | None:
176
176
 
177
177
 
178
178
  def _pid_alive(pid: int) -> bool:
179
- try:
180
- os.kill(pid, 0) # signal 0 → existence/permission check, no actual signal
181
- except ProcessLookupError:
182
- return False
183
- except PermissionError:
184
- return True # exists but owned by another user
185
- return True
179
+ """Delegates to run_auto's probe — NOT ``os.kill(pid, 0)``, which on Windows is
180
+ ``TerminateProcess`` (it kills a live agentd) and raises a bare OSError
181
+ (WinError 87) for a dead pid, crashing `status`/`start` on a stale pidfile."""
182
+ from sdk import run_auto
183
+ return run_auto._pid_alive(pid)
186
184
 
187
185
 
188
186
  def _daemon_running() -> int | None:
@@ -49,9 +49,10 @@ MAX_CONSECUTIVE_FAILURES = 3
49
49
 
50
50
  _SLUG_RE = re.compile(r"[^A-Za-z0-9_.-]+")
51
51
 
52
- # What a schedule fires: a web-app agent, or a local plan (a task file run through
53
- # ``sdk.plan_run`` — the same pipeline a ticket gets, with no ticket).
54
- KINDS = ("agent", "plan")
52
+ # What a schedule fires: a web-app agent, a local plan (a task file run through
53
+ # ``sdk.plan_run`` — the same pipeline a ticket gets, with no ticket), or named
54
+ # Jira tickets (``agentos-auto ticket <keys> [--agent ID]``, queued behind a busy loop).
55
+ KINDS = ("agent", "plan", "tickets")
55
56
 
56
57
  # Where a run's files are copied, in addition to the agent's own output folder:
57
58
  # ``flat`` drops them straight into ``output_dir``; ``timestamp`` makes a fresh
@@ -272,12 +273,15 @@ def create(agent: str, cron: str = "", *, timezone_name: str = "Asia/Karachi",
272
273
  variables: dict | None = None, agent_id: str = "",
273
274
  scope: str = "custom", enabled: bool = True,
274
275
  run_at: str = "", kind: str = "agent", task_file: str = "",
275
- output_dir: str = "", output_layout: str = "flat") -> dict:
276
+ output_dir: str = "", output_layout: str = "flat",
277
+ ticket_keys: list[str] | None = None) -> dict:
276
278
  """Persist a new schedule. Raises ``ValueError`` on anything unusable.
277
279
 
278
280
  Exactly one of ``cron`` (recurring) and ``run_at`` (once, at that local date
279
281
  and time) must be given. ``kind="plan"`` fires ``task_file`` through the local
280
282
  coding pipeline instead of an agent; ``agent`` is then just its display name.
283
+ ``kind="tickets"`` runs ``ticket_keys`` through ``agentos-auto ticket``, with
284
+ ``agent_id`` (when set) overriding each ticket's own agent.
281
285
  """
282
286
  if not _valid_agent_name(agent):
283
287
  raise ValueError(f"invalid agent name: {agent!r}")
@@ -286,6 +290,9 @@ def create(agent: str, cron: str = "", *, timezone_name: str = "Asia/Karachi",
286
290
  raise ValueError(f"kind must be one of {', '.join(KINDS)}")
287
291
  if kind == "plan" and not str(task_file or "").strip():
288
292
  raise ValueError("a plan schedule needs a task file")
293
+ keys = [str(k).strip().upper() for k in (ticket_keys or []) if str(k).strip()]
294
+ if kind == "tickets" and not keys:
295
+ raise ValueError("a tickets schedule needs at least one ticket key")
289
296
 
290
297
  cron = (cron or "").strip()
291
298
  run_at_utc = ""
@@ -310,6 +317,7 @@ def create(agent: str, cron: str = "", *, timezone_name: str = "Asia/Karachi",
310
317
  # is by name, so a rename would otherwise orphan the schedule silently.
311
318
  "agent_id": str(agent_id or ""),
312
319
  "task_file": str(task_file or "").strip(),
320
+ "ticket_keys": keys,
313
321
  "scope": scope,
314
322
  "cron": cron,
315
323
  "run_at": run_at_utc,
@@ -202,15 +202,9 @@ def busy_pid() -> int | None:
202
202
  pid = int(raw)
203
203
  except ValueError:
204
204
  return None
205
- try:
206
- os.kill(pid, 0)
207
- except ProcessLookupError:
208
- return None
209
- except PermissionError:
210
- return pid # alive, owned by someone else
211
- except OSError:
212
- return None
213
- return pid
205
+ # run_auto's probe: ``os.kill(pid, 0)`` on Windows is TerminateProcess.
206
+ from sdk import run_auto
207
+ return pid if run_auto._pid_alive(pid) else None
214
208
 
215
209
 
216
210
  async def fetch_dom(url: str, *, timeout: float = _FETCH_TIMEOUT_S) -> Fetched:
@@ -0,0 +1,136 @@
1
+ """Claude plan usage gate — keeps scheduled runs inside the subscription limits.
2
+
3
+ The scheduler fires agents around the clock to keep the VPS busy. Each run is a
4
+ whole ``claude`` process billed against the same 5-hour and 7-day windows the
5
+ person's own ticket work uses, so filler work must yield BEFORE the windows
6
+ fill, not after a run dies with "usage limit reached" (``sdk.local_exec`` only
7
+ detects the limit once it has already been hit).
8
+
9
+ The numbers come from the same endpoint Claude Code's own ``/usage`` reads,
10
+ authenticated with the CLI's OAuth token in ``~/.claude/.credentials.json``.
11
+ It is not a documented API, so this module FAILS OPEN: no credentials, a
12
+ network error or an unexpected shape all report "allowed". The limit-hit
13
+ detection in ``local_exec`` is still the backstop in that case.
14
+
15
+ Config — process env OR ``~/.agentos/.env``, all optional:
16
+ ``AGENTOS_USAGE_GATE`` ``0``/``false``/``no``/``off`` disables the gate.
17
+ ``AGENTOS_USAGE_MAX_5H_PCT`` skip scheduled runs at/above this 5-hour % (default 60).
18
+ ``AGENTOS_USAGE_MAX_7D_PCT`` skip scheduled runs at/above this 7-day % (default 75).
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import logging
24
+ import os
25
+ import time
26
+ import urllib.request
27
+ from pathlib import Path
28
+
29
+ logger = logging.getLogger("sdk.claude_usage")
30
+
31
+ USAGE_URL = "https://api.anthropic.com/api/oauth/usage"
32
+ CREDENTIALS = Path.home() / ".claude" / ".credentials.json"
33
+
34
+ DEFAULT_MAX_5H_PCT = 60.0
35
+ DEFAULT_MAX_7D_PCT = 75.0
36
+
37
+ # One fetch serves every schedule due in the same few minutes.
38
+ _CACHE_TTL_S = 300.0
39
+ _TIMEOUT_S = 10.0
40
+
41
+ _cache: dict = {"at": 0.0, "data": None}
42
+
43
+
44
+ def _setting(name: str) -> str:
45
+ raw = os.environ.get(name)
46
+ if raw is not None and str(raw).strip() != "":
47
+ return str(raw).strip()
48
+ try:
49
+ from sdk.config import _dotenv_value
50
+ return (_dotenv_value(name) or "").strip()
51
+ except Exception: # noqa: BLE001
52
+ return ""
53
+
54
+
55
+ def enabled() -> bool:
56
+ return (_setting("AGENTOS_USAGE_GATE") or "1").lower() not in ("0", "false", "no", "off")
57
+
58
+
59
+ def _pct(name: str, default: float) -> float:
60
+ raw = _setting(name)
61
+ if not raw:
62
+ return default
63
+ try:
64
+ return max(0.0, float(raw))
65
+ except ValueError:
66
+ logger.warning("claude_usage.bad_threshold %s=%r — using %s", name, raw, default)
67
+ return default
68
+
69
+
70
+ def _token() -> str:
71
+ try:
72
+ data = json.loads(CREDENTIALS.read_text(encoding="utf-8"))
73
+ return str((data.get("claudeAiOauth") or {}).get("accessToken") or "")
74
+ except (OSError, ValueError, AttributeError):
75
+ return ""
76
+
77
+
78
+ def fetch(*, force: bool = False) -> dict | None:
79
+ """The raw usage document, cached for a few minutes. None when unavailable."""
80
+ now = time.monotonic()
81
+ if not force and _cache["data"] is not None and now - _cache["at"] < _CACHE_TTL_S:
82
+ return _cache["data"]
83
+ token = _token()
84
+ if not token:
85
+ return None
86
+ req = urllib.request.Request(USAGE_URL, headers={
87
+ "Authorization": f"Bearer {token}",
88
+ "anthropic-beta": "oauth-2025-04-20",
89
+ "User-Agent": "agentos-sdk",
90
+ })
91
+ try:
92
+ with urllib.request.urlopen(req, timeout=_TIMEOUT_S) as resp:
93
+ data = json.loads(resp.read().decode("utf-8"))
94
+ except Exception as exc: # noqa: BLE001 — fail open, never break the scheduler
95
+ logger.info("claude_usage.fetch_failed err=%s", exc)
96
+ return None
97
+ if not isinstance(data, dict):
98
+ return None
99
+ _cache.update(at=now, data=data)
100
+ return data
101
+
102
+
103
+ def _window(data: dict, key: str) -> tuple[float | None, str]:
104
+ win = data.get(key)
105
+ if not isinstance(win, dict):
106
+ return None, ""
107
+ try:
108
+ return float(win.get("utilization")), str(win.get("resets_at") or "")
109
+ except (TypeError, ValueError):
110
+ return None, ""
111
+
112
+
113
+ def check(data: dict | None = None) -> dict:
114
+ """``{"allowed", "reason", "five_hour", "seven_day"}`` for a scheduled run now.
115
+
116
+ ``allowed`` is False only when a window is known to be at/above its
117
+ threshold; anything unknown is allowed.
118
+ """
119
+ out = {"allowed": True, "reason": "", "five_hour": None, "seven_day": None}
120
+ if not enabled():
121
+ return out
122
+ data = data if data is not None else fetch()
123
+ if not data:
124
+ return out
125
+ for key, label, env, default in (
126
+ ("five_hour", "5-hour", "AGENTOS_USAGE_MAX_5H_PCT", DEFAULT_MAX_5H_PCT),
127
+ ("seven_day", "7-day", "AGENTOS_USAGE_MAX_7D_PCT", DEFAULT_MAX_7D_PCT),
128
+ ):
129
+ used, resets = _window(data, key)
130
+ out[key] = used
131
+ limit = _pct(env, default)
132
+ if used is not None and used >= limit and out["allowed"]:
133
+ out["allowed"] = False
134
+ out["reason"] = (f"Claude {label} usage {used:.0f}% ≥ {limit:.0f}% cap"
135
+ + (f" (resets {resets})" if resets else ""))
136
+ return out
@@ -231,13 +231,28 @@ def main() -> None:
231
231
  _fallback_command(rest)
232
232
  return
233
233
 
234
+ if noun == "help":
235
+ # `agentos help -h` is the command list; `agentos help <problem>` (or bare
236
+ # `agentos help` on a terminal, which asks for one) is DevOps Bot Help.
237
+ if rest[:1] in (["-h"], ["--help"]):
238
+ _print_usage()
239
+ return
240
+ from sdk import helpdesk
241
+ sys.exit(helpdesk.cli(rest))
242
+
243
+ if noun == "doctor":
244
+ from sdk import helpdesk
245
+ sys.exit(helpdesk.doctor())
246
+
234
247
  _print_usage()
235
248
  sys.exit(1)
236
249
 
237
250
 
238
251
  def _print_usage() -> None:
239
- print("usage: agentos <configure|chat|agent|ticket|integration|default-agents|agentd|github|report|browser|fallback|firewall|activity-setup|swagger> ...")
252
+ print("usage: agentos <configure|chat|agent|ticket|integration|default-agents|agentd|github|report|browser|fallback|help|doctor|firewall|activity-setup|swagger> ...")
240
253
  print()
254
+ print(' agentos help "<your problem>" # DevOps Bot Help: reads your logs + the SDK, tells you the fix')
255
+ print(" agentos doctor # collect a diagnostics bundle to send to your admin (no AI)")
241
256
  print(" agentos configure [--rotate]")
242
257
  print(" agentos chat [--product-id ID] [--pv-id ID] [--no-autorun] # requirements → ticket")
243
258
  print(" agentos agent <list|run NAME|chat NAME MSG|logs NAME|enable NAME|disable NAME|rm NAME>")
@@ -1383,7 +1398,7 @@ def _browser_command(args: list[str]) -> None:
1383
1398
  # ── fallback ─────────────────────────────────────────────────────────────
1384
1399
 
1385
1400
  def _fallback_usage() -> None:
1386
- print("usage: agentos fallback install # where `ad` is, or how to install it")
1401
+ print("usage: agentos fallback install # install `ad`, or update an outdated one")
1387
1402
  print(" agentos fallback login [--browser | --api-key [KEY]] [--base-url URL] [--no-browser]")
1388
1403
  print(" agentos fallback status [--verify] # --verify asks the server too")
1389
1404
  print(" agentos fallback logout")
@@ -1411,8 +1426,11 @@ def _fallback_command(args: list[str]) -> None:
1411
1426
  if le._fallback_disabled():
1412
1427
  print("error: the fallback is disabled (AGENTOS_FALLBACK_CLI=off)")
1413
1428
  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.
1429
+ # Installs the CLI when it is missing, and replaces an outdated one (the old
1430
+ # package name, or a release older than FALLBACK_MIN_VERSION).
1431
+ done = le.upgrade_fallback_cli(install_missing=True)
1432
+ if done:
1433
+ print(done)
1416
1434
  cli = le.fallback_cli_path()
1417
1435
  if not cli:
1418
1436
  print(f"error: the AI Draftsman CLI (`ad`) was not found. Install it with:\n"
@@ -19,6 +19,7 @@ import json
19
19
  import logging
20
20
  import os
21
21
  import random
22
+ import re
22
23
  from typing import Any, AsyncGenerator
23
24
 
24
25
  import httpx
@@ -97,6 +98,69 @@ def is_retryable_backend_error(status_code: int, body: str) -> bool:
97
98
  return status_code in (408, 429) or status_code >= 500
98
99
 
99
100
 
101
+ #: How many times set_task_status waits out a 429 before giving up on a ticket.
102
+ _RATE_LIMIT_RETRIES = 3
103
+ _RETRY_AFTER_DEFAULT_S = 60.0
104
+ _RETRY_AFTER_MAX_S = 120.0
105
+
106
+
107
+ def _retry_after_seconds(resp: httpx.Response) -> float:
108
+ """Seconds to wait after a 429: the Retry-After header, else the "retry after N
109
+ seconds" the backend puts in its message, else 60. Capped so a bad value can't
110
+ hang the CLI."""
111
+ raw = resp.headers.get("retry-after", "")
112
+ match = re.search(r"retry after (\d+)", resp.text or "", re.IGNORECASE)
113
+ for candidate in (raw, match.group(1) if match else ""):
114
+ try:
115
+ secs = float(candidate)
116
+ except (TypeError, ValueError):
117
+ continue
118
+ if secs > 0:
119
+ return min(secs, _RETRY_AFTER_MAX_S)
120
+ return _RETRY_AFTER_DEFAULT_S
121
+
122
+
123
+ #: Statuses where the backend refused the request WITHOUT acting on it, so a retry
124
+ #: cannot duplicate a create. 401 is here because the backend intermittently fails
125
+ #: to verify a perfectly good token (its profile lookup blips) — the same request
126
+ #: succeeds a moment later. A plain 500 / read timeout is deliberately NOT: a POST
127
+ #: may already have created the ticket or agent.
128
+ _SAFE_RETRY_STATUSES = (401, 408, 429, 502, 503, 504)
129
+ _SAFE_RETRY_ATTEMPTS = 3
130
+ _SAFE_RETRY_MAX_WAIT_S = 30.0
131
+
132
+
133
+ async def _request_with_safe_retry(send, *, label: str) -> httpx.Response:
134
+ """Call ``send()`` (one HTTP request) and retry only failures where nothing was
135
+ processed.
136
+
137
+ Retries the statuses in ``_SAFE_RETRY_STATUSES``, a saturated DB pool, and a
138
+ connection that never opened (``httpx.ConnectError`` — the request was never
139
+ sent). Backoff 1s/2s/4s, or the server's Retry-After for a 429. Any other
140
+ exception propagates exactly as before.
141
+ """
142
+ for attempt in range(_SAFE_RETRY_ATTEMPTS + 1):
143
+ last = attempt == _SAFE_RETRY_ATTEMPTS
144
+ try:
145
+ resp = await send()
146
+ except httpx.ConnectError as exc:
147
+ if last:
148
+ raise
149
+ reason = f"connect error ({exc})"
150
+ wait = float(2 ** attempt)
151
+ else:
152
+ pool = any(m in (resp.text or "").lower() for m in _POOL_MARKERS)
153
+ if last or not (resp.status_code in _SAFE_RETRY_STATUSES or pool):
154
+ return resp
155
+ reason = f"HTTP {resp.status_code}"
156
+ wait = (min(_retry_after_seconds(resp), _SAFE_RETRY_MAX_WAIT_S)
157
+ if resp.status_code == 429 else float(2 ** attempt))
158
+ logger.warning("client.%s %s — retrying in %ss (%d/%d)", label, reason, wait,
159
+ attempt + 1, _SAFE_RETRY_ATTEMPTS)
160
+ await asyncio.sleep(wait)
161
+ raise AssertionError("unreachable") # pragma: no cover
162
+
163
+
100
164
  def _notify_status(task_id: str, status: str) -> None:
101
165
  for cb in list(_status_observers):
102
166
  try:
@@ -1369,9 +1433,31 @@ class BackendClient:
1369
1433
  k: v for k, v in self._headers.items() if k.lower() != "content-type"
1370
1434
  }
1371
1435
  headers.update({"accept": "*/*", "developer-name": "null", "product": ""})
1436
+ async def _put(client: httpx.AsyncClient, value: str) -> httpx.Response:
1437
+ # A bulk `set-status` (dozens of tickets) trips the backend's rate limit
1438
+ # ("Too many requests. Please retry after 60 seconds."). Wait it out and
1439
+ # retry, so the batch slows down instead of failing every remaining ticket.
1440
+ for attempt in range(_RATE_LIMIT_RETRIES + 1):
1441
+ resp = await client.put(url, files={"status": (None, value)}, headers=headers)
1442
+ if resp.status_code != 429 or attempt == _RATE_LIMIT_RETRIES:
1443
+ return resp
1444
+ wait = _retry_after_seconds(resp)
1445
+ logger.warning("client.set_task_status rate-limited task=%s — waiting %ss "
1446
+ "(retry %d/%d)", task_id, wait, attempt + 1, _RATE_LIMIT_RETRIES)
1447
+ await asyncio.sleep(wait)
1448
+ return resp
1449
+
1372
1450
  try:
1373
1451
  async with httpx.AsyncClient(timeout=20.0) as client:
1374
- resp = await client.put(url, files={"status": (None, status)}, headers=headers)
1452
+ resp = await _put(client, status)
1453
+ # A ticket left In Progress by a loop that died mid-run has no running
1454
+ # timer, and the backend refuses to move it anywhere without one. Start
1455
+ # a timer (re-mark In Progress) and retry once — the same blip the
1456
+ # reject path does before move_to_backlog.
1457
+ if (resp.status_code >= 400 and "no running timer" in resp.text.lower()
1458
+ and status.strip().lower() != "in progress"):
1459
+ await _put(client, "In Progress")
1460
+ resp = await _put(client, status)
1375
1461
  if resp.status_code >= 400:
1376
1462
  logger.warning("client.set_task_status failed task=%s status=%s body=%s",
1377
1463
  task_id, resp.status_code, resp.text[:200])
@@ -1552,7 +1638,9 @@ class BackendClient:
1552
1638
  files = to_multipart(fields, model_used)
1553
1639
  try:
1554
1640
  async with httpx.AsyncClient(timeout=30.0) as client:
1555
- resp = await client.post(url, files=files, headers=headers)
1641
+ resp = await _request_with_safe_retry(
1642
+ lambda: client.post(url, files=files, headers=headers),
1643
+ label="create_task_ml")
1556
1644
  if resp.status_code >= 400:
1557
1645
  try:
1558
1646
  self.last_task_ml_error = resp.json() or {}
@@ -2546,8 +2634,10 @@ class BackendClient:
2546
2634
  setattr(self, err_attr, {})
2547
2635
  try:
2548
2636
  async with httpx.AsyncClient(timeout=timeout) as client:
2549
- resp = await client.request(method, self._url(path), json=json,
2550
- headers=self._headers)
2637
+ resp = await _request_with_safe_retry(
2638
+ lambda: client.request(method, self._url(path), json=json,
2639
+ headers=self._headers),
2640
+ label=f"{method} {path}")
2551
2641
  if resp.status_code >= 400:
2552
2642
  setattr(self, err_attr, self._error_record(resp))
2553
2643
  logger.warning("client.%s %s failed status=%s body=%s", method, path,