devops-bot-sdk 1.6.26__tar.gz → 1.6.28__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 (159) hide show
  1. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/SOURCES.txt +3 -0
  4. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/cli.py +53 -3
  6. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/client.py +123 -40
  7. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/exceptions.py +17 -0
  8. devops_bot_sdk-1.6.28/sdk/proof_readers.py +420 -0
  9. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/self_report.py +184 -13
  10. devops_bot_sdk-1.6.28/sdk/self_report_local.py +631 -0
  11. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/api.py +42 -3
  12. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/app.js +11 -3
  13. devops_bot_sdk-1.6.28/tests/test_proof_readers.py +263 -0
  14. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_self_report.py +416 -0
  15. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/README.md +0 -0
  16. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  17. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  18. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/requires.txt +0 -0
  19. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  20. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/pyproject.toml +0 -0
  21. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agentd.py +0 -0
  22. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/__init__.py +0 -0
  23. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/activity.py +0 -0
  24. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/builder.py +0 -0
  25. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/chat.py +0 -0
  26. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/connectors.py +0 -0
  27. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/__init__.py +0 -0
  28. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/claude_code.py +0 -0
  29. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/http.py +0 -0
  30. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/mcp.py +0 -0
  31. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/n8n.py +0 -0
  32. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/shell.py +0 -0
  33. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/integrations.py +0 -0
  34. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/inventory.py +0 -0
  35. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/library/__init__.py +0 -0
  36. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/manifest.py +0 -0
  37. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/prompt_studio.py +0 -0
  38. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/prompt_template.py +0 -0
  39. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/prompt_vars.py +0 -0
  40. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/run_log.py +0 -0
  41. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/schedules.py +0 -0
  42. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/secrets.py +0 -0
  43. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/ticket_run.py +0 -0
  44. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/usage.py +0 -0
  45. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/varstore.py +0 -0
  46. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/__init__.py +0 -0
  47. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/files.py +0 -0
  48. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/process.py +0 -0
  49. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/screenshot.py +0 -0
  50. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/config.py +0 -0
  51. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/crucial.py +0 -0
  52. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/departments.py +0 -0
  53. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/__init__.py +0 -0
  54. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/assets.py +0 -0
  55. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/bootstrap.py +0 -0
  56. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/browser.py +0 -0
  57. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/compare.py +0 -0
  58. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/loop.py +0 -0
  59. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/formats.py +0 -0
  60. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/git_ops.py +0 -0
  61. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/github_ingest.py +0 -0
  62. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/graph_recon.py +0 -0
  63. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/graphify.py +0 -0
  64. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/hooks/__init__.py +0 -0
  65. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/hooks/crucial_guard.py +0 -0
  66. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/intake.py +0 -0
  67. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ipc/__init__.py +0 -0
  68. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ipc/electron_bridge.py +0 -0
  69. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ipc/handlers.py +0 -0
  70. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/local_exec.py +0 -0
  71. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/__init__.py +0 -0
  72. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/envelope.py +0 -0
  73. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/requests.py +0 -0
  74. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/responses.py +0 -0
  75. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/snapshots.py +0 -0
  76. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/phase_timer.py +0 -0
  77. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/plan_run.py +0 -0
  78. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/platform_compat.py +0 -0
  79. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/py.typed +0 -0
  80. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/run_auto.py +0 -0
  81. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/scheduler.py +0 -0
  82. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/sse.py +0 -0
  83. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/task_file.py +0 -0
  84. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/test.py +0 -0
  85. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/test_pipeline.py +0 -0
  86. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ticket_fields.py +0 -0
  87. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/__init__.py +0 -0
  88. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/dashboard.py +0 -0
  89. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/health.py +0 -0
  90. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/ip_allowlist.py +0 -0
  91. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/jobs.py +0 -0
  92. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/models.py +0 -0
  93. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/runner.py +0 -0
  94. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/security.py +0 -0
  95. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/server.py +0 -0
  96. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/service.py +0 -0
  97. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/session.py +0 -0
  98. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/setup_manager.py +0 -0
  99. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/agents.html +0 -0
  100. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/chat.html +0 -0
  101. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/custom-agents.html +0 -0
  102. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/default-agents.html +0 -0
  103. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/index.html +0 -0
  104. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/login.html +0 -0
  105. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/report.html +0 -0
  106. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/runs.html +0 -0
  107. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/schedules.html +0 -0
  108. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/setup.html +0 -0
  109. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/styles.css +0 -0
  110. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/tickets.html +0 -0
  111. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/updater.py +0 -0
  112. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/vps_health.py +0 -0
  113. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/setup.cfg +0 -0
  114. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_activity.py +0 -0
  115. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_activity_tracking.py +0 -0
  116. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_chat_features.py +0 -0
  117. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_listing.py +0 -0
  118. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_prompt_async.py +0 -0
  119. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_prompt_models.py +0 -0
  120. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_run_ticket.py +0 -0
  121. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_schedules.py +0 -0
  122. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_workspace_settings.py +0 -0
  123. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agents_connectors_builder.py +0 -0
  124. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_allowed_tools_config.py +0 -0
  125. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_auto_agent_dispatch.py +0 -0
  126. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_client_jira_projects.py +0 -0
  127. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_config_token_diagnostics.py +0 -0
  128. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_configure_token_owner.py +0 -0
  129. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_console_encoding.py +0 -0
  130. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_departments.py +0 -0
  131. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_epic_decompose_run.py +0 -0
  132. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_epic_inherited_agent_id.py +0 -0
  133. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_epic_session_isolation.py +0 -0
  134. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_formats.py +0 -0
  135. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_git_action.py +0 -0
  136. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_github_ingest.py +0 -0
  137. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_graph_recon.py +0 -0
  138. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_graphify_build.py +0 -0
  139. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_graphify_hook_install.py +0 -0
  140. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_health.py +0 -0
  141. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_intake_plan_review.py +0 -0
  142. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_otp_login.py +0 -0
  143. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_phase_timer.py +0 -0
  144. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_plan_new_command.py +0 -0
  145. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_plan_run_activity.py +0 -0
  146. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_recon_session_isolation.py +0 -0
  147. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_run_auto_agent_only.py +0 -0
  148. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_run_outputs.py +0 -0
  149. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_schedule_api.py +0 -0
  150. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_scheduler.py +0 -0
  151. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_service_token_warning.py +0 -0
  152. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_task_file_and_plan_run.py +0 -0
  153. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_task_file_format_doc.py +0 -0
  154. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_ticket_fields.py +0 -0
  155. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_ticket_run_resolve_by_id.py +0 -0
  156. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_ticket_speed_fixes.py +0 -0
  157. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_update_timer.py +0 -0
  158. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_updater_extras.py +0 -0
  159. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_vps_health.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.26
3
+ Version: 1.6.28
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.26
3
+ Version: 1.6.28
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
@@ -24,10 +24,12 @@ sdk/local_exec.py
24
24
  sdk/phase_timer.py
25
25
  sdk/plan_run.py
26
26
  sdk/platform_compat.py
27
+ sdk/proof_readers.py
27
28
  sdk/py.typed
28
29
  sdk/run_auto.py
29
30
  sdk/scheduler.py
30
31
  sdk/self_report.py
32
+ sdk/self_report_local.py
31
33
  sdk/sse.py
32
34
  sdk/task_file.py
33
35
  sdk/test.py
@@ -137,6 +139,7 @@ tests/test_otp_login.py
137
139
  tests/test_phase_timer.py
138
140
  tests/test_plan_new_command.py
139
141
  tests/test_plan_run_activity.py
142
+ tests/test_proof_readers.py
140
143
  tests/test_recon_session_isolation.py
141
144
  tests/test_run_auto_agent_only.py
142
145
  tests/test_run_outputs.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.26
3
+ Version: 1.6.28
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.26" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.28" # 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
@@ -67,6 +67,9 @@ Command surface:
67
67
  sdk/self_report.py.
68
68
  agentos report list [--limit N]
69
69
  Recent submissions, newest first: date, score, verdict, id.
70
+ agentos report formats
71
+ Every proof format the SDK can read on this machine, and what happens
72
+ to the ones it cannot.
70
73
  agentos report show <ID>
71
74
  One submission in full — its text, its proofs and the evaluator's
72
75
  per-criterion reasoning.
@@ -728,6 +731,7 @@ def _report_usage() -> None:
728
731
  print(" [--project NAME] [--proof PATH|URL]… [--no-wait]")
729
732
  print(" agentos report list [--limit N]")
730
733
  print(" agentos report show <ID>")
734
+ print(" agentos report formats # what a proof can be")
731
735
  print()
732
736
  print(" Report work that never reached Jira or GitHub, attach proofs, and have the")
733
737
  print(" submission scored against them. It then appears in your daily and overall")
@@ -762,7 +766,7 @@ def _report_command(args: list[str]) -> None:
762
766
  ``agentos configure`` prints, and exit 1 — never a traceback.
763
767
  """
764
768
  from sdk.exceptions import TokenNotConfigured
765
- from sdk.self_report import SelfReportError
769
+ from sdk.self_report import _SUBMIT_ERRORS, SelfReportError
766
770
 
767
771
  verb = args[0] if args else None
768
772
  rest = args[1:]
@@ -778,6 +782,9 @@ def _report_command(args: list[str]) -> None:
778
782
  if verb == "show" and rest and not rest[0].startswith("-"):
779
783
  _report_show(rest[0])
780
784
  return
785
+ if verb == "formats":
786
+ _report_formats()
787
+ return
781
788
  except TokenNotConfigured as exc:
782
789
  # The same failure every other verb gives on an unconfigured machine —
783
790
  # `sdk.config` composes a cause-specific message; we only add the prefix.
@@ -786,6 +793,12 @@ def _report_command(args: list[str]) -> None:
786
793
  except SelfReportError as exc:
787
794
  print(f"error: {exc}")
788
795
  sys.exit(1)
796
+ except _SUBMIT_ERRORS as exc:
797
+ # The backend answered and the answer was no. Its own words — a 404 says
798
+ # the deployment has no self-report routes, which is not a token problem
799
+ # and must not be printed as one.
800
+ print(f"error: {exc}")
801
+ sys.exit(1)
789
802
 
790
803
  _report_usage()
791
804
  sys.exit(1)
@@ -842,6 +855,13 @@ def _report_submit(args: list[str]) -> None:
842
855
  + (f" project: {result['project']}" if result.get("project") else ""))
843
856
  print(f"proofs : {len(result.get('proofs') or [])} accepted"
844
857
  + (f", {len(result.get('rejected') or [])} skipped" if result.get("rejected") else ""))
858
+ if result.get("stored") == "gateway":
859
+ # Say which half holds it. "Recorded" and "recorded in the durable store"
860
+ # are different promises, and only the person filing it can decide whether
861
+ # the difference matters to them.
862
+ print("stored : locally + the activity feed the daily/overall reports read")
863
+ print(" (the platform's self-report store is not deployed on this "
864
+ "backend; scored on this machine)")
845
865
  if status == "evaluated":
846
866
  print(f"score : {result.get('score', 0)}/100")
847
867
  print(f"verdict : {result.get('verdict') or 'unknown'}")
@@ -856,6 +876,33 @@ def _report_submit(args: list[str]) -> None:
856
876
  # backend-side outcome the person cannot act on, and it is recorded, not lost.
857
877
 
858
878
 
879
+ def _report_formats() -> None:
880
+ """What the SDK can read from a proof without the backend.
881
+
882
+ Worth its own verb: a person choosing what to attach needs to know that a
883
+ screen recording is usable and a .7z is not BEFORE they file, not after the
884
+ score comes back lower than the work deserved.
885
+ """
886
+ from sdk import proof_readers
887
+
888
+ print("Proof files the SDK reads on this machine:\n")
889
+ for group, exts in proof_readers.supported().items():
890
+ joined = " ".join(exts)
891
+ print(f" {group}")
892
+ for i in range(0, len(joined), 68):
893
+ print(f" {joined[i:i + 68]}")
894
+ print()
895
+ if proof_readers.have_ffmpeg():
896
+ print(" ffmpeg is installed — video proofs are sampled into still frames the")
897
+ print(" scorer can look at.")
898
+ else:
899
+ print(" ffmpeg is NOT installed — a video proof is recorded and described, but")
900
+ print(" its content cannot be inspected. Install ffmpeg to change that.")
901
+ print(" Audio cannot be transcribed locally; it is recorded and described.")
902
+ print(" URL proofs are fetched here. A GitHub PR or issue is read through the API")
903
+ print(" with your linked token, so proofs in private repos are readable.")
904
+
905
+
859
906
  def _report_list(limit: int) -> None:
860
907
  """Recent submissions, newest first — date, score, verdict, id."""
861
908
  from sdk import self_report
@@ -870,8 +917,11 @@ def _report_list(limit: int) -> None:
870
917
  score = f"{ev.get('score')}/100" if r.get("status") == "evaluated" else "—"
871
918
  verdict = (ev.get("verdict") or r.get("status") or "")[:25]
872
919
  what = " ".join(str(r.get("text") or "").split())[:46]
873
- print(f"{str(r.get('date', '')):<12}{score:<8}{verdict:<26}"
874
- f"{str(r.get('id', '')):<26}{what}")
920
+ rid = str(r.get("id", "")) + (" *" if r.get("stored") == "gateway" else "")
921
+ print(f"{str(r.get('date', '')):<12}{score:<8}{verdict:<26}{rid:<26}{what}")
922
+ if any(r.get("stored") == "gateway" for r in rows):
923
+ print("\n* recorded on this machine and in the activity feed — the platform's "
924
+ "self-report store was not deployed when it was filed.")
875
925
 
876
926
 
877
927
  def _report_show(report_id: str) -> None:
@@ -23,7 +23,7 @@ from typing import Any, AsyncGenerator
23
23
  import httpx
24
24
 
25
25
  from sdk.exceptions import (
26
- BackendAuthFailed, BackendFeatureMissing, BackendPermissionDenied, BackendUnreachable,
26
+ BackendAuthFailed, BackendFeatureMissing, BackendPermissionDenied, BackendRejected, BackendUnreachable,
27
27
  BackendVersionTooOld, TokenNotConfigured,
28
28
  )
29
29
  from sdk.models.envelope import Envelope
@@ -1551,9 +1551,57 @@ class BackendClient:
1551
1551
  # `self_report`), the blob upload and the evaluator; the SDK only speaks
1552
1552
  # HTTP to it with the co_ token, exactly like every other method here.
1553
1553
  #
1554
- # Best-effort return shapes (dict / None) rather than raised errors, so the
1555
- # panel and the CLI can present a partial result instead of a traceback —
1556
- # the same posture `create_task_ml` and `list_jira_projects` take.
1554
+ # A FAILURE is typed, an EMPTY is empty. Every call in this family routes its
1555
+ # response through `_check_report_status`, so the ways it can go wrong stay
1556
+ # different answers: 401 = the token needs reconfiguring, 403 = not this
1557
+ # caller's to write, 400/422 = the backend read the submission and refused it,
1558
+ # with its own reason. Collapsing them into `None` is what once made a backend
1559
+ # missing `routes/reports.py` read as "check `agentos configure`" — a config
1560
+ # that was fine, and an operator sent to fix it.
1561
+ #
1562
+ # 404 depends on the PATH, which is why `_check_report_status` takes a flag.
1563
+ # On the collection (`/reports/self`) there is no id to be wrong about, so it
1564
+ # means the routes are not deployed. On an item (`/reports/self/{id}`) it is a
1565
+ # documented answer — unknown id, or not this caller's — and comes back as
1566
+ # None. Reading the second as the first would send someone to chase a
1567
+ # deployment over a typo.
1568
+ #
1569
+ # `get_self_report` stays best-effort (None) beyond that, because it is the
1570
+ # poll loop's read: a blip mid-evaluation must not turn a STORED submission
1571
+ # into a reported failure.
1572
+
1573
+ @staticmethod
1574
+ def _backend_detail(resp: httpx.Response) -> str:
1575
+ """The backend's own words for a failure, trimmed to a banner line."""
1576
+ try:
1577
+ body = resp.json()
1578
+ except ValueError:
1579
+ return (resp.text or "").strip()[:300]
1580
+ if isinstance(body, dict):
1581
+ for key in ("detail", "message", "error"):
1582
+ val = body.get(key)
1583
+ if val:
1584
+ return str(val)[:300]
1585
+ return (resp.text or "").strip()[:300]
1586
+
1587
+ def _check_report_status(self, resp: httpx.Response, url: str, *,
1588
+ missing_is_absent_feature: bool = True) -> None:
1589
+ """Raise the error the status code actually means. No-op under 400.
1590
+
1591
+ ``missing_is_absent_feature`` is what makes 404 readable. On the COLLECTION
1592
+ paths (``/reports/self``) there is no id to be wrong about, so a 404 can
1593
+ only mean the route is not deployed. On an ITEM path (``/reports/self/{id}``)
1594
+ 404 is a documented answer — that submission does not exist, or is not this
1595
+ caller's — and reporting it as a missing feature would send someone to chase
1596
+ a deployment over a typo'd id.
1597
+ """
1598
+ if resp.status_code == 404 and missing_is_absent_feature:
1599
+ raise BackendFeatureMissing("Self-reporting", url)
1600
+ if resp.status_code == 403:
1601
+ raise BackendPermissionDenied(self._backend_detail(resp), url)
1602
+ _check_status(resp.status_code, url) # 401 / 426 / 5xx
1603
+ if resp.status_code >= 400: # 400, 422, everything else
1604
+ raise BackendRejected(self._backend_detail(resp), resp.status_code, url)
1557
1605
 
1558
1606
  async def submit_self_report(
1559
1607
  self,
@@ -1579,8 +1627,9 @@ class BackendClient:
1579
1627
  purpose: an ``UploadFile``'s spooled temp file dies with the request,
1580
1628
  while the evaluation job deliberately outlives it.
1581
1629
 
1582
- Returns the parsed 202 body (``{id, job_id, status, status_url, …}``)
1583
- or None on any failure.
1630
+ Returns the parsed 202 body (``{id, job_id, status, status_url, …}``).
1631
+ Raises rather than returning None when the backend answers: a submission
1632
+ that was NOT stored must say why — see `_check_report_status`.
1584
1633
  """
1585
1634
  url = self._url("/api/v1/reports/self")
1586
1635
  headers = {k: v for k, v in self._headers.items() if k.lower() != "content-type"}
@@ -1607,22 +1656,34 @@ class BackendClient:
1607
1656
  # Generous: the request itself uploads every proof's bytes.
1608
1657
  async with httpx.AsyncClient(timeout=180.0) as client:
1609
1658
  resp = await client.post(url, files=parts, headers=headers)
1610
- if resp.status_code >= 400:
1611
- logger.warning("client.submit_self_report failed status=%s body=%s",
1612
- resp.status_code, resp.text[:300])
1613
- return None
1659
+ except httpx.RequestError as exc:
1660
+ logger.warning("client.submit_self_report unreachable err=%s", exc)
1661
+ raise BackendUnreachable(url, str(exc)) from exc
1662
+ if resp.status_code >= 400:
1663
+ logger.warning("client.submit_self_report failed status=%s body=%s",
1664
+ resp.status_code, resp.text[:300])
1665
+ self._check_report_status(resp, url)
1666
+ try:
1614
1667
  body = resp.json() or {}
1615
- return body if isinstance(body, dict) else None
1616
- except Exception as exc: # noqa: BLE001
1617
- logger.warning("client.submit_self_report error err=%s", exc)
1618
- return None
1668
+ except ValueError as exc:
1669
+ raise BackendRejected("the backend returned non-JSON to a submission.",
1670
+ resp.status_code, url) from exc
1671
+ return body if isinstance(body, dict) else None
1619
1672
 
1620
1673
  async def get_self_report(self, report_id: str) -> dict | None:
1621
1674
  """GET /api/v1/reports/self/{id} — one submission and its current verdict.
1622
1675
 
1623
1676
  This is what the poll loop reads while evaluation runs server-side;
1624
1677
  `status` moves through `accepted` → `evaluating` → `evaluated` |
1625
- `evaluation_failed`. None on any failure.
1678
+ `evaluation_failed`.
1679
+
1680
+ None on a TRANSIENT failure (a blip, a 5xx, a dropped connection) — the
1681
+ submission is already stored by the time anyone calls this, and one bad
1682
+ read must not be reported as a lost report. Also None on 404, which on
1683
+ this path is a real answer: no such submission, or not this caller's (the
1684
+ backend scopes the lookup, so the two are deliberately indistinguishable).
1685
+ A backend that refuses the caller (403) or rejects the token (401) raises
1686
+ — those do not come back on the next poll.
1626
1687
  """
1627
1688
  rid = str(report_id or "").strip()
1628
1689
  if not rid:
@@ -1631,21 +1692,31 @@ class BackendClient:
1631
1692
  try:
1632
1693
  async with httpx.AsyncClient(timeout=30.0) as client:
1633
1694
  resp = await client.get(url, headers=self._headers)
1634
- if resp.status_code >= 400:
1635
- logger.warning("client.get_self_report failed id=%s status=%s body=%s",
1636
- rid, resp.status_code, resp.text[:200])
1637
- return None
1695
+ except httpx.RequestError as exc:
1696
+ logger.warning("client.get_self_report unreachable id=%s err=%s", rid, exc)
1697
+ return None
1698
+ if resp.status_code >= 400:
1699
+ logger.warning("client.get_self_report failed id=%s status=%s body=%s",
1700
+ rid, resp.status_code, resp.text[:200])
1701
+ if resp.status_code in (401, 403, 426):
1702
+ self._check_report_status(resp, url, missing_is_absent_feature=False)
1703
+ if resp.status_code >= 400:
1704
+ # 404 = no such submission; 5xx = a blip the next poll retries.
1705
+ return None
1706
+ try:
1638
1707
  body = resp.json() or {}
1639
- return body if isinstance(body, dict) else None
1640
- except Exception as exc: # noqa: BLE001
1641
- logger.warning("client.get_self_report error id=%s err=%s", rid, exc)
1708
+ except ValueError:
1709
+ logger.warning("client.get_self_report non-JSON id=%s", rid)
1642
1710
  return None
1711
+ return body if isinstance(body, dict) else None
1643
1712
 
1644
1713
  async def list_self_reports(self, limit: int = 25, user_id: str = "") -> list[dict]:
1645
1714
  """GET /api/v1/reports/self — this caller's submissions, newest first.
1646
1715
 
1647
- Returns [] on any failure — the panel renders an empty list rather than
1648
- an error banner, and the CLI says "no submissions yet".
1716
+ An empty list means "this caller has submitted nothing" and nothing
1717
+ else. A backend that cannot answer raises (`_check_report_status`), so
1718
+ "no submissions yet" is never a 404 wearing a friendly face — the state
1719
+ this list is FOR is exactly the state a masked failure imitates.
1649
1720
  """
1650
1721
  url = self._url("/api/v1/reports/self")
1651
1722
  params: dict[str, Any] = {"limit": max(1, min(int(limit or 25), 200))}
@@ -1655,23 +1726,30 @@ class BackendClient:
1655
1726
  try:
1656
1727
  async with httpx.AsyncClient(timeout=30.0) as client:
1657
1728
  resp = await client.get(url, params=params, headers=self._headers)
1658
- if resp.status_code >= 400:
1659
- logger.warning("client.list_self_reports failed status=%s body=%s",
1660
- resp.status_code, resp.text[:200])
1661
- return []
1729
+ except httpx.RequestError as exc:
1730
+ logger.warning("client.list_self_reports unreachable err=%s", exc)
1731
+ raise BackendUnreachable(url, str(exc)) from exc
1732
+ if resp.status_code >= 400:
1733
+ logger.warning("client.list_self_reports failed status=%s body=%s",
1734
+ resp.status_code, resp.text[:200])
1735
+ self._check_report_status(resp, url)
1736
+ try:
1662
1737
  body = resp.json() or {}
1663
- rows = body.get("reports") if isinstance(body, dict) else body
1664
- return [r for r in (rows or []) if isinstance(r, dict)]
1665
- except Exception as exc: # noqa: BLE001
1666
- logger.warning("client.list_self_reports error err=%s", exc)
1738
+ except ValueError:
1739
+ logger.warning("client.list_self_reports non-JSON body")
1667
1740
  return []
1741
+ rows = body.get("reports") if isinstance(body, dict) else body
1742
+ return [r for r in (rows or []) if isinstance(r, dict)]
1668
1743
 
1669
1744
  async def reevaluate_self_report(self, report_id: str) -> dict | None:
1670
1745
  """POST /api/v1/reports/self/{id}/evaluate — score an existing submission again.
1671
1746
 
1672
1747
  The backend REPLACES the stored verdict rather than appending a second
1673
1748
  one (the previous verdict moves into the submission's audit trail), so
1674
- this is safe to call repeatedly. None on any failure.
1749
+ this is safe to call repeatedly. Raises on a backend answer it cannot use
1750
+ — a re-evaluation that did not happen must say why, same as a submission —
1751
+ except 404, which on this item path means the submission is not there,
1752
+ and comes back as None.
1675
1753
  """
1676
1754
  rid = str(report_id or "").strip()
1677
1755
  if not rid:
@@ -1680,15 +1758,20 @@ class BackendClient:
1680
1758
  try:
1681
1759
  async with httpx.AsyncClient(timeout=60.0) as client:
1682
1760
  resp = await client.post(url, json={}, headers=self._headers)
1683
- if resp.status_code >= 400:
1684
- logger.warning("client.reevaluate_self_report failed id=%s status=%s body=%s",
1685
- rid, resp.status_code, resp.text[:300])
1686
- return None
1761
+ except httpx.RequestError as exc:
1762
+ logger.warning("client.reevaluate_self_report unreachable id=%s err=%s", rid, exc)
1763
+ raise BackendUnreachable(url, str(exc)) from exc
1764
+ if resp.status_code >= 400:
1765
+ logger.warning("client.reevaluate_self_report failed id=%s status=%s body=%s",
1766
+ rid, resp.status_code, resp.text[:300])
1767
+ if resp.status_code == 404:
1768
+ return None # no such submission
1769
+ self._check_report_status(resp, url)
1770
+ try:
1687
1771
  body = resp.json() or {}
1688
- return body if isinstance(body, dict) else None
1689
- except Exception as exc: # noqa: BLE001
1690
- logger.warning("client.reevaluate_self_report error id=%s err=%s", rid, exc)
1772
+ except ValueError:
1691
1773
  return None
1774
+ return body if isinstance(body, dict) else None
1692
1775
 
1693
1776
  # ── OAuth ─────────────────────────────────────────────────────────
1694
1777
 
@@ -84,6 +84,23 @@ class BackendPermissionDenied(Exception):
84
84
  self.url = url
85
85
 
86
86
 
87
+ class BackendRejected(Exception):
88
+ """Raised when the backend answers 4xx with its own reason (400, 422, …).
89
+
90
+ Not 401 (bad token), not 403 (not permitted), not 404 (feature absent): the
91
+ call reached a route that understood it and refused the CONTENT — a future
92
+ date, an empty body, a proof the content filter blocked. ``detail`` is the
93
+ backend's own words, so the surface can say WHY rather than guess.
94
+ """
95
+ def __init__(self, detail: str = "", status: int = 0, url: str = "") -> None:
96
+ super().__init__(
97
+ detail or f"The backend refused the request (HTTP {status} at {url})."
98
+ )
99
+ self.detail = detail
100
+ self.status = status
101
+ self.url = url
102
+
103
+
87
104
  class CollectorError(Exception):
88
105
  """Raised by collectors when a payload fails size or type validation."""
89
106
  def __init__(self, message: str, path: str = "") -> None: