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.
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/SOURCES.txt +3 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/cli.py +53 -3
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/client.py +123 -40
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/exceptions.py +17 -0
- devops_bot_sdk-1.6.28/sdk/proof_readers.py +420 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/self_report.py +184 -13
- devops_bot_sdk-1.6.28/sdk/self_report_local.py +631 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/api.py +42 -3
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/app.js +11 -3
- devops_bot_sdk-1.6.28/tests/test_proof_readers.py +263 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_self_report.py +416 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/README.md +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/login.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_otp_login.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.28}/tests/test_vps_health.py +0 -0
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
874
|
-
|
|
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
|
-
#
|
|
1555
|
-
#
|
|
1556
|
-
# the
|
|
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
|
-
|
|
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
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
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
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
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`.
|
|
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
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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
|
-
|
|
1640
|
-
|
|
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
|
-
|
|
1648
|
-
|
|
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
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
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
|
-
|
|
1664
|
-
|
|
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.
|
|
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
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
-
|
|
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:
|