devops-bot-sdk 1.6.87__tar.gz → 1.6.89__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 (254) hide show
  1. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/__init__.py +2 -2
  4. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/app.js +215 -45
  5. devops_bot_sdk-1.6.89/sdk/ui/static/help.html +79 -0
  6. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/styles.css +100 -9
  7. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_panel_help.py +50 -0
  8. devops_bot_sdk-1.6.87/sdk/ui/static/help.html +0 -48
  9. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/README.md +0 -0
  10. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  11. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  12. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  13. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/devops_bot_sdk.egg-info/requires.txt +0 -0
  14. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  15. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/pyproject.toml +0 -0
  16. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/__init__.py +0 -0
  17. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/classifier.py +0 -0
  18. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/collectors/__init__.py +0 -0
  19. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/collectors/common.py +0 -0
  20. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/collectors/linux_auditd.py +0 -0
  21. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/collectors/macos_eslogger.py +0 -0
  22. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/collectors/windows_eventlog.py +0 -0
  23. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/models.py +0 -0
  24. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/os_setup.py +0 -0
  25. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/recorder.py +0 -0
  26. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/redact.py +0 -0
  27. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/settings.py +0 -0
  28. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/shipper.py +0 -0
  29. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/activity/spool.py +0 -0
  30. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agentd.py +0 -0
  31. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/__init__.py +0 -0
  32. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/activity.py +0 -0
  33. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/builder.py +0 -0
  34. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/chat.py +0 -0
  35. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/connectors.py +0 -0
  36. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/engines/__init__.py +0 -0
  37. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/engines/claude_code.py +0 -0
  38. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/engines/http.py +0 -0
  39. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/engines/mcp.py +0 -0
  40. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/engines/n8n.py +0 -0
  41. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/engines/shell.py +0 -0
  42. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/integrations.py +0 -0
  43. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/inventory.py +0 -0
  44. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/library/__init__.py +0 -0
  45. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/manifest.py +0 -0
  46. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/prompt_studio.py +0 -0
  47. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/prompt_template.py +0 -0
  48. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/prompt_vars.py +0 -0
  49. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/run_log.py +0 -0
  50. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/schedules.py +0 -0
  51. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/secrets.py +0 -0
  52. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/ticket_run.py +0 -0
  53. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/usage.py +0 -0
  54. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/agents/varstore.py +0 -0
  55. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/browser_profile.py +0 -0
  56. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/claude_usage.py +0 -0
  57. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/cli.py +0 -0
  58. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/client.py +0 -0
  59. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/collectors/__init__.py +0 -0
  60. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/collectors/files.py +0 -0
  61. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/collectors/process.py +0 -0
  62. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/collectors/screenshot.py +0 -0
  63. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/config.py +0 -0
  64. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/crucial.py +0 -0
  65. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/custom_agents.py +0 -0
  66. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/departments.py +0 -0
  67. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/design_verify/__init__.py +0 -0
  68. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/design_verify/assets.py +0 -0
  69. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/design_verify/bootstrap.py +0 -0
  70. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/design_verify/browser.py +0 -0
  71. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/design_verify/compare.py +0 -0
  72. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/design_verify/loop.py +0 -0
  73. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/estimator.py +0 -0
  74. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/exceptions.py +0 -0
  75. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/fallback_handoff.py +0 -0
  76. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/firewall.py +0 -0
  77. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/formats.py +0 -0
  78. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/git_ops.py +0 -0
  79. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/github_fetch.py +0 -0
  80. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/github_ingest.py +0 -0
  81. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/graph_recon.py +0 -0
  82. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/graphify.py +0 -0
  83. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/helpdesk.py +0 -0
  84. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/helpdesk_kb.md +0 -0
  85. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/hooks/__init__.py +0 -0
  86. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/hooks/crucial_guard.py +0 -0
  87. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/intake.py +0 -0
  88. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ipc/__init__.py +0 -0
  89. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ipc/electron_bridge.py +0 -0
  90. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ipc/handlers.py +0 -0
  91. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/journal.py +0 -0
  92. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/local_exec.py +0 -0
  93. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/memory_guard.py +0 -0
  94. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/models/__init__.py +0 -0
  95. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/models/envelope.py +0 -0
  96. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/models/requests.py +0 -0
  97. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/models/responses.py +0 -0
  98. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/models/snapshots.py +0 -0
  99. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/phase_timer.py +0 -0
  100. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/plan_run.py +0 -0
  101. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/platform_compat.py +0 -0
  102. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/prd_progress.py +0 -0
  103. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/proof_readers.py +0 -0
  104. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/providers.py +0 -0
  105. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/py.typed +0 -0
  106. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/resume.py +0 -0
  107. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/router.py +0 -0
  108. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/run_auto.py +0 -0
  109. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/schedule_tickets.py +0 -0
  110. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/scheduler.py +0 -0
  111. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/self_report.py +0 -0
  112. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/self_report_local.py +0 -0
  113. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/sse.py +0 -0
  114. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/swagger.py +0 -0
  115. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/task_file.py +0 -0
  116. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/task_types.py +0 -0
  117. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/test.py +0 -0
  118. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/test_pipeline.py +0 -0
  119. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ticket_fields.py +0 -0
  120. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/tickets.py +0 -0
  121. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/tiers/__init__.py +0 -0
  122. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/tiers/mini_agent.py +0 -0
  123. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/__init__.py +0 -0
  124. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/api.py +0 -0
  125. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/dashboard.py +0 -0
  126. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/health.py +0 -0
  127. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/ip_allowlist.py +0 -0
  128. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/jobs.py +0 -0
  129. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/models.py +0 -0
  130. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/runner.py +0 -0
  131. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/security.py +0 -0
  132. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/server.py +0 -0
  133. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/service.py +0 -0
  134. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/session.py +0 -0
  135. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/setup_manager.py +0 -0
  136. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/agents.html +0 -0
  137. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/chat.html +0 -0
  138. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/custom-agents.html +0 -0
  139. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/default-agents.html +0 -0
  140. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/index.html +0 -0
  141. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/report.html +0 -0
  142. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/runs.html +0 -0
  143. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/schedules.html +0 -0
  144. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/setup.html +0 -0
  145. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/static/tickets.html +0 -0
  146. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/ui/swagger.py +0 -0
  147. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/updater.py +0 -0
  148. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/url_access.py +0 -0
  149. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/vps_health.py +0 -0
  150. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/vps_identity.py +0 -0
  151. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/sdk/write_hook.py +0 -0
  152. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/setup.cfg +0 -0
  153. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_activity_claude_runs.py +0 -0
  154. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_activity_collectors.py +0 -0
  155. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_activity_core.py +0 -0
  156. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_activity_live_tracking.py +0 -0
  157. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_activity_macos.py +0 -0
  158. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_activity_os_setup.py +0 -0
  159. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_activity_pipeline.py +0 -0
  160. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_activity.py +0 -0
  161. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_activity_tracking.py +0 -0
  162. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_chat_features.py +0 -0
  163. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_crud_cli.py +0 -0
  164. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_listing.py +0 -0
  165. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_prompt_async.py +0 -0
  166. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_prompt_models.py +0 -0
  167. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_run_ticket.py +0 -0
  168. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_schedules.py +0 -0
  169. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agent_workspace_settings.py +0 -0
  170. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_agents_connectors_builder.py +0 -0
  171. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_allowed_tools_config.py +0 -0
  172. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_auto_agent_dispatch.py +0 -0
  173. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_auto_ticket_queue.py +0 -0
  174. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_browser_profile.py +0 -0
  175. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_cheap_tier.py +0 -0
  176. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_claude_usage_gate.py +0 -0
  177. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_client_jira_projects.py +0 -0
  178. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_config_token_diagnostics.py +0 -0
  179. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_configure_token_owner.py +0 -0
  180. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_console_encoding.py +0 -0
  181. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_crash_recovery.py +0 -0
  182. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_create_safe_retry.py +0 -0
  183. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_departments.py +0 -0
  184. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_done_transition.py +0 -0
  185. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_epic_decompose_run.py +0 -0
  186. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_epic_inherited_agent_id.py +0 -0
  187. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_epic_session_isolation.py +0 -0
  188. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_estimator.py +0 -0
  189. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_fallback_cli.py +0 -0
  190. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_fallback_cli_upgrade.py +0 -0
  191. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_firewall_open_on_update.py +0 -0
  192. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_formats.py +0 -0
  193. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_git_action.py +0 -0
  194. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_github_fetch.py +0 -0
  195. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_github_ingest.py +0 -0
  196. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_graph_recon.py +0 -0
  197. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_graphify_build.py +0 -0
  198. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_graphify_hook_install.py +0 -0
  199. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_health.py +0 -0
  200. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_helpdesk.py +0 -0
  201. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_intake_job_outcome.py +0 -0
  202. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_intake_plan_review.py +0 -0
  203. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_journal.py +0 -0
  204. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_limit_fallback_handoff.py +0 -0
  205. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_local_exec_provider.py +0 -0
  206. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_mcp_route_contract.py +0 -0
  207. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_memory_guard.py +0 -0
  208. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_panel_ticket_schedules.py +0 -0
  209. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_phase_timer.py +0 -0
  210. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_plan_new_command.py +0 -0
  211. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_plan_run_activity.py +0 -0
  212. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_pr_identity.py +0 -0
  213. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_prd_artifact.py +0 -0
  214. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_project_path_resolution.py +0 -0
  215. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_proof_readers.py +0 -0
  216. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_providers.py +0 -0
  217. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_recon_session_isolation.py +0 -0
  218. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_resume.py +0 -0
  219. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_router.py +0 -0
  220. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_run_auto_agent_only.py +0 -0
  221. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_run_outputs.py +0 -0
  222. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_run_verdict_is_honest.py +0 -0
  223. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_schedule_api.py +0 -0
  224. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_schedule_outputs_and_plans.py +0 -0
  225. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_schedule_tickets.py +0 -0
  226. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_scheduler.py +0 -0
  227. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_scheduler_transient_retry.py +0 -0
  228. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_self_report.py +0 -0
  229. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_service_token_warning.py +0 -0
  230. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_stale_panel_restart.py +0 -0
  231. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_swagger_command.py +0 -0
  232. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_task_file_and_plan_run.py +0 -0
  233. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_task_file_format_doc.py +0 -0
  234. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_ticket_cli.py +0 -0
  235. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_ticket_fields.py +0 -0
  236. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_ticket_run_resolve_by_id.py +0 -0
  237. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_ticket_schedule_and_agent.py +0 -0
  238. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_ticket_speed_fixes.py +0 -0
  239. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_tier_flag_parity.py +0 -0
  240. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_ui_gate.py +0 -0
  241. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_ui_lifespan.py +0 -0
  242. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_update_timer.py +0 -0
  243. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_updater_extras.py +0 -0
  244. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_updater_ignores_cwd.py +0 -0
  245. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_updater_pep668.py +0 -0
  246. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_updater_reports_the_truth.py +0 -0
  247. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_url_access.py +0 -0
  248. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_vps_health.py +0 -0
  249. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_vps_only_reporting.py +0 -0
  250. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_windows_loop_identity.py +0 -0
  251. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_windows_panel_task.py +0 -0
  252. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_windows_stop_and_pidfile.py +0 -0
  253. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/tests/test_windows_task_paths.py +0 -0
  254. {devops_bot_sdk-1.6.87 → devops_bot_sdk-1.6.89}/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.87
3
+ Version: 1.6.89
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.87
3
+ Version: 1.6.89
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
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.87
3
+ Version: 1.6.89
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.87" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.89" # 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
@@ -220,7 +220,12 @@
220
220
  <span id="loop-badge" class="badge idle"><span class="pulse"></span> …</span>
221
221
  <a class="btn" href="/setup.html">Setup</a>`;
222
222
  const link = host.querySelector(`[data-nav="${active}"]`);
223
- if (link) link.classList.add("active");
223
+ if (link) {
224
+ link.classList.add("active");
225
+ // The nav scrolls sideways rather than wrapping when the window is narrow;
226
+ // keep the page you are on visible.
227
+ link.scrollIntoView({ block: "nearest", inline: "nearest" });
228
+ }
224
229
  }
225
230
 
226
231
  async function pollBadge() {
@@ -1216,10 +1221,14 @@
1216
1221
 
1217
1222
  async function start() {
1218
1223
  if (TOKEN) { boot(); return; }
1224
+ let unlocked = false;
1219
1225
  try {
1220
1226
  const res = await fetch("/api/ui/session-token");
1221
- if (res.ok) { const d = await res.json(); if (d && d.token !== undefined) { setToken(d.token || "loopback"); boot(); return; } }
1227
+ if (res.ok) { const d = await res.json(); if (d && d.token !== undefined) { setToken(d.token || "loopback"); unlocked = true; } }
1222
1228
  } catch (e) { /* fall through */ }
1229
+ // boot() OUTSIDE the try: a page whose init throws must show that error in the
1230
+ // console, not be swallowed here and turned into a misleading token prompt.
1231
+ if (unlocked) { boot(); return; }
1223
1232
  showGate();
1224
1233
  }
1225
1234
 
@@ -2036,81 +2045,242 @@
2036
2045
  }
2037
2046
 
2038
2047
  // ── HELP: DevOps Bot Help (`agentos help` / `agentos doctor` in the panel) ──
2048
+ // A real answer is plain text with light markdown: "Diagnosis / Evidence / Fix /
2049
+ // If that doesn't work" sections, lists, `code`, ``` blocks and ⚠ lines.
2050
+ // renderHelpAnswer() ESCAPES FIRST and only then adds markup, so nothing the
2051
+ // model writes can become HTML.
2052
+ const HP_SECTIONS = [
2053
+ [/^diagnosis$/i, "diagnosis", "Diagnosis"],
2054
+ [/^evidence$/i, "evidence", "Evidence"],
2055
+ [/^fix$/i, "fix", "Fix"],
2056
+ [/^if that does(?:n'|n’|n)?t work$|^if that doesn't work$|^if it still fails$/i, "next", "If that doesn't work"],
2057
+ ];
2058
+
2059
+ function hpInline(t) {
2060
+ return t
2061
+ .replace(/`([^`]+)`/g, "<code>$1</code>")
2062
+ .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
2063
+ }
2064
+
2065
+ function renderHelpAnswer(text) {
2066
+ const parts = String(text || "").split(/```[^\n]*\n?/);
2067
+ let html = "", list = null, section = null;
2068
+ const closeList = () => { if (list) { html += `</${list}>`; list = null; } };
2069
+ const closeSection = () => { closeList(); if (section) { html += "</section>"; section = null; } };
2070
+ const openSection = (cls, label) => {
2071
+ closeSection();
2072
+ html += `<section class="hp-sec hp-sec-${cls}"><div class="hp-sec-label">${label}</div>`;
2073
+ section = cls;
2074
+ };
2075
+ parts.forEach((part, i) => {
2076
+ if (i % 2 === 1) { // inside a ``` block
2077
+ closeList();
2078
+ // Drop blank edge lines and the indent the block was nested at.
2079
+ const lines = part.replace(/^\s*\n/, "").replace(/\s+$/, "").split("\n");
2080
+ const indent = Math.min(...lines.filter(l => l.trim()).map(l => l.match(/^\s*/)[0].length));
2081
+ const code = esc(lines.map(l => l.slice(indent)).join("\n"));
2082
+ html += `<div class="hp-code"><pre><code>${code}</code></pre>` +
2083
+ `<button type="button" class="hp-copy" data-copy="${code}">Copy</button></div>`;
2084
+ return;
2085
+ }
2086
+ esc(part).split("\n").forEach(raw => {
2087
+ const line = raw.trimEnd();
2088
+ if (!line.trim()) { closeList(); return; }
2089
+ const head = line.match(/^\s*\**([A-Za-z' ’]+?)\**\s*(?::|—|–|-)\s*(.*)$/);
2090
+ const sec = head && HP_SECTIONS.find(([re]) => re.test(head[1].trim()));
2091
+ if (sec) {
2092
+ openSection(sec[1], sec[2]);
2093
+ if (head[2]) html += `<p>${hpInline(head[2])}</p>`;
2094
+ return;
2095
+ }
2096
+ const bullet = line.match(/^\s*[-*•]\s+(.*)$/);
2097
+ const num = line.match(/^\s*(\d+)[.)]\s+(.*)$/);
2098
+ if (bullet || num) {
2099
+ const kind = bullet ? "ul" : "ol";
2100
+ if (list !== kind) { closeList(); html += `<${kind}>`; list = kind; }
2101
+ html += `<li>${hpInline(bullet ? bullet[1] : num[2])}</li>`;
2102
+ return;
2103
+ }
2104
+ closeList();
2105
+ if (/^\s*(⚠|&lt;!|warning:)/i.test(line)) {
2106
+ html += `<div class="hp-warn">${hpInline(line.replace(/^\s*⚠\s*/, ""))}</div>`;
2107
+ } else {
2108
+ html += `<p>${hpInline(line)}</p>`;
2109
+ }
2110
+ });
2111
+ });
2112
+ closeSection();
2113
+ return html;
2114
+ }
2115
+
2116
+ async function hpCopy(text, btn) {
2117
+ try { await navigator.clipboard.writeText(text); }
2118
+ catch (e) { // http:// on a remote box: no clipboard API
2119
+ const ta = document.createElement("textarea");
2120
+ ta.value = text; document.body.appendChild(ta); ta.select();
2121
+ try { document.execCommand("copy"); } catch (_) { /* nothing more to try */ }
2122
+ ta.remove();
2123
+ }
2124
+ if (btn) { const was = btn.textContent; btn.textContent = "Copied"; setTimeout(() => { btn.textContent = was; }, 1200); }
2125
+ }
2126
+
2039
2127
  function initHelp() {
2040
- let caseName = "";
2041
-
2042
- function show(res, question) {
2043
- const host = $("hp-thread");
2044
- const notes = (res.notes || []).map(n => `<div class="hp-note">${esc(n)}</div>`).join("");
2045
- host.insertAdjacentHTML("beforeend", `
2046
- <div class="hp-q"><span class="muted">You</span><div>${esc(question)}</div></div>
2047
- <div class="hp-a">
2048
- <div class="muted">DevOps Bot Help · ${esc(res.engine === "ad" ? "AI Draftsman fallback" : "claude")}
2049
- · case <code>${esc(res.case || "")}</code></div>
2050
- ${res.ok ? `<pre class="hp-answer">${esc(res.answer || "(empty answer)")}</pre>`
2051
- : `<div class="hp-err">${esc(res.error || "the helper could not finish")}</div>`}
2052
- ${notes}
2053
- </div>`);
2054
- host.lastElementChild.scrollIntoView({ behavior: "smooth", block: "start" });
2128
+ const $ = (id) => document.getElementById(id);
2129
+ let caseName = "", jobId = null, timer = null, busy = false;
2130
+
2131
+ function fmt(secs) { return `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, "0")}`; }
2132
+
2133
+ function working(on, followUp) {
2134
+ busy = on;
2135
+ $("hp-working").style.display = on ? "" : "none";
2136
+ $("hp-ask").disabled = on; $("hp-follow-btn").disabled = on; $("hp-doctor").disabled = on;
2137
+ clearInterval(timer);
2138
+ if (!on) { jobId = null; return; }
2139
+ const started = Date.now();
2140
+ const tick = () => {
2141
+ const secs = Math.round((Date.now() - started) / 1000);
2142
+ $("hp-elapsed").textContent = fmt(secs);
2143
+ $("hp-status").textContent = followUp ? "Thinking about your follow-up…"
2144
+ : secs < 6 ? "Collecting diagnostics and recent logs…"
2145
+ : secs < 20 ? "Reading your logs and the SDK source…"
2146
+ : "Working out the cause — usually 30 seconds to 2 minutes…";
2147
+ };
2148
+ tick(); timer = setInterval(tick, 1000);
2149
+ }
2150
+
2151
+ // During a conversation only the follow-up box shows; "New question" brings
2152
+ // the main box back. Two inputs at once read as two different things.
2153
+ function conversation(on) {
2154
+ document.querySelector(".hp-composer").style.display = on ? "none" : "";
2155
+ $("hp-follow").style.display = on ? "" : "none";
2156
+ $("hp-empty").style.display = on ? "none" : "";
2157
+ }
2158
+
2159
+ function addQuestion(question) {
2160
+ $("hp-empty").style.display = "none";
2161
+ document.querySelector(".hp-composer").style.display = "none";
2162
+ $("hp-thread").insertAdjacentHTML("beforeend",
2163
+ `<div class="hp-q"><div class="hp-bubble">${esc(question)}</div></div>`);
2164
+ }
2165
+
2166
+ function addAnswer(res) {
2167
+ const fallback = res.engine === "ad";
2168
+ const engine = !res.engine ? "" : fallback
2169
+ ? `<span class="hp-engine hp-engine-ad" title="claude was not available, so the AI Draftsman CLI answered">AI Draftsman fallback</span>`
2170
+ : `<span class="hp-engine">claude</span>`;
2171
+ const notes = (res.notes || []).map(n => `<div class="hp-note">${hpInline(esc(n))}</div>`).join("");
2172
+ const body = res.ok
2173
+ ? `<div class="hp-body">${renderHelpAnswer(res.answer || "(the helper returned an empty answer)")}</div>`
2174
+ : `<div class="hp-err"><strong>The helper couldn't finish.</strong> ${esc(res.error || "")}</div>`;
2175
+ const card = document.createElement("div");
2176
+ card.className = "hp-a";
2177
+ card.innerHTML = `
2178
+ <div class="hp-a-head">
2179
+ <span class="hp-avatar" aria-hidden="true">🤖</span>
2180
+ <strong>DevOps Bot Help</strong>${engine}
2181
+ <span class="spacer"></span>
2182
+ ${res.case ? `<span class="muted hp-case" title="Case folder on this machine">${esc(res.case)}</span>` : ""}
2183
+ ${res.ok ? `<button type="button" class="btn hp-copy-all">Copy answer</button>` : ""}
2184
+ </div>${notes}${body}`;
2185
+ $("hp-thread").appendChild(card);
2186
+ card.querySelectorAll(".hp-copy").forEach(b =>
2187
+ b.addEventListener("click", () => {
2188
+ const t = document.createElement("textarea"); t.innerHTML = b.getAttribute("data-copy");
2189
+ hpCopy(t.value, b);
2190
+ }));
2191
+ const all = card.querySelector(".hp-copy-all");
2192
+ if (all) all.addEventListener("click", () => hpCopy(res.answer || "", all));
2193
+ card.scrollIntoView({ behavior: "smooth", block: "start" });
2055
2194
  }
2056
2195
 
2057
2196
  async function ask(question, followUp) {
2058
- const btn = followUp ? $("hp-follow-btn") : $("hp-ask");
2059
- btn.disabled = true;
2060
- $("hp-status").textContent = followUp
2061
- ? "Thinking about your follow-up…"
2062
- : "Collecting diagnostics and logs, then reading the SDK source… (a minute or two)";
2197
+ if (busy) return;
2198
+ addQuestion(question);
2199
+ working(true, followUp);
2063
2200
  try {
2064
2201
  const res = await runJob("/api/ui/help/ask",
2065
- { question, case: followUp ? caseName : "" }, "agentos_help_job");
2202
+ { question, case: followUp ? caseName : "" },
2203
+ { persistKey: "agentos_help_job", onStart: (id) => { jobId = id; } });
2066
2204
  if (res.case) caseName = res.case;
2067
- show(res, question);
2068
- $("hp-follow").style.display = res.ok ? "" : "none";
2069
- $("hp-bundle").style.display = caseName ? "" : "none";
2205
+ addAnswer(res);
2206
+ conversation(true);
2207
+ $("hp-bundle-2").style.display = caseName ? "" : "none";
2208
+ $("hp-follow-q").focus();
2070
2209
  } catch (e) {
2071
- if (!e.cancelled) toast(e.message, "err");
2210
+ addAnswer({ ok: false, error: e.cancelled ? "Stopped." : e.message });
2211
+ conversation(true);
2072
2212
  } finally {
2073
- btn.disabled = false;
2074
- $("hp-status").textContent = "";
2213
+ working(false);
2075
2214
  }
2076
2215
  }
2077
2216
 
2078
- async function download() {
2079
- if (!caseName) return;
2080
- const res = await fetch(`/api/ui/help/case/${encodeURIComponent(caseName)}/bundle`,
2081
- { headers: { "Authorization": "Bearer " + TOKEN } });
2082
- if (!res.ok) { toast("Could not build the bundle", "err"); return; }
2083
- const url = URL.createObjectURL(await res.blob());
2084
- const a = document.createElement("a");
2085
- a.href = url; a.download = caseName + ".zip"; a.click();
2086
- setTimeout(() => URL.revokeObjectURL(url), 5000);
2087
- }
2088
-
2089
- $("hp-ask").addEventListener("click", () => {
2217
+ function askNew() {
2090
2218
  const q = $("hp-question").value.trim();
2091
- if (!q) { toast("Describe the problem first", "err"); return; }
2219
+ if (!q) { toast("Describe the problem first", "err"); $("hp-question").focus(); return; }
2092
2220
  $("hp-thread").innerHTML = "";
2093
2221
  caseName = "";
2222
+ $("hp-question").value = "";
2094
2223
  ask(q, false);
2095
- });
2096
- $("hp-follow-btn").addEventListener("click", () => {
2224
+ }
2225
+
2226
+ function askFollowUp() {
2097
2227
  const q = $("hp-follow-q").value.trim();
2098
2228
  if (!q) return;
2099
2229
  $("hp-follow-q").value = "";
2100
2230
  ask(q, true);
2231
+ }
2232
+
2233
+ async function download() {
2234
+ if (!caseName) return;
2235
+ const btn = $("hp-bundle"); btn.disabled = true;
2236
+ try {
2237
+ const res = await fetch(`/api/ui/help/case/${encodeURIComponent(caseName)}/bundle`,
2238
+ { headers: { "Authorization": "Bearer " + TOKEN } });
2239
+ if (!res.ok) throw new Error("could not build the bundle");
2240
+ const url = URL.createObjectURL(await res.blob());
2241
+ const a = document.createElement("a");
2242
+ a.href = url; a.download = caseName + ".zip"; a.click();
2243
+ setTimeout(() => URL.revokeObjectURL(url), 5000);
2244
+ } catch (e) { toast(e.message, "err"); }
2245
+ finally { btn.disabled = false; }
2246
+ }
2247
+
2248
+ $("hp-ask").addEventListener("click", askNew);
2249
+ $("hp-question").addEventListener("keydown", (e) => {
2250
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); askNew(); }
2251
+ });
2252
+ $("hp-follow-btn").addEventListener("click", askFollowUp);
2253
+ $("hp-follow-q").addEventListener("keydown", (e) => {
2254
+ if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); askFollowUp(); }
2255
+ });
2256
+ $("hp-new").addEventListener("click", () => {
2257
+ $("hp-thread").innerHTML = ""; caseName = "";
2258
+ $("hp-bundle").style.display = "none";
2259
+ conversation(false); $("hp-question").focus();
2101
2260
  });
2261
+ $("hp-stop").addEventListener("click", () => { if (jobId) cancelJob(jobId); });
2262
+ document.querySelectorAll(".hp-chip").forEach(chip =>
2263
+ chip.addEventListener("click", () => {
2264
+ $("hp-question").value = chip.textContent.trim();
2265
+ $("hp-question").focus();
2266
+ }));
2102
2267
  $("hp-doctor").addEventListener("click", async () => {
2103
2268
  const btn = $("hp-doctor"); btn.disabled = true;
2269
+ const was = btn.textContent; btn.textContent = "Collecting…";
2104
2270
  try {
2105
2271
  const d = await api("/api/ui/help/doctor", { method: "POST", body: {} });
2106
2272
  caseName = d.case;
2107
2273
  $("hp-diag").textContent = d.diagnostics + "\n" + d.errors;
2108
2274
  $("hp-diag-wrap").style.display = "";
2275
+ $("hp-diag-wrap").open = true;
2109
2276
  $("hp-bundle").style.display = "";
2277
+ $("hp-diag-wrap").scrollIntoView({ behavior: "smooth", block: "start" });
2110
2278
  } catch (e) { toast(e.message, "err"); }
2111
- finally { btn.disabled = false; }
2279
+ finally { btn.disabled = false; btn.textContent = was; }
2112
2280
  });
2113
2281
  $("hp-bundle").addEventListener("click", download);
2282
+ $("hp-bundle-2").addEventListener("click", download);
2283
+ $("hp-question").focus();
2114
2284
  }
2115
2285
 
2116
2286
  // ── RUN & LOGS: command buttons + live log ───────────────────────────────
@@ -0,0 +1,79 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>AgentOS — Help</title>
7
+ <link rel="stylesheet" href="/styles.css" />
8
+ </head>
9
+ <body data-page="help">
10
+ <div id="topbar"></div>
11
+
12
+ <div class="wrap hp-wrap">
13
+ <div class="hp-head">
14
+ <div>
15
+ <h1>DevOps Bot Help</h1>
16
+ <p class="muted">
17
+ Describe what's going wrong. The helper reads this machine's diagnostics and recent
18
+ logs, checks them against the installed SDK, and tells you the cause and the fix —
19
+ with the log or code line behind every claim.
20
+ </p>
21
+ </div>
22
+ <span class="hp-readonly" title="It can read files only: no commands, no edits, no web.">🔒 Read-only</span>
23
+ </div>
24
+
25
+ <div id="hp-thread" class="hp-thread"></div>
26
+
27
+ <div id="hp-empty" class="hp-empty">
28
+ <div class="muted hp-empty-label">Common problems — click one to start</div>
29
+ <div class="hp-chips">
30
+ <button type="button" class="hp-chip">My agentos-auto loop isn't picking up tickets</button>
31
+ <button type="button" class="hp-chip">agentos-auto update says the upgrade did not take</button>
32
+ <button type="button" class="hp-chip">Tickets are stuck In Progress and set-status fails</button>
33
+ <button type="button" class="hp-chip">The AI Draftsman fallback fails with 429 RESOURCE_EXHAUSTED</button>
34
+ <button type="button" class="hp-chip">A scheduled ticket run never started</button>
35
+ <button type="button" class="hp-chip">agentos-auto stop crashes on Windows</button>
36
+ </div>
37
+ </div>
38
+
39
+ <div id="hp-working" class="hp-working" style="display:none">
40
+ <span class="hp-spinner" aria-hidden="true"></span>
41
+ <span id="hp-status">Working…</span>
42
+ <span id="hp-elapsed" class="muted hp-elapsed">0:00</span>
43
+ <button id="hp-stop" class="btn" type="button">Stop</button>
44
+ </div>
45
+
46
+ <div class="hp-composer panelbox">
47
+ <div class="bd">
48
+ <label for="hp-question" class="hp-label">Your problem</label>
49
+ <textarea id="hp-question" rows="3"
50
+ placeholder="e.g. agentos-auto stop crashes with AttributeError getpgid and tickets stay In Progress"></textarea>
51
+ <div class="hp-actions">
52
+ <button id="hp-ask" class="btn primary">Ask</button>
53
+ <span class="muted hp-kbd">Ctrl / ⌘ + Enter</span>
54
+ <span class="spacer"></span>
55
+ <button id="hp-doctor" class="btn" type="button" title="Collect this machine's diagnostics without asking the AI">Collect diagnostics only</button>
56
+ <button id="hp-bundle" class="btn" type="button" style="display:none"
57
+ title="A zip of the diagnostics, errors and logs — no source code, secrets removed">⬇ Bundle for your admin</button>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div id="hp-follow" class="hp-follow panelbox" style="display:none">
63
+ <div class="bd hp-follow-row">
64
+ <input id="hp-follow-q" type="text" placeholder="Ask a follow-up about the same problem (Enter to send)" />
65
+ <button id="hp-follow-btn" class="btn primary">Send</button>
66
+ <button id="hp-bundle-2" class="btn" type="button" style="display:none"
67
+ title="A zip of the diagnostics, errors and logs — no source code, secrets removed">⬇ Bundle</button>
68
+ <button id="hp-new" class="btn" type="button">New question</button>
69
+ </div>
70
+ </div>
71
+
72
+ <details id="hp-diag-wrap" class="hp-diag panelbox" style="display:none">
73
+ <summary>Diagnostics collected from this machine</summary>
74
+ <pre id="hp-diag"></pre>
75
+ </details>
76
+ </div>
77
+ <script src="/app.js"></script>
78
+ </body>
79
+ </html>
@@ -53,10 +53,12 @@ a { color: var(--accent); }
53
53
  .wrap { max-width: 1160px; margin: 0 auto; padding: 22px; }
54
54
 
55
55
  /* ── Main nav ── */
56
- .mainnav { display: flex; gap: 4px; margin-left: 10px; }
56
+ .mainnav { display: flex; gap: 4px; margin-left: 10px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
57
+ .mainnav::-webkit-scrollbar { display: none; }
57
58
  .mainnav a {
59
+ white-space: nowrap; flex: none;
58
60
  text-decoration: none; color: var(--muted); font-weight: 600; font-size: 14px;
59
- padding: 7px 13px; border-radius: 8px; transition: .12s;
61
+ padding: 7px 10px; border-radius: 8px; transition: .12s;
60
62
  }
61
63
  .mainnav a:hover { background: var(--panel-2); color: var(--text); }
62
64
  .mainnav a.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
@@ -444,10 +446,99 @@ tr:last-child td { border-bottom: none; }
444
446
  .sc-tk-list .empty { padding: 10px; }
445
447
 
446
448
  /* Help page */
447
- #hp-question { width: 100%; }
448
- #hp-follow-q { flex: 1; min-width: 200px; }
449
- .hp-q, .hp-a { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-top: 10px; }
450
- .hp-q > div { margin-top: 4px; white-space: pre-wrap; }
451
- .hp-answer { white-space: pre-wrap; word-break: break-word; margin: 8px 0 0; max-height: 70vh; overflow: auto; }
452
- .hp-note { margin-top: 8px; color: var(--muted); }
453
- .hp-err { margin-top: 8px; color: var(--red); }
449
+ .hp-wrap { max-width: 920px; }
450
+ .hp-head { display: flex; gap: 16px; align-items: flex-start; margin: 4px 0 18px; }
451
+ .hp-head h1 { margin: 0 0 6px; font-size: 22px; }
452
+ .hp-head p { margin: 0; max-width: 680px; }
453
+ .hp-readonly { flex: none; font-size: 12px; font-weight: 600; color: var(--green);
454
+ border: 1px solid color-mix(in srgb, var(--green) 40%, var(--border)); border-radius: 999px; padding: 4px 10px; }
455
+
456
+ .hp-empty { margin-bottom: 14px; }
457
+ .hp-empty-label { font-size: 13px; margin-bottom: 8px; }
458
+ .hp-chips { display: flex; flex-wrap: wrap; gap: 8px; }
459
+ .hp-chip { font: inherit; font-size: 13px; color: var(--text); background: var(--panel); cursor: pointer;
460
+ border: 1px solid var(--border); border-radius: 999px; padding: 6px 12px; transition: .12s; }
461
+ .hp-chip:hover { border-color: var(--accent); color: var(--accent); }
462
+
463
+ .hp-composer .bd, .hp-follow .bd { padding: 14px 16px; }
464
+ .hp-label { display: block; font-weight: 600; font-size: 13px; margin-bottom: 6px; }
465
+ #hp-question { width: 100%; font: inherit; font-size: 15px; color: var(--text); background: var(--panel-2);
466
+ border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; resize: vertical; min-height: 84px; }
467
+ #hp-question:focus, #hp-follow-q:focus { outline: none; border-color: var(--accent);
468
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
469
+ .hp-actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
470
+ .hp-kbd { font-size: 12px; }
471
+
472
+ .hp-thread { display: flex; flex-direction: column; gap: 12px; margin-bottom: 14px; }
473
+ .hp-thread:empty { display: none; }
474
+ .hp-q { display: flex; justify-content: flex-end; }
475
+ .hp-bubble { max-width: 80%; background: var(--accent); color: var(--accent-ink); padding: 9px 14px;
476
+ border-radius: 14px 14px 4px 14px; white-space: pre-wrap; word-break: break-word; }
477
+ .hp-a { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
478
+ box-shadow: var(--shadow); padding: 14px 16px; }
479
+ .hp-a-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
480
+ .hp-avatar { font-size: 18px; }
481
+ .hp-engine { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
482
+ color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
483
+ border-radius: 6px; padding: 2px 7px; }
484
+ .hp-engine-ad { color: var(--amber); background: color-mix(in srgb, var(--amber) 14%, transparent); }
485
+ .hp-case { font-size: 12px; font-family: ui-monospace, Menlo, Consolas, monospace; }
486
+ .hp-a .btn { padding: 4px 10px; font-size: 12px; }
487
+
488
+ .hp-body { font-size: 14.5px; line-height: 1.55; }
489
+ .hp-body p { margin: 4px 0; }
490
+ .hp-body ul, .hp-body ol { margin: 4px 0 6px; padding-left: 22px; }
491
+ .hp-body li { margin: 3px 0; }
492
+ .hp-body code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12.5px;
493
+ background: var(--panel-2); border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px;
494
+ word-break: break-word; }
495
+ .hp-sec { border-left: 3px solid var(--border); padding: 2px 0 2px 12px; margin: 10px 0; }
496
+ .hp-sec-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px;
497
+ color: var(--muted); margin-bottom: 2px; }
498
+ .hp-sec-diagnosis { border-left-color: var(--accent); background: color-mix(in srgb, var(--accent) 7%, transparent);
499
+ border-radius: 0 8px 8px 0; padding: 8px 12px; }
500
+ .hp-sec-diagnosis .hp-sec-label { color: var(--accent); }
501
+ .hp-sec-diagnosis p { font-size: 15px; font-weight: 500; }
502
+ .hp-sec-fix { border-left-color: var(--green); }
503
+ .hp-sec-fix .hp-sec-label { color: var(--green); }
504
+ .hp-sec-next { border-left-color: var(--amber); }
505
+ .hp-code { position: relative; margin: 6px 0; }
506
+ .hp-code pre { margin: 0; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
507
+ padding: 10px 12px; overflow-x: auto; font-size: 12.5px; }
508
+ .hp-code pre code { background: none; border: 0; padding: 0; }
509
+ .hp-copy { position: absolute; top: 6px; right: 6px; font: inherit; font-size: 11px; cursor: pointer;
510
+ color: var(--muted); background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 2px 8px; }
511
+ .hp-copy:hover { color: var(--accent); border-color: var(--accent); }
512
+ .hp-warn { margin: 6px 0; padding: 7px 10px; border-radius: 8px; font-size: 13.5px;
513
+ background: color-mix(in srgb, var(--amber) 12%, transparent); border: 1px solid color-mix(in srgb, var(--amber) 35%, transparent); }
514
+ .hp-warn::before { content: "⚠ "; }
515
+ .hp-note { margin: 0 0 8px; padding: 7px 10px; border-radius: 8px; font-size: 13px; color: var(--text);
516
+ background: color-mix(in srgb, var(--amber) 10%, transparent); }
517
+ .hp-err { padding: 8px 10px; border-radius: 8px; color: var(--red);
518
+ background: color-mix(in srgb, var(--red) 8%, transparent); }
519
+
520
+ .hp-working { display: flex; align-items: center; gap: 10px; margin: 0 0 14px; padding: 10px 14px;
521
+ background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); }
522
+ .hp-spinner { width: 16px; height: 16px; border-radius: 50%; flex: none;
523
+ border: 2px solid color-mix(in srgb, var(--accent) 25%, transparent); border-top-color: var(--accent);
524
+ animation: hp-spin .8s linear infinite; }
525
+ @keyframes hp-spin { to { transform: rotate(360deg); } }
526
+ .hp-elapsed { font-variant-numeric: tabular-nums; margin-left: auto; }
527
+
528
+ .hp-follow { margin-top: 0; }
529
+ .hp-follow-row { display: flex; gap: 8px; align-items: center; }
530
+ #hp-follow-q { flex: 1; min-width: 0; font: inherit; color: var(--text); background: var(--panel-2);
531
+ border: 1px solid var(--border); border-radius: 10px; padding: 9px 12px; }
532
+
533
+ .hp-diag { margin-top: 14px; }
534
+ .hp-diag summary { cursor: pointer; padding: 12px 16px; font-weight: 600; }
535
+ .hp-diag pre { margin: 0; padding: 0 16px 16px; white-space: pre-wrap; word-break: break-word;
536
+ font-size: 12px; max-height: 60vh; overflow: auto; }
537
+
538
+ @media (max-width: 640px) {
539
+ .hp-head { flex-direction: column; }
540
+ .hp-bubble { max-width: 92%; }
541
+ .hp-follow-row { flex-wrap: wrap; }
542
+ #hp-follow-q { flex: 1 1 100%; }
543
+ .hp-kbd { display: none; }
544
+ }
@@ -149,3 +149,53 @@ class TestPage:
149
149
  js = (STATIC / "app.js").read_text()
150
150
  assert 'href="/help.html"' in js and 'page === "help"' in js
151
151
  assert "/api/ui/help/ask" in js and "/api/ui/help/doctor" in js
152
+
153
+
154
+ # ── The page's script, not just its strings ──────────────────────────────────
155
+ # Added after the Help page shipped with `initHelp` using `$` without defining it:
156
+ # every check above passed (they look for strings), and Ask did nothing.
157
+
158
+ import re
159
+ import shutil
160
+ import subprocess
161
+
162
+
163
+ def _init_functions(js: str) -> dict[str, str]:
164
+ """`function initX() { … }` bodies, cut at the next top-level `function`."""
165
+ out = {}
166
+ for m in re.finditer(r"\n function (init\w+)\(\) \{", js):
167
+ rest = js[m.end():]
168
+ nxt = re.search(r"\n (?:async )?function \w+\(", rest)
169
+ out[m.group(1)] = rest[: nxt.start() if nxt else len(rest)]
170
+ return out
171
+
172
+
173
+ def test_every_page_init_defines_the_dollar_it_uses():
174
+ js = (STATIC / "app.js").read_text()
175
+ inits = _init_functions(js)
176
+ assert "initHelp" in inits
177
+ missing = [name for name, body in inits.items()
178
+ if "$(" in body and "const $ =" not in body
179
+ and not re.search(r"\n const \$ =", js[: js.index("function " + name)])]
180
+ assert not missing, f"these use $(…) without defining it: {missing}"
181
+
182
+
183
+ @pytest.mark.skipif(not shutil.which("node"), reason="node is not installed")
184
+ def test_the_answer_renderer_escapes_and_structures():
185
+ js = (STATIC / "app.js").read_text()
186
+ pieces = [re.search(r" function esc\(s\).*\n", js).group(0)]
187
+ start = js.index(" const HP_SECTIONS")
188
+ pieces.append(js[start: js.index(" async function hpCopy")])
189
+ answer = ("Diagnosis: loop is <b>not</b> running\n\nEvidence:\n- diagnostics.md:7: `x`\n\n"
190
+ "Fix:\n1. run `agentos-auto`\n ```\n agentos-auto logs -f\n ```\n"
191
+ "⚠ `set-status` changes status\n<script>alert(1)</script>\n"
192
+ "If that doesn't work: send the folder")
193
+ script = "\n".join(pieces) + f"\nprocess.stdout.write(renderHelpAnswer({answer!r}));"
194
+ html = subprocess.run(["node", "-e", script], capture_output=True, text=True, check=True).stdout
195
+ assert "<script>" not in html and "&lt;script&gt;" in html
196
+ assert "&lt;b&gt;not&lt;/b&gt;" in html
197
+ for cls in ("hp-sec-diagnosis", "hp-sec-evidence", "hp-sec-fix", "hp-sec-next"):
198
+ assert cls in html
199
+ assert "<ol><li>run <code>agentos-auto</code></li></ol>" in html
200
+ assert "<pre><code>agentos-auto logs -f</code></pre>" in html, "fenced block dedented"
201
+ assert '<div class="hp-warn"><code>set-status</code> changes status</div>' in html