devops-bot-sdk 1.6.79__tar.gz → 1.6.85__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 (252) hide show
  1. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/SOURCES.txt +14 -0
  4. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/pyproject.toml +1 -1
  5. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/__init__.py +2 -2
  6. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agentd.py +5 -7
  7. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/schedules.py +12 -4
  8. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/browser_profile.py +3 -9
  9. devops_bot_sdk-1.6.85/sdk/claude_usage.py +136 -0
  10. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/cli.py +16 -1
  11. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/client.py +94 -4
  12. devops_bot_sdk-1.6.85/sdk/fallback_handoff.py +202 -0
  13. devops_bot_sdk-1.6.85/sdk/helpdesk.py +751 -0
  14. devops_bot_sdk-1.6.85/sdk/helpdesk_kb.md +126 -0
  15. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ipc/handlers.py +110 -3
  16. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/local_exec.py +26 -6
  17. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/run_auto.py +420 -35
  18. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/scheduler.py +54 -10
  19. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/api.py +69 -4
  20. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/models.py +24 -4
  21. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/app.js +135 -11
  22. devops_bot_sdk-1.6.85/sdk/ui/static/help.html +48 -0
  23. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/schedules.html +24 -1
  24. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/styles.css +16 -0
  25. devops_bot_sdk-1.6.85/tests/test_auto_ticket_queue.py +150 -0
  26. devops_bot_sdk-1.6.85/tests/test_claude_usage_gate.py +63 -0
  27. devops_bot_sdk-1.6.85/tests/test_create_safe_retry.py +108 -0
  28. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_fallback_cli.py +3 -2
  29. devops_bot_sdk-1.6.85/tests/test_helpdesk.py +277 -0
  30. devops_bot_sdk-1.6.85/tests/test_limit_fallback_handoff.py +164 -0
  31. devops_bot_sdk-1.6.85/tests/test_panel_help.py +151 -0
  32. devops_bot_sdk-1.6.85/tests/test_panel_ticket_schedules.py +86 -0
  33. devops_bot_sdk-1.6.85/tests/test_ticket_schedule_and_agent.py +183 -0
  34. devops_bot_sdk-1.6.85/tests/test_windows_stop_and_pidfile.py +106 -0
  35. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/README.md +0 -0
  36. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  37. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  38. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/requires.txt +0 -0
  39. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  40. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/__init__.py +0 -0
  41. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/classifier.py +0 -0
  42. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/__init__.py +0 -0
  43. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/common.py +0 -0
  44. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/linux_auditd.py +0 -0
  45. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/macos_eslogger.py +0 -0
  46. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/windows_eventlog.py +0 -0
  47. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/models.py +0 -0
  48. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/os_setup.py +0 -0
  49. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/recorder.py +0 -0
  50. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/redact.py +0 -0
  51. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/settings.py +0 -0
  52. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/shipper.py +0 -0
  53. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/spool.py +0 -0
  54. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/__init__.py +0 -0
  55. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/activity.py +0 -0
  56. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/builder.py +0 -0
  57. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/chat.py +0 -0
  58. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/connectors.py +0 -0
  59. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/__init__.py +0 -0
  60. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/claude_code.py +0 -0
  61. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/http.py +0 -0
  62. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/mcp.py +0 -0
  63. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/n8n.py +0 -0
  64. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/shell.py +0 -0
  65. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/integrations.py +0 -0
  66. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/inventory.py +0 -0
  67. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/library/__init__.py +0 -0
  68. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/manifest.py +0 -0
  69. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_studio.py +0 -0
  70. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_template.py +0 -0
  71. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_vars.py +0 -0
  72. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/run_log.py +0 -0
  73. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/secrets.py +0 -0
  74. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/ticket_run.py +0 -0
  75. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/usage.py +0 -0
  76. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/varstore.py +0 -0
  77. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/__init__.py +0 -0
  78. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/files.py +0 -0
  79. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/process.py +0 -0
  80. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/screenshot.py +0 -0
  81. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/config.py +0 -0
  82. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/crucial.py +0 -0
  83. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/custom_agents.py +0 -0
  84. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/departments.py +0 -0
  85. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/__init__.py +0 -0
  86. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/assets.py +0 -0
  87. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/bootstrap.py +0 -0
  88. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/browser.py +0 -0
  89. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/compare.py +0 -0
  90. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/loop.py +0 -0
  91. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/estimator.py +0 -0
  92. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/exceptions.py +0 -0
  93. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/firewall.py +0 -0
  94. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/formats.py +0 -0
  95. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/git_ops.py +0 -0
  96. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/github_fetch.py +0 -0
  97. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/github_ingest.py +0 -0
  98. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/graph_recon.py +0 -0
  99. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/graphify.py +0 -0
  100. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/hooks/__init__.py +0 -0
  101. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/hooks/crucial_guard.py +0 -0
  102. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/intake.py +0 -0
  103. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ipc/__init__.py +0 -0
  104. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ipc/electron_bridge.py +0 -0
  105. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/journal.py +0 -0
  106. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/memory_guard.py +0 -0
  107. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/__init__.py +0 -0
  108. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/envelope.py +0 -0
  109. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/requests.py +0 -0
  110. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/responses.py +0 -0
  111. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/snapshots.py +0 -0
  112. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/phase_timer.py +0 -0
  113. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/plan_run.py +0 -0
  114. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/platform_compat.py +0 -0
  115. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/prd_progress.py +0 -0
  116. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/proof_readers.py +0 -0
  117. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/providers.py +0 -0
  118. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/py.typed +0 -0
  119. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/resume.py +0 -0
  120. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/router.py +0 -0
  121. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/schedule_tickets.py +0 -0
  122. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/self_report.py +0 -0
  123. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/self_report_local.py +0 -0
  124. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/sse.py +0 -0
  125. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/swagger.py +0 -0
  126. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/task_file.py +0 -0
  127. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/task_types.py +0 -0
  128. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/test.py +0 -0
  129. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/test_pipeline.py +0 -0
  130. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ticket_fields.py +0 -0
  131. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/tickets.py +0 -0
  132. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/tiers/__init__.py +0 -0
  133. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/tiers/mini_agent.py +0 -0
  134. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/__init__.py +0 -0
  135. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/dashboard.py +0 -0
  136. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/health.py +0 -0
  137. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/ip_allowlist.py +0 -0
  138. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/jobs.py +0 -0
  139. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/runner.py +0 -0
  140. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/security.py +0 -0
  141. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/server.py +0 -0
  142. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/service.py +0 -0
  143. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/session.py +0 -0
  144. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/setup_manager.py +0 -0
  145. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/agents.html +0 -0
  146. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/chat.html +0 -0
  147. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/custom-agents.html +0 -0
  148. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/default-agents.html +0 -0
  149. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/index.html +0 -0
  150. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/report.html +0 -0
  151. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/runs.html +0 -0
  152. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/setup.html +0 -0
  153. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/tickets.html +0 -0
  154. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/swagger.py +0 -0
  155. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/updater.py +0 -0
  156. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/url_access.py +0 -0
  157. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/vps_health.py +0 -0
  158. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/vps_identity.py +0 -0
  159. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/write_hook.py +0 -0
  160. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/setup.cfg +0 -0
  161. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_claude_runs.py +0 -0
  162. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_collectors.py +0 -0
  163. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_core.py +0 -0
  164. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_live_tracking.py +0 -0
  165. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_macos.py +0 -0
  166. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_os_setup.py +0 -0
  167. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_pipeline.py +0 -0
  168. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_activity.py +0 -0
  169. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_activity_tracking.py +0 -0
  170. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_chat_features.py +0 -0
  171. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_crud_cli.py +0 -0
  172. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_listing.py +0 -0
  173. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_prompt_async.py +0 -0
  174. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_prompt_models.py +0 -0
  175. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_run_ticket.py +0 -0
  176. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_schedules.py +0 -0
  177. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_workspace_settings.py +0 -0
  178. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agents_connectors_builder.py +0 -0
  179. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_allowed_tools_config.py +0 -0
  180. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_auto_agent_dispatch.py +0 -0
  181. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_browser_profile.py +0 -0
  182. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_cheap_tier.py +0 -0
  183. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_client_jira_projects.py +0 -0
  184. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_config_token_diagnostics.py +0 -0
  185. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_configure_token_owner.py +0 -0
  186. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_console_encoding.py +0 -0
  187. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_crash_recovery.py +0 -0
  188. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_departments.py +0 -0
  189. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_done_transition.py +0 -0
  190. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_epic_decompose_run.py +0 -0
  191. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_epic_inherited_agent_id.py +0 -0
  192. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_epic_session_isolation.py +0 -0
  193. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_estimator.py +0 -0
  194. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_fallback_cli_upgrade.py +0 -0
  195. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_firewall_open_on_update.py +0 -0
  196. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_formats.py +0 -0
  197. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_git_action.py +0 -0
  198. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_github_fetch.py +0 -0
  199. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_github_ingest.py +0 -0
  200. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_graph_recon.py +0 -0
  201. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_graphify_build.py +0 -0
  202. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_graphify_hook_install.py +0 -0
  203. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_health.py +0 -0
  204. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_intake_job_outcome.py +0 -0
  205. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_intake_plan_review.py +0 -0
  206. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_journal.py +0 -0
  207. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_local_exec_provider.py +0 -0
  208. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_mcp_route_contract.py +0 -0
  209. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_memory_guard.py +0 -0
  210. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_phase_timer.py +0 -0
  211. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_plan_new_command.py +0 -0
  212. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_plan_run_activity.py +0 -0
  213. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_pr_identity.py +0 -0
  214. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_prd_artifact.py +0 -0
  215. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_project_path_resolution.py +0 -0
  216. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_proof_readers.py +0 -0
  217. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_providers.py +0 -0
  218. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_recon_session_isolation.py +0 -0
  219. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_resume.py +0 -0
  220. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_router.py +0 -0
  221. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_run_auto_agent_only.py +0 -0
  222. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_run_outputs.py +0 -0
  223. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_run_verdict_is_honest.py +0 -0
  224. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_schedule_api.py +0 -0
  225. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_schedule_outputs_and_plans.py +0 -0
  226. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_schedule_tickets.py +0 -0
  227. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_scheduler.py +0 -0
  228. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_scheduler_transient_retry.py +0 -0
  229. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_self_report.py +0 -0
  230. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_service_token_warning.py +0 -0
  231. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_stale_panel_restart.py +0 -0
  232. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_swagger_command.py +0 -0
  233. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_task_file_and_plan_run.py +0 -0
  234. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_task_file_format_doc.py +0 -0
  235. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_cli.py +0 -0
  236. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_fields.py +0 -0
  237. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_run_resolve_by_id.py +0 -0
  238. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_speed_fixes.py +0 -0
  239. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_tier_flag_parity.py +0 -0
  240. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ui_gate.py +0 -0
  241. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ui_lifespan.py +0 -0
  242. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_update_timer.py +0 -0
  243. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_updater_extras.py +0 -0
  244. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_updater_pep668.py +0 -0
  245. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_updater_reports_the_truth.py +0 -0
  246. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_url_access.py +0 -0
  247. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_vps_health.py +0 -0
  248. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_vps_only_reporting.py +0 -0
  249. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_windows_loop_identity.py +0 -0
  250. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_windows_panel_task.py +0 -0
  251. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_windows_task_paths.py +0 -0
  252. {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/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.79
3
+ Version: 1.6.85
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.79
3
+ Version: 1.6.85
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
@@ -134,6 +138,7 @@ sdk/ui/static/app.js
134
138
  sdk/ui/static/chat.html
135
139
  sdk/ui/static/custom-agents.html
136
140
  sdk/ui/static/default-agents.html
141
+ sdk/ui/static/help.html
137
142
  sdk/ui/static/index.html
138
143
  sdk/ui/static/report.html
139
144
  sdk/ui/static/runs.html
@@ -161,13 +166,16 @@ tests/test_agent_workspace_settings.py
161
166
  tests/test_agents_connectors_builder.py
162
167
  tests/test_allowed_tools_config.py
163
168
  tests/test_auto_agent_dispatch.py
169
+ tests/test_auto_ticket_queue.py
164
170
  tests/test_browser_profile.py
165
171
  tests/test_cheap_tier.py
172
+ tests/test_claude_usage_gate.py
166
173
  tests/test_client_jira_projects.py
167
174
  tests/test_config_token_diagnostics.py
168
175
  tests/test_configure_token_owner.py
169
176
  tests/test_console_encoding.py
170
177
  tests/test_crash_recovery.py
178
+ tests/test_create_safe_retry.py
171
179
  tests/test_departments.py
172
180
  tests/test_done_transition.py
173
181
  tests/test_epic_decompose_run.py
@@ -185,12 +193,16 @@ tests/test_graph_recon.py
185
193
  tests/test_graphify_build.py
186
194
  tests/test_graphify_hook_install.py
187
195
  tests/test_health.py
196
+ tests/test_helpdesk.py
188
197
  tests/test_intake_job_outcome.py
189
198
  tests/test_intake_plan_review.py
190
199
  tests/test_journal.py
200
+ tests/test_limit_fallback_handoff.py
191
201
  tests/test_local_exec_provider.py
192
202
  tests/test_mcp_route_contract.py
193
203
  tests/test_memory_guard.py
204
+ tests/test_panel_help.py
205
+ tests/test_panel_ticket_schedules.py
194
206
  tests/test_phase_timer.py
195
207
  tests/test_plan_new_command.py
196
208
  tests/test_plan_run_activity.py
@@ -219,6 +231,7 @@ tests/test_task_file_format_doc.py
219
231
  tests/test_ticket_cli.py
220
232
  tests/test_ticket_fields.py
221
233
  tests/test_ticket_run_resolve_by_id.py
234
+ tests/test_ticket_schedule_and_agent.py
222
235
  tests/test_ticket_speed_fixes.py
223
236
  tests/test_tier_flag_parity.py
224
237
  tests/test_ui_gate.py
@@ -232,5 +245,6 @@ tests/test_vps_health.py
232
245
  tests/test_vps_only_reporting.py
233
246
  tests/test_windows_loop_identity.py
234
247
  tests/test_windows_panel_task.py
248
+ tests/test_windows_stop_and_pidfile.py
235
249
  tests/test_windows_task_paths.py
236
250
  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.79
3
+ Version: 1.6.85
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.79" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.85" # 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>")
@@ -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,