devops-bot-sdk 1.6.84__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 (253) hide show
  1. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/SOURCES.txt +2 -0
  4. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/helpdesk.py +347 -15
  6. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/helpdesk_kb.md +5 -0
  7. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/local_exec.py +5 -2
  8. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/api.py +47 -0
  9. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/models.py +14 -0
  10. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/app.js +80 -0
  11. devops_bot_sdk-1.6.85/sdk/ui/static/help.html +48 -0
  12. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/styles.css +9 -0
  13. devops_bot_sdk-1.6.85/tests/test_helpdesk.py +277 -0
  14. devops_bot_sdk-1.6.85/tests/test_panel_help.py +151 -0
  15. devops_bot_sdk-1.6.84/tests/test_helpdesk.py +0 -134
  16. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/README.md +0 -0
  17. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  18. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  19. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/requires.txt +0 -0
  20. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  21. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/pyproject.toml +0 -0
  22. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/__init__.py +0 -0
  23. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/classifier.py +0 -0
  24. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/__init__.py +0 -0
  25. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/common.py +0 -0
  26. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/linux_auditd.py +0 -0
  27. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/macos_eslogger.py +0 -0
  28. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/windows_eventlog.py +0 -0
  29. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/models.py +0 -0
  30. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/os_setup.py +0 -0
  31. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/recorder.py +0 -0
  32. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/redact.py +0 -0
  33. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/settings.py +0 -0
  34. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/shipper.py +0 -0
  35. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/activity/spool.py +0 -0
  36. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agentd.py +0 -0
  37. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/__init__.py +0 -0
  38. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/activity.py +0 -0
  39. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/builder.py +0 -0
  40. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/chat.py +0 -0
  41. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/connectors.py +0 -0
  42. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/engines/__init__.py +0 -0
  43. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/engines/claude_code.py +0 -0
  44. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/engines/http.py +0 -0
  45. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/engines/mcp.py +0 -0
  46. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/engines/n8n.py +0 -0
  47. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/engines/shell.py +0 -0
  48. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/integrations.py +0 -0
  49. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/inventory.py +0 -0
  50. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/library/__init__.py +0 -0
  51. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/manifest.py +0 -0
  52. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_studio.py +0 -0
  53. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_template.py +0 -0
  54. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_vars.py +0 -0
  55. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/run_log.py +0 -0
  56. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/schedules.py +0 -0
  57. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/secrets.py +0 -0
  58. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/ticket_run.py +0 -0
  59. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/usage.py +0 -0
  60. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/agents/varstore.py +0 -0
  61. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/browser_profile.py +0 -0
  62. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/claude_usage.py +0 -0
  63. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/cli.py +0 -0
  64. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/client.py +0 -0
  65. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/collectors/__init__.py +0 -0
  66. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/collectors/files.py +0 -0
  67. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/collectors/process.py +0 -0
  68. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/collectors/screenshot.py +0 -0
  69. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/config.py +0 -0
  70. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/crucial.py +0 -0
  71. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/custom_agents.py +0 -0
  72. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/departments.py +0 -0
  73. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/design_verify/__init__.py +0 -0
  74. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/design_verify/assets.py +0 -0
  75. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/design_verify/bootstrap.py +0 -0
  76. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/design_verify/browser.py +0 -0
  77. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/design_verify/compare.py +0 -0
  78. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/design_verify/loop.py +0 -0
  79. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/estimator.py +0 -0
  80. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/exceptions.py +0 -0
  81. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/fallback_handoff.py +0 -0
  82. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/firewall.py +0 -0
  83. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/formats.py +0 -0
  84. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/git_ops.py +0 -0
  85. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/github_fetch.py +0 -0
  86. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/github_ingest.py +0 -0
  87. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/graph_recon.py +0 -0
  88. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/graphify.py +0 -0
  89. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/hooks/__init__.py +0 -0
  90. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/hooks/crucial_guard.py +0 -0
  91. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/intake.py +0 -0
  92. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ipc/__init__.py +0 -0
  93. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ipc/electron_bridge.py +0 -0
  94. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ipc/handlers.py +0 -0
  95. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/journal.py +0 -0
  96. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/memory_guard.py +0 -0
  97. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/models/__init__.py +0 -0
  98. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/models/envelope.py +0 -0
  99. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/models/requests.py +0 -0
  100. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/models/responses.py +0 -0
  101. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/models/snapshots.py +0 -0
  102. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/phase_timer.py +0 -0
  103. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/plan_run.py +0 -0
  104. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/platform_compat.py +0 -0
  105. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/prd_progress.py +0 -0
  106. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/proof_readers.py +0 -0
  107. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/providers.py +0 -0
  108. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/py.typed +0 -0
  109. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/resume.py +0 -0
  110. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/router.py +0 -0
  111. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/run_auto.py +0 -0
  112. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/schedule_tickets.py +0 -0
  113. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/scheduler.py +0 -0
  114. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/self_report.py +0 -0
  115. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/self_report_local.py +0 -0
  116. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/sse.py +0 -0
  117. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/swagger.py +0 -0
  118. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/task_file.py +0 -0
  119. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/task_types.py +0 -0
  120. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/test.py +0 -0
  121. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/test_pipeline.py +0 -0
  122. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ticket_fields.py +0 -0
  123. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/tickets.py +0 -0
  124. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/tiers/__init__.py +0 -0
  125. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/tiers/mini_agent.py +0 -0
  126. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/__init__.py +0 -0
  127. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/dashboard.py +0 -0
  128. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/health.py +0 -0
  129. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/ip_allowlist.py +0 -0
  130. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/jobs.py +0 -0
  131. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/runner.py +0 -0
  132. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/security.py +0 -0
  133. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/server.py +0 -0
  134. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/service.py +0 -0
  135. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/session.py +0 -0
  136. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/setup_manager.py +0 -0
  137. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/agents.html +0 -0
  138. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/chat.html +0 -0
  139. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/custom-agents.html +0 -0
  140. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/default-agents.html +0 -0
  141. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/index.html +0 -0
  142. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/report.html +0 -0
  143. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/runs.html +0 -0
  144. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/schedules.html +0 -0
  145. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/setup.html +0 -0
  146. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/static/tickets.html +0 -0
  147. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/ui/swagger.py +0 -0
  148. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/updater.py +0 -0
  149. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/url_access.py +0 -0
  150. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/vps_health.py +0 -0
  151. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/vps_identity.py +0 -0
  152. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/sdk/write_hook.py +0 -0
  153. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/setup.cfg +0 -0
  154. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_activity_claude_runs.py +0 -0
  155. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_activity_collectors.py +0 -0
  156. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_activity_core.py +0 -0
  157. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_activity_live_tracking.py +0 -0
  158. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_activity_macos.py +0 -0
  159. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_activity_os_setup.py +0 -0
  160. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_activity_pipeline.py +0 -0
  161. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_activity.py +0 -0
  162. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_activity_tracking.py +0 -0
  163. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_chat_features.py +0 -0
  164. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_crud_cli.py +0 -0
  165. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_listing.py +0 -0
  166. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_prompt_async.py +0 -0
  167. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_prompt_models.py +0 -0
  168. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_run_ticket.py +0 -0
  169. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_schedules.py +0 -0
  170. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agent_workspace_settings.py +0 -0
  171. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_agents_connectors_builder.py +0 -0
  172. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_allowed_tools_config.py +0 -0
  173. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_auto_agent_dispatch.py +0 -0
  174. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_auto_ticket_queue.py +0 -0
  175. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_browser_profile.py +0 -0
  176. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_cheap_tier.py +0 -0
  177. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_claude_usage_gate.py +0 -0
  178. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_client_jira_projects.py +0 -0
  179. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_config_token_diagnostics.py +0 -0
  180. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_configure_token_owner.py +0 -0
  181. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_console_encoding.py +0 -0
  182. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_crash_recovery.py +0 -0
  183. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_create_safe_retry.py +0 -0
  184. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_departments.py +0 -0
  185. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_done_transition.py +0 -0
  186. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_epic_decompose_run.py +0 -0
  187. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_epic_inherited_agent_id.py +0 -0
  188. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_epic_session_isolation.py +0 -0
  189. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_estimator.py +0 -0
  190. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_fallback_cli.py +0 -0
  191. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_fallback_cli_upgrade.py +0 -0
  192. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_firewall_open_on_update.py +0 -0
  193. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_formats.py +0 -0
  194. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_git_action.py +0 -0
  195. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_github_fetch.py +0 -0
  196. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_github_ingest.py +0 -0
  197. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_graph_recon.py +0 -0
  198. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_graphify_build.py +0 -0
  199. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_graphify_hook_install.py +0 -0
  200. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_health.py +0 -0
  201. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_intake_job_outcome.py +0 -0
  202. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_intake_plan_review.py +0 -0
  203. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_journal.py +0 -0
  204. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_limit_fallback_handoff.py +0 -0
  205. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_local_exec_provider.py +0 -0
  206. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_mcp_route_contract.py +0 -0
  207. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_memory_guard.py +0 -0
  208. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_panel_ticket_schedules.py +0 -0
  209. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_phase_timer.py +0 -0
  210. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_plan_new_command.py +0 -0
  211. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_plan_run_activity.py +0 -0
  212. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_pr_identity.py +0 -0
  213. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_prd_artifact.py +0 -0
  214. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_project_path_resolution.py +0 -0
  215. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_proof_readers.py +0 -0
  216. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_providers.py +0 -0
  217. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_recon_session_isolation.py +0 -0
  218. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_resume.py +0 -0
  219. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_router.py +0 -0
  220. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_run_auto_agent_only.py +0 -0
  221. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_run_outputs.py +0 -0
  222. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_run_verdict_is_honest.py +0 -0
  223. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_schedule_api.py +0 -0
  224. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_schedule_outputs_and_plans.py +0 -0
  225. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_schedule_tickets.py +0 -0
  226. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_scheduler.py +0 -0
  227. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_scheduler_transient_retry.py +0 -0
  228. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_self_report.py +0 -0
  229. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_service_token_warning.py +0 -0
  230. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_stale_panel_restart.py +0 -0
  231. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_swagger_command.py +0 -0
  232. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_task_file_and_plan_run.py +0 -0
  233. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_task_file_format_doc.py +0 -0
  234. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_ticket_cli.py +0 -0
  235. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_ticket_fields.py +0 -0
  236. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_ticket_run_resolve_by_id.py +0 -0
  237. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_ticket_schedule_and_agent.py +0 -0
  238. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_ticket_speed_fixes.py +0 -0
  239. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_tier_flag_parity.py +0 -0
  240. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_ui_gate.py +0 -0
  241. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_ui_lifespan.py +0 -0
  242. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_update_timer.py +0 -0
  243. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_updater_extras.py +0 -0
  244. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_updater_pep668.py +0 -0
  245. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_updater_reports_the_truth.py +0 -0
  246. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_url_access.py +0 -0
  247. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_vps_health.py +0 -0
  248. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_vps_only_reporting.py +0 -0
  249. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_windows_loop_identity.py +0 -0
  250. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_windows_panel_task.py +0 -0
  251. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_windows_stop_and_pidfile.py +0 -0
  252. {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.85}/tests/test_windows_task_paths.py +0 -0
  253. {devops_bot_sdk-1.6.84 → 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.84
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.84
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
@@ -138,6 +138,7 @@ sdk/ui/static/app.js
138
138
  sdk/ui/static/chat.html
139
139
  sdk/ui/static/custom-agents.html
140
140
  sdk/ui/static/default-agents.html
141
+ sdk/ui/static/help.html
141
142
  sdk/ui/static/index.html
142
143
  sdk/ui/static/report.html
143
144
  sdk/ui/static/runs.html
@@ -200,6 +201,7 @@ tests/test_limit_fallback_handoff.py
200
201
  tests/test_local_exec_provider.py
201
202
  tests/test_mcp_route_contract.py
202
203
  tests/test_memory_guard.py
204
+ tests/test_panel_help.py
203
205
  tests/test_panel_ticket_schedules.py
204
206
  tests/test_phase_timer.py
205
207
  tests/test_plan_new_command.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.84
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.84" # 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
@@ -16,6 +16,13 @@ log line for every claim and to say "not determinable" rather than guess. It
16
16
  cannot run commands, edit files or reach the web, so asking for help can never
17
17
  change the machine.
18
18
 
19
+ When claude is not installed, not logged in, or fails mid-answer, the AI Draftsman
20
+ fallback (``ad``) answers instead — the same fallback tickets use. ``ad`` has no
21
+ tool allowlist, ``--add-dir`` or system-prompt flag, so the helper compensates:
22
+ ``--permission-mode plan`` (its no-changes mode), the SDK source and docs copied
23
+ INTO the case folder (its working directory), and the same rules sent at the top
24
+ of the prompt.
25
+
19
26
  ``agentos doctor`` builds the same case folder with no AI, for sending to an admin.
20
27
  """
21
28
  from __future__ import annotations
@@ -43,6 +50,11 @@ _RUN_LOGS = 5
43
50
  #: Case folders kept; older ones are pruned on each run.
44
51
  _KEEP_CASES = 10
45
52
  _ANSWER_TIMEOUT_S = 900
53
+ #: A rate-limited `ad` run (its gateway's 429 / RESOURCE_EXHAUSTED) is retried this
54
+ #: many times, this far apart — the quota window is per minute.
55
+ _AD_RATE_LIMIT_RETRIES = 2
56
+ _AD_RATE_LIMIT_WAIT_S = 45.0
57
+ _RATE_LIMIT_RE = re.compile(r"RATE_LIMIT|RESOURCE_EXHAUSTED|\b429\b")
46
58
 
47
59
  # Secrets that may appear in config, env or logs. Order matters: the specific
48
60
  # token shapes first, then the generic key=value catch-all.
@@ -78,7 +90,10 @@ Your evidence, all readable with Read/Grep/Glob:
78
90
  - The knowledge base: {kb} — a map of programs, files, known issues and fixes.
79
91
  {docs_line}
80
92
  Accuracy rules — these override everything else:
81
- 1. Read before you answer. Start with diagnostics.md and errors.md; open the logs
93
+ 0. WRITE NOTHING until you have finished reading. Your first actions must be file
94
+ reads; do not print a heading, a guess or a "Diagnosis" line before that.
95
+ 1. Read before you answer. Start with diagnostics.md ("Key facts" at its top are
96
+ computed by the SDK and are always right) and errors.md; open the logs
82
97
  and the source that bear on the problem. Never answer from memory alone.
83
98
  2. Every claim about what happened must quote the log line (file + the line) that
84
99
  shows it; every claim about how the SDK behaves must cite the source as
@@ -225,6 +240,75 @@ def _panel_log() -> str:
225
240
  return ""
226
241
 
227
242
 
243
+ def key_facts() -> list[str]:
244
+ """The yes/no state an answer most often gets wrong, computed — not inferred.
245
+
246
+ Put at the top of diagnostics.md and into the prompt, so a model never has to
247
+ work out from raw JSON whether the loop is running (the AI Draftsman fallback
248
+ once answered "the loop appears to be running" with running_pid null).
249
+ """
250
+ def yn(v) -> str:
251
+ return "YES" if v else "NO"
252
+
253
+ facts: list[str] = []
254
+ try:
255
+ st = _loop_state()
256
+ facts.append(f"agentos-auto loop running: {yn(st['running_pid'])}"
257
+ + (f" (pid {st['running_pid']})" if st["running_pid"] else ""))
258
+ facts.append(f"stop flag set (~/.agentos/auto.stop): {yn(st['stop_pending'])}")
259
+ facts.append(f"ticket in flight: {st['current_ticket'] or 'none'}")
260
+ facts.append(f"queued ticket batches: {len(st['queue'])}")
261
+ except Exception as exc: # noqa: BLE001
262
+ facts.append(f"agentos-auto loop state: unknown ({exc})")
263
+ try:
264
+ facts.append(f"agentd running: {yn(_agentd_state()['alive'])}")
265
+ except Exception as exc: # noqa: BLE001
266
+ facts.append(f"agentd state: unknown ({exc})")
267
+ try:
268
+ c = _claude_state()
269
+ facts.append(f"claude CLI: installed {yn(c.get('installed'))}, "
270
+ f"logged in {yn(c.get('logged_in'))}")
271
+ except Exception as exc: # noqa: BLE001
272
+ facts.append(f"claude CLI: unknown ({exc})")
273
+ try:
274
+ from sdk import local_exec
275
+ f = local_exec.fallback_status()
276
+ facts.append(f"AI Draftsman fallback (ad): installed {yn(f.get('installed'))}, "
277
+ f"logged in {yn(f.get('logged_in'))}")
278
+ except Exception as exc: # noqa: BLE001
279
+ facts.append(f"AI Draftsman fallback: unknown ({exc})")
280
+ return facts
281
+
282
+
283
+ def valid_commands() -> dict[str, set[str]]:
284
+ """Every real ``agentos <noun>`` and ``agentos-auto <cmd>``, read off the source."""
285
+ import inspect
286
+
287
+ from sdk import cli as cli_mod, run_auto
288
+
289
+ cli_src, auto_src = inspect.getsource(cli_mod), inspect.getsource(run_auto)
290
+ auto = set(re.findall(r'cmd == "([\w-]+)"', auto_src))
291
+ for group in re.findall(r"cmd in \(([^)]*)\)", auto_src):
292
+ auto |= set(re.findall(r'"([\w-]+)"', group))
293
+ return {"agentos": set(re.findall(r'noun == "([\w-]+)"', cli_src)),
294
+ "agentos-auto": auto}
295
+
296
+
297
+ def unknown_commands(answer: str) -> list[str]:
298
+ """Commands written as code in ``answer`` that do not exist in the source."""
299
+ # One snippet per line, and only spaces/tabs between a program and its
300
+ # sub-command: "`agentos-auto` loop … `agentos-ui`" is two mentions, not a
301
+ # command `agentos-auto agentos-ui`.
302
+ code = "\n".join(re.findall(r"```.*?```", answer, re.S)
303
+ + re.findall(r"`([^`\n]+)`", answer))
304
+ valid = valid_commands()
305
+ bad: list[str] = []
306
+ for prog, sub in re.findall(r"(?<![\w-])(agentos-auto|agentos(?!-))[ \t]+([a-z][\w-]*)", code):
307
+ if sub not in valid[prog] and f"{prog} {sub}" not in bad:
308
+ bad.append(f"{prog} {sub}")
309
+ return bad
310
+
311
+
228
312
  def _log_sources() -> list[tuple[str, Path | str]]:
229
313
  """(name in the case folder, source path or literal text)."""
230
314
  out: list[tuple[str, Path | str]] = []
@@ -261,6 +345,8 @@ def collect_case(question: str = "") -> Path:
261
345
  "# Diagnostics", "",
262
346
  f"collected: {datetime.now(timezone.utc).isoformat(timespec='seconds')}",
263
347
  f"question: {redact(question) or '(none — doctor run)'}", "",
348
+ "## Key facts (computed by the SDK — trust these over any inference)",
349
+ *(f"- {f}" for f in key_facts()), "",
264
350
  "## Environment",
265
351
  f"- sdk version: {__version__}",
266
352
  f"- sdk source: {_SDK_DIR}",
@@ -327,14 +413,118 @@ def _docs_dir() -> Path | None:
327
413
  return None
328
414
 
329
415
 
330
- def claude_argv(cli: str, question: str, case: Path, *, follow_up: bool = False) -> list[str]:
331
- """The read-only `claude -p` invocation for one question."""
416
+ def _system_prompt(case: Path, sdk_dir: Path, kb: Path, docs: Path | None) -> str:
332
417
  from sdk import __version__
333
418
 
334
- docs = _docs_dir()
335
- system = SYSTEM_PROMPT.format(
336
- sdk_dir=_SDK_DIR, version=__version__, kb=_KB_FILE, case_dir=case,
419
+ return SYSTEM_PROMPT.format(
420
+ sdk_dir=sdk_dir, version=__version__, kb=kb, case_dir=case,
337
421
  docs_line=(f"- Project docs: {docs}\n" if docs else ""))
422
+
423
+
424
+ def _copy_sources(case: Path) -> tuple[Path, Path, Path | None]:
425
+ """Copy the SDK source (.py/.md/.html/.js) and docs into ``case`` for ``ad``,
426
+ which can only see its working directory. Returns (sdk dir, kb, docs dir)."""
427
+ def keep(_dir: str, names: list[str]) -> set[str]:
428
+ return {n for n in names
429
+ if n == "__pycache__"
430
+ or ("." in n and not n.endswith((".py", ".md", ".html", ".js")))}
431
+
432
+ sdk_copy = case / "sdk_source"
433
+ if not sdk_copy.exists():
434
+ shutil.copytree(_SDK_DIR, sdk_copy, ignore=keep)
435
+ docs = _docs_dir()
436
+ docs_copy = None
437
+ if docs:
438
+ docs_copy = case / "docs"
439
+ if not docs_copy.exists():
440
+ shutil.copytree(docs, docs_copy, ignore=keep)
441
+ return sdk_copy, sdk_copy / _KB_FILE.name, docs_copy
442
+
443
+
444
+ def ad_argv(cli: str, *, follow_up: bool = False) -> list[str]:
445
+ """The `ad -p` invocation; the prompt goes on stdin (no Windows argv limit)."""
446
+ from sdk import local_exec
447
+
448
+ args = ["-p", "--output-format", "text", "--permission-mode", "plan"]
449
+ if follow_up:
450
+ args.append("--continue")
451
+ return local_exec._fallback_argv(cli, *args)
452
+
453
+
454
+ def ad_prompt(question: str, case: Path, *, follow_up: bool = False) -> str:
455
+ """``ad`` has no system-prompt flag: the rules travel at the top of the prompt,
456
+ with every path pointing INSIDE the case folder, where the sources were copied."""
457
+ if follow_up:
458
+ return question
459
+ sdk_copy, kb, docs = _copy_sources(case)
460
+ facts = ""
461
+ try:
462
+ diag = (case / "diagnostics.md").read_text(encoding="utf-8")
463
+ block = diag.split("## Key facts", 1)[1].split("\n## ", 1)[0]
464
+ facts = "\nKey facts (computed by the SDK — always right):" + block.split("\n", 1)[1]
465
+ except (OSError, IndexError):
466
+ pass
467
+ return (_system_prompt(case, sdk_copy, kb, docs) + facts
468
+ + "\nAll of the above is in your working directory: ./diagnostics.md, "
469
+ "./errors.md, ./logs/, ./sdk_source/ (cite it as sdk/<file>.py:<line>)"
470
+ + (", ./docs/" if docs else "") + ".\n\n"
471
+ + f"The employee's problem:\n{question}\n")
472
+
473
+
474
+ def _engines() -> list[tuple[str, str]]:
475
+ """Ready answer engines, best first: [("claude", path), ("ad", path)]."""
476
+ from sdk import local_exec
477
+
478
+ out: list[tuple[str, str]] = []
479
+ cli = local_exec.claude_cli_path()
480
+ if cli:
481
+ try:
482
+ ready = local_exec.claude_status(timeout=15.0).get("logged_in")
483
+ except Exception: # noqa: BLE001 — unknown: let the run itself decide
484
+ ready = True
485
+ if ready:
486
+ out.append(("claude", cli))
487
+ try:
488
+ fb = local_exec.fallback_status()
489
+ except Exception: # noqa: BLE001
490
+ fb = {}
491
+ if fb.get("installed") and fb.get("logged_in") and fb.get("path"):
492
+ out.append(("ad", str(fb["path"])))
493
+ return out
494
+
495
+
496
+ def _answer(engine: tuple[str, str], question: str, case: Path, *, follow_up: bool,
497
+ echo: bool = True) -> tuple[int, str]:
498
+ """One answer: ``(exit code, captured text)``. ``echo`` streams it to the
499
+ terminal (the CLI); without it the text is only returned (the panel)."""
500
+ import time
501
+
502
+ name, cli = engine
503
+ if name == "claude":
504
+ argv = claude_argv(cli, question, case, follow_up=follow_up)
505
+ if echo:
506
+ return _run(argv, case), ""
507
+ return _run_capture(argv, case, stdin=None, echo=False)
508
+ prompt = ad_prompt(question, case, follow_up=follow_up)
509
+ for attempt in range(_AD_RATE_LIMIT_RETRIES + 1):
510
+ rc, out = _run_capture(ad_argv(cli, follow_up=follow_up), case, stdin=prompt, echo=echo)
511
+ if rc == 0:
512
+ if echo:
513
+ _check_ad_answer(out)
514
+ return rc, out
515
+ if not _RATE_LIMIT_RE.search(out[-4000:]) or attempt == _AD_RATE_LIMIT_RETRIES:
516
+ return rc, out
517
+ if echo:
518
+ print(f"\n(the AI Draftsman service is rate-limited — retrying in "
519
+ f"{int(_AD_RATE_LIMIT_WAIT_S)}s, {attempt + 1}/{_AD_RATE_LIMIT_RETRIES})\n")
520
+ time.sleep(_AD_RATE_LIMIT_WAIT_S)
521
+ return rc, out
522
+
523
+
524
+ def claude_argv(cli: str, question: str, case: Path, *, follow_up: bool = False) -> list[str]:
525
+ """The read-only `claude -p` invocation for one question."""
526
+ docs = _docs_dir()
527
+ system = _system_prompt(case, _SDK_DIR, _KB_FILE, docs)
338
528
  argv = [cli, "-p", question,
339
529
  "--output-format", "text",
340
530
  "--allowedTools", "Read,Grep,Glob",
@@ -357,15 +547,26 @@ def ask(question: str, *, interactive: bool | None = None) -> int:
357
547
  case = collect_case(question)
358
548
  print(f" case folder: {case}")
359
549
 
360
- cli = local_exec.claude_cli_path()
361
- if not cli:
362
- print("\nDevOps Bot Help needs the Claude Code CLI, which is not installed here.")
550
+ engines = _engines()
551
+ if not engines:
552
+ print("\nDevOps Bot Help needs claude or the AI Draftsman fallback, and neither "
553
+ "is ready here.")
363
554
  print(f" {local_exec.NO_LOCAL_CLI_MESSAGE}")
364
555
  print(f"Meanwhile, send this folder to your admin: {case}")
365
556
  return 1
366
557
 
558
+ engine = engines[0]
559
+ if engine[0] == "ad":
560
+ print(" (claude is not installed or not logged in — answering with the AI "
561
+ "Draftsman fallback)")
367
562
  print("🤖 DevOps Bot Help is reading your logs and the SDK source …\n")
368
- rc = _run(claude_argv(cli, question, case), case)
563
+ rc = _answer(engine, question, case, follow_up=False)[0]
564
+ if rc != 0 and len(engines) > 1:
565
+ # claude started but failed (expired login, usage limit, crash): one retry on ad.
566
+ engine = engines[1]
567
+ print(f"\n(claude could not answer, exit {rc} — retrying with the AI Draftsman "
568
+ "fallback)\n")
569
+ rc = _answer(engine, question, case, follow_up=False)[0]
369
570
  if interactive is None:
370
571
  interactive = sys.stdin.isatty()
371
572
  while rc == 0 and interactive:
@@ -375,24 +576,155 @@ def ask(question: str, *, interactive: bool | None = None) -> int:
375
576
  break
376
577
  if not more:
377
578
  break
378
- rc = _run(claude_argv(cli, more, case, follow_up=True), case)
579
+ rc = _answer(engine, more, case, follow_up=True)[0]
379
580
  if rc != 0:
380
581
  print(f"\nThe helper could not finish (exit {rc}). Send this folder to your admin: {case}")
381
582
  return rc
382
583
 
383
584
 
384
- def _run(argv: list[str], case: Path) -> int:
385
- sys.stdout.flush() # our progress lines must land before claude's answer
585
+ def _run(argv: list[str], case: Path, *, stdin: str | None = None) -> int:
586
+ sys.stdout.flush() # our progress lines must land before the answer
386
587
  try:
387
- return subprocess.run(argv, cwd=str(case), timeout=_ANSWER_TIMEOUT_S).returncode
588
+ return subprocess.run(argv, cwd=str(case), timeout=_ANSWER_TIMEOUT_S,
589
+ input=stdin, text=stdin is not None,
590
+ encoding="utf-8" if stdin is not None else None).returncode
388
591
  except subprocess.TimeoutExpired:
389
592
  print("\n(timed out)")
390
593
  return 124
391
594
  except OSError as exc:
392
- print(f"\ncould not start claude: {exc}")
595
+ print(f"\ncould not start {Path(argv[0]).name}: {exc}")
393
596
  return 1
394
597
 
395
598
 
599
+ def ad_notes(answer: str) -> list[str]:
600
+ """The fallback model follows the rules less reliably than claude, so check the
601
+ one thing that can be checked mechanically — commands — and say so."""
602
+ notes = ["answered by the AI Draftsman fallback, which is less reliable than "
603
+ "claude: double-check before acting."]
604
+ bad = unknown_commands(answer)
605
+ if bad:
606
+ notes.append("⚠ these commands in the answer DO NOT EXIST — ignore them: "
607
+ + ", ".join(f"`{b}`" for b in bad)
608
+ + ". The real ones: `agentos help -h` and the top of `sdk/run_auto.py`.")
609
+ return notes
610
+
611
+
612
+ def _check_ad_answer(answer: str) -> None:
613
+ print()
614
+ for note in ad_notes(answer):
615
+ print(f"— {note}")
616
+
617
+
618
+ def _run_capture(argv: list[str], case: Path, *, stdin: str | None,
619
+ echo: bool = True) -> tuple[int, str]:
620
+ """Like _run, but KEEP the output (and echo it live unless ``echo`` is off), so
621
+ a rate-limit failure can be told apart from any other, the answer can be
622
+ checked afterwards, and the panel can show it."""
623
+ sys.stdout.flush()
624
+ tail: list[str] = []
625
+ try:
626
+ proc = subprocess.Popen(argv, cwd=str(case),
627
+ stdin=subprocess.PIPE if stdin is not None else subprocess.DEVNULL,
628
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
629
+ text=True, encoding="utf-8", errors="replace")
630
+ except OSError as exc:
631
+ msg = f"could not start {Path(argv[0]).name}: {exc}"
632
+ if echo:
633
+ print("\n" + msg)
634
+ return 1, msg
635
+ if stdin is not None:
636
+ try:
637
+ proc.stdin.write(stdin)
638
+ proc.stdin.close()
639
+ except OSError:
640
+ pass
641
+ import time
642
+ deadline = time.monotonic() + _ANSWER_TIMEOUT_S
643
+ for line in proc.stdout:
644
+ if echo:
645
+ sys.stdout.write(line)
646
+ sys.stdout.flush()
647
+ tail.append(line)
648
+ if time.monotonic() > deadline:
649
+ proc.kill()
650
+ if echo:
651
+ print("\n(timed out)")
652
+ return 124, "".join(tail) + "\n(timed out)"
653
+ return proc.wait(), "".join(tail)
654
+
655
+
656
+ # ── The control panel's Help page ──────────────────────────────────────────
657
+
658
+ _CASE_NAME_RE = re.compile(r"^case-\d{8}-\d{6}$")
659
+
660
+
661
+ def case_path(name: str) -> Path | None:
662
+ """A case folder by NAME, only if it is one of ours — the panel passes it back."""
663
+ if not _CASE_NAME_RE.match(name or ""):
664
+ return None
665
+ path = _CASES_DIR / name
666
+ return path if path.is_dir() else None
667
+
668
+
669
+ def answer(question: str, case_name: str = "") -> dict:
670
+ """Blocking, panel-facing ``agentos help``: the same case folder, engines and
671
+ rules, returned instead of printed.
672
+
673
+ A new question builds a new case; ``case_name`` continues that case's
674
+ conversation on the engine that answered it (`--continue` / `-c`).
675
+ """
676
+ from sdk import local_exec
677
+
678
+ question = (question or "").strip()
679
+ if not question:
680
+ return {"ok": False, "error": "describe the problem first"}
681
+ follow_up = bool(case_name)
682
+ if follow_up:
683
+ case = case_path(case_name)
684
+ if case is None:
685
+ return {"ok": False, "error": f"unknown case {case_name!r}"}
686
+ else:
687
+ case = collect_case(question)
688
+
689
+ engines = _engines()
690
+ if follow_up:
691
+ used = (case / "engine.txt").read_text().strip() if (case / "engine.txt").exists() else ""
692
+ engines = [e for e in engines if e[0] == used] or engines[:1]
693
+ base = {"case": case.name, "case_path": str(case)}
694
+ if not engines:
695
+ return {**base, "ok": False,
696
+ "error": "neither claude nor the AI Draftsman fallback is ready on this "
697
+ f"machine. {local_exec.NO_LOCAL_CLI_MESSAGE}"}
698
+
699
+ engine = engines[0]
700
+ rc, out = _answer(engine, question, case, follow_up=follow_up, echo=False)
701
+ if rc != 0 and not follow_up and len(engines) > 1:
702
+ engine = engines[1]
703
+ rc, out = _answer(engine, question, case, follow_up=False, echo=False)
704
+ (case / "engine.txt").write_text(engine[0])
705
+ result = {**base, "ok": rc == 0, "engine": engine[0], "answer": out.strip(),
706
+ "notes": ad_notes(out) if engine[0] == "ad" and rc == 0 else []}
707
+ if rc != 0:
708
+ result["error"] = (f"the helper could not finish (exit {rc}) — send the case "
709
+ f"folder to your admin: {case}")
710
+ return result
711
+
712
+
713
+ def bundle_zip(case: Path) -> Path:
714
+ """A zip of a case's evidence (diagnostics, errors, logs) — NOT the SDK source
715
+ copied in for `ad`, which the admin already has."""
716
+ import zipfile
717
+
718
+ out = case / f"{case.name}.zip"
719
+ with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf:
720
+ for path in sorted(case.rglob("*")):
721
+ rel = path.relative_to(case)
722
+ if (path.is_file() and path != out
723
+ and rel.parts[0] not in ("sdk_source", "docs")):
724
+ zf.write(path, f"{case.name}/{rel.as_posix()}")
725
+ return out
726
+
727
+
396
728
  def doctor() -> int:
397
729
  """`agentos doctor` — the case folder only, no AI."""
398
730
  case = collect_case()
@@ -119,3 +119,8 @@ agentos fallback install | login | status | logout
119
119
  agentos doctor # collect a diagnostics bundle (no AI)
120
120
  agentos help "<problem>" # this helper
121
121
  ```
122
+
123
+ `agentos help` answers with claude when it is installed and logged in, otherwise
124
+ (or if claude fails mid-answer) with the AI Draftsman fallback `ad`, which must be
125
+ logged in (`agentos fallback login`). With neither ready it only builds the case
126
+ folder — the same bundle `agentos doctor` makes.
@@ -1099,8 +1099,11 @@ def _setting(key: str) -> str:
1099
1099
  #: The npm package that ships `ad`. It was published as @aidraftsman/cli before 1.1.1.
1100
1100
  FALLBACK_PACKAGE = "@consultancy-outfit/ai-draftsman-cli"
1101
1101
  _LEGACY_FALLBACK_PACKAGES = ("@aidraftsman/cli",)
1102
- #: The oldest `ad` that works against the gateway's Gemini models (see above).
1103
- FALLBACK_MIN_VERSION = (1, 1, 1)
1102
+ #: The oldest `ad` to keep. 1.1.1 works against the gateway's Gemini models (see
1103
+ #: above); 1.1.2 also retries the gateway's in-stream 429 RESOURCE_EXHAUSTED frame
1104
+ #: (ai-draftsman-cli#3) instead of failing the turn — without it a fallback ticket
1105
+ #: or `agentos help` dies on the first per-minute quota hit.
1106
+ FALLBACK_MIN_VERSION = (1, 1, 2)
1104
1107
  #: How to get the CLI when it is missing — shown in every "not found" message.
1105
1108
  FALLBACK_INSTALL_HINT = f"npm install -g {FALLBACK_PACKAGE}"
1106
1109
  #: `upgrade_fallback_cli` runs at most this often from the start-up path.
@@ -30,6 +30,7 @@ from sdk.ui.models import (
30
30
  CustomAgentCreateRequest,
31
31
  CustomAgentUpdateRequest,
32
32
  GitActionRequest,
33
+ HelpAskRequest,
33
34
  PromptRegenerateRequest,
34
35
  PromptRunRequest,
35
36
  PromptVerifyRequest,
@@ -936,6 +937,52 @@ async def agent_run_download(name: str = Query(...), file: str = Query(...)):
936
937
  # to the backend `agents` table; the SDK lists/reads/runs them (consume-only).
937
938
 
938
939
 
940
+ # ── DevOps Bot Help (the panel's Help page; `agentos help` / `agentos doctor`) ──
941
+
942
+ @router.post("/help/ask", dependencies=[_auth])
943
+ async def help_ask(body: HelpAskRequest) -> dict:
944
+ """Ask DevOps Bot Help. Runs as a job (an answer takes a minute or more);
945
+ poll GET /api/ui/jobs/{job_id} → ``{ok, answer, engine, notes, case, error?}``."""
946
+ import asyncio
947
+
948
+ from sdk import helpdesk
949
+ from sdk.ui import jobs
950
+
951
+ async def work() -> dict:
952
+ return await asyncio.to_thread(helpdesk.answer, body.question, body.case)
953
+
954
+ return {"job_id": jobs.start(work())}
955
+
956
+
957
+ @router.post("/help/doctor", dependencies=[_auth])
958
+ async def help_doctor() -> dict:
959
+ """Collect a diagnostics bundle (no AI) — the panel's `agentos doctor`."""
960
+ import asyncio
961
+
962
+ from sdk import helpdesk
963
+
964
+ case = await asyncio.to_thread(helpdesk.collect_case)
965
+ return {"case": case.name, "case_path": str(case),
966
+ "diagnostics": (case / "diagnostics.md").read_text(encoding="utf-8"),
967
+ "errors": (case / "errors.md").read_text(encoding="utf-8")}
968
+
969
+
970
+ @router.get("/help/case/{name}/bundle", dependencies=[_auth])
971
+ async def help_bundle(name: str):
972
+ """The case's evidence as a zip, to send to an admin (no SDK source inside)."""
973
+ import asyncio
974
+
975
+ from fastapi.responses import FileResponse
976
+
977
+ from sdk import helpdesk
978
+
979
+ case = helpdesk.case_path(name)
980
+ if case is None:
981
+ raise HTTPException(status_code=404, detail=f"unknown case: {name}")
982
+ zipped = await asyncio.to_thread(helpdesk.bundle_zip, case)
983
+ return FileResponse(zipped, media_type="application/zip", filename=zipped.name)
984
+
985
+
939
986
  @router.get("/jobs/{job_id}", dependencies=[_auth])
940
987
  async def job_status(job_id: str) -> dict:
941
988
  """Poll a detached job started by a long-running action (chat/analyze/run).
@@ -684,3 +684,17 @@ class SelfReportEvaluateRequest(BaseModel):
684
684
  reason: str = Field(
685
685
  "", max_length=500,
686
686
  description="Optional note for the audit trail — why it was re-evaluated.")
687
+
688
+
689
+ class HelpAskRequest(BaseModel):
690
+ """Body for ``POST /api/ui/help/ask`` — DevOps Bot Help, from the panel.
691
+
692
+ The same helper as ``agentos help``: diagnostics + logs from THIS machine, the
693
+ installed SDK source, read-only claude (or the AI Draftsman fallback).
694
+ """
695
+
696
+ question: str = Field(..., min_length=1, max_length=4000,
697
+ description="The problem, in the employee's own words.")
698
+ case: str = Field("", max_length=40,
699
+ description="A case name from a previous answer, to ask a follow-up "
700
+ "in the same conversation. Empty = a new case.")