devops-bot-sdk 1.6.26__tar.gz → 1.6.32__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 (163) hide show
  1. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/SOURCES.txt +7 -0
  4. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/__init__.py +2 -2
  5. devops_bot_sdk-1.6.32/sdk/browser_profile.py +362 -0
  6. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/cli.py +196 -4
  7. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/client.py +123 -40
  8. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/exceptions.py +17 -0
  9. devops_bot_sdk-1.6.32/sdk/proof_readers.py +420 -0
  10. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/self_report.py +314 -13
  11. devops_bot_sdk-1.6.32/sdk/self_report_local.py +533 -0
  12. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/api.py +42 -3
  13. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/app.js +11 -3
  14. devops_bot_sdk-1.6.32/sdk/url_access.py +647 -0
  15. devops_bot_sdk-1.6.32/tests/test_browser_profile.py +223 -0
  16. devops_bot_sdk-1.6.32/tests/test_proof_readers.py +263 -0
  17. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_self_report.py +552 -2
  18. devops_bot_sdk-1.6.32/tests/test_url_access.py +480 -0
  19. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/README.md +0 -0
  20. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  21. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  22. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/requires.txt +0 -0
  23. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  24. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/pyproject.toml +0 -0
  25. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agentd.py +0 -0
  26. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/__init__.py +0 -0
  27. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/activity.py +0 -0
  28. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/builder.py +0 -0
  29. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/chat.py +0 -0
  30. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/connectors.py +0 -0
  31. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/engines/__init__.py +0 -0
  32. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/engines/claude_code.py +0 -0
  33. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/engines/http.py +0 -0
  34. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/engines/mcp.py +0 -0
  35. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/engines/n8n.py +0 -0
  36. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/engines/shell.py +0 -0
  37. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/integrations.py +0 -0
  38. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/inventory.py +0 -0
  39. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/library/__init__.py +0 -0
  40. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/manifest.py +0 -0
  41. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/prompt_studio.py +0 -0
  42. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/prompt_template.py +0 -0
  43. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/prompt_vars.py +0 -0
  44. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/run_log.py +0 -0
  45. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/schedules.py +0 -0
  46. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/secrets.py +0 -0
  47. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/ticket_run.py +0 -0
  48. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/usage.py +0 -0
  49. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/agents/varstore.py +0 -0
  50. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/collectors/__init__.py +0 -0
  51. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/collectors/files.py +0 -0
  52. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/collectors/process.py +0 -0
  53. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/collectors/screenshot.py +0 -0
  54. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/config.py +0 -0
  55. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/crucial.py +0 -0
  56. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/departments.py +0 -0
  57. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/design_verify/__init__.py +0 -0
  58. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/design_verify/assets.py +0 -0
  59. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/design_verify/bootstrap.py +0 -0
  60. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/design_verify/browser.py +0 -0
  61. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/design_verify/compare.py +0 -0
  62. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/design_verify/loop.py +0 -0
  63. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/formats.py +0 -0
  64. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/git_ops.py +0 -0
  65. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/github_ingest.py +0 -0
  66. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/graph_recon.py +0 -0
  67. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/graphify.py +0 -0
  68. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/hooks/__init__.py +0 -0
  69. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/hooks/crucial_guard.py +0 -0
  70. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/intake.py +0 -0
  71. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ipc/__init__.py +0 -0
  72. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ipc/electron_bridge.py +0 -0
  73. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ipc/handlers.py +0 -0
  74. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/local_exec.py +0 -0
  75. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/models/__init__.py +0 -0
  76. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/models/envelope.py +0 -0
  77. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/models/requests.py +0 -0
  78. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/models/responses.py +0 -0
  79. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/models/snapshots.py +0 -0
  80. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/phase_timer.py +0 -0
  81. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/plan_run.py +0 -0
  82. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/platform_compat.py +0 -0
  83. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/py.typed +0 -0
  84. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/run_auto.py +0 -0
  85. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/scheduler.py +0 -0
  86. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/sse.py +0 -0
  87. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/task_file.py +0 -0
  88. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/test.py +0 -0
  89. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/test_pipeline.py +0 -0
  90. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ticket_fields.py +0 -0
  91. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/__init__.py +0 -0
  92. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/dashboard.py +0 -0
  93. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/health.py +0 -0
  94. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/ip_allowlist.py +0 -0
  95. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/jobs.py +0 -0
  96. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/models.py +0 -0
  97. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/runner.py +0 -0
  98. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/security.py +0 -0
  99. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/server.py +0 -0
  100. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/service.py +0 -0
  101. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/session.py +0 -0
  102. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/setup_manager.py +0 -0
  103. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/agents.html +0 -0
  104. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/chat.html +0 -0
  105. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/custom-agents.html +0 -0
  106. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/default-agents.html +0 -0
  107. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/index.html +0 -0
  108. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/login.html +0 -0
  109. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/report.html +0 -0
  110. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/runs.html +0 -0
  111. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/schedules.html +0 -0
  112. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/setup.html +0 -0
  113. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/styles.css +0 -0
  114. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/ui/static/tickets.html +0 -0
  115. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/updater.py +0 -0
  116. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/sdk/vps_health.py +0 -0
  117. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/setup.cfg +0 -0
  118. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_activity.py +0 -0
  119. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_activity_tracking.py +0 -0
  120. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_chat_features.py +0 -0
  121. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_listing.py +0 -0
  122. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_prompt_async.py +0 -0
  123. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_prompt_models.py +0 -0
  124. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_run_ticket.py +0 -0
  125. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_schedules.py +0 -0
  126. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agent_workspace_settings.py +0 -0
  127. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_agents_connectors_builder.py +0 -0
  128. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_allowed_tools_config.py +0 -0
  129. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_auto_agent_dispatch.py +0 -0
  130. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_client_jira_projects.py +0 -0
  131. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_config_token_diagnostics.py +0 -0
  132. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_configure_token_owner.py +0 -0
  133. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_console_encoding.py +0 -0
  134. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_departments.py +0 -0
  135. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_epic_decompose_run.py +0 -0
  136. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_epic_inherited_agent_id.py +0 -0
  137. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_epic_session_isolation.py +0 -0
  138. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_formats.py +0 -0
  139. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_git_action.py +0 -0
  140. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_github_ingest.py +0 -0
  141. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_graph_recon.py +0 -0
  142. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_graphify_build.py +0 -0
  143. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_graphify_hook_install.py +0 -0
  144. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_health.py +0 -0
  145. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_intake_plan_review.py +0 -0
  146. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_otp_login.py +0 -0
  147. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_phase_timer.py +0 -0
  148. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_plan_new_command.py +0 -0
  149. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_plan_run_activity.py +0 -0
  150. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_recon_session_isolation.py +0 -0
  151. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_run_auto_agent_only.py +0 -0
  152. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_run_outputs.py +0 -0
  153. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_schedule_api.py +0 -0
  154. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_scheduler.py +0 -0
  155. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_service_token_warning.py +0 -0
  156. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_task_file_and_plan_run.py +0 -0
  157. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_task_file_format_doc.py +0 -0
  158. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_ticket_fields.py +0 -0
  159. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_ticket_run_resolve_by_id.py +0 -0
  160. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_ticket_speed_fixes.py +0 -0
  161. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_update_timer.py +0 -0
  162. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/tests/test_updater_extras.py +0 -0
  163. {devops_bot_sdk-1.6.26 → devops_bot_sdk-1.6.32}/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.32
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.32
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
@@ -8,6 +8,7 @@ devops_bot_sdk.egg-info/requires.txt
8
8
  devops_bot_sdk.egg-info/top_level.txt
9
9
  sdk/__init__.py
10
10
  sdk/agentd.py
11
+ sdk/browser_profile.py
11
12
  sdk/cli.py
12
13
  sdk/client.py
13
14
  sdk/config.py
@@ -24,16 +25,19 @@ sdk/local_exec.py
24
25
  sdk/phase_timer.py
25
26
  sdk/plan_run.py
26
27
  sdk/platform_compat.py
28
+ sdk/proof_readers.py
27
29
  sdk/py.typed
28
30
  sdk/run_auto.py
29
31
  sdk/scheduler.py
30
32
  sdk/self_report.py
33
+ sdk/self_report_local.py
31
34
  sdk/sse.py
32
35
  sdk/task_file.py
33
36
  sdk/test.py
34
37
  sdk/test_pipeline.py
35
38
  sdk/ticket_fields.py
36
39
  sdk/updater.py
40
+ sdk/url_access.py
37
41
  sdk/vps_health.py
38
42
  sdk/agents/__init__.py
39
43
  sdk/agents/activity.py
@@ -117,6 +121,7 @@ tests/test_agent_workspace_settings.py
117
121
  tests/test_agents_connectors_builder.py
118
122
  tests/test_allowed_tools_config.py
119
123
  tests/test_auto_agent_dispatch.py
124
+ tests/test_browser_profile.py
120
125
  tests/test_client_jira_projects.py
121
126
  tests/test_config_token_diagnostics.py
122
127
  tests/test_configure_token_owner.py
@@ -137,6 +142,7 @@ tests/test_otp_login.py
137
142
  tests/test_phase_timer.py
138
143
  tests/test_plan_new_command.py
139
144
  tests/test_plan_run_activity.py
145
+ tests/test_proof_readers.py
140
146
  tests/test_recon_session_isolation.py
141
147
  tests/test_run_auto_agent_only.py
142
148
  tests/test_run_outputs.py
@@ -151,4 +157,5 @@ tests/test_ticket_run_resolve_by_id.py
151
157
  tests/test_ticket_speed_fixes.py
152
158
  tests/test_update_timer.py
153
159
  tests/test_updater_extras.py
160
+ tests/test_url_access.py
154
161
  tests/test_vps_health.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.32
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.32" # 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
@@ -0,0 +1,362 @@
1
+ """A browser profile the SDK owns, and the operator signs in to once.
2
+
3
+ The generic answer to "read the link I am logged in to" is a browser, and the
4
+ browser has to be one with sessions in it. Neither obvious candidate is:
5
+
6
+ * **Chrome for Testing**, which ``design_verify/bootstrap`` installs for the
7
+ design loop, is a fresh binary with a blank profile. It is signed in to
8
+ nothing, so driving it answers every private link with a login page.
9
+ * **The operator's own Chrome profile** cannot be driven either. Chrome 136 and
10
+ later refuse ``--remote-debugging-port`` while using the default user-data-dir
11
+ — deliberately, because allowing it is how session cookies get stolen — and a
12
+ second Chrome cannot open a profile the running one holds. Copying the profile
13
+ would work and is precisely the cookie-theft shape that decision exists to
14
+ prevent, so it is not done here either.
15
+
16
+ So this module keeps a THIRD profile: ``~/.agentos/browser-profile``, belonging
17
+ to the SDK. ``agentos browser login <url>`` opens a normal, visible window on it;
18
+ the person signs in the way they would anywhere — password, SSO, MFA, whatever
19
+ the site asks — and closes it. The persistent cookies that login writes stay in
20
+ that directory, and every later read runs headless against the same directory and
21
+ is therefore signed in too.
22
+
23
+ Two properties make this the right trade:
24
+
25
+ * **Nothing decrypts anything.** The SDK never reads the person's own cookie
26
+ store. It has its own profile, holding only sessions they deliberately signed
27
+ into for this purpose, in a directory they can delete in one command.
28
+ * **No new dependency and no CDP.** ``chrome --headless=new --dump-dom <url>``
29
+ prints the rendered DOM to stdout. That is the entire mechanism: no WebSocket,
30
+ no Playwright, no driver to keep in step with the browser version.
31
+
32
+ It is genuinely host-agnostic. Nothing here knows what GitHub or Outlook is — a
33
+ site is readable if the person signed in to it, and the list of such sites is
34
+ whatever they chose.
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import asyncio
39
+ import json
40
+ import logging
41
+ import os
42
+ import platform
43
+ import shutil
44
+ import subprocess
45
+ import sys
46
+ from contextlib import suppress
47
+ from dataclasses import dataclass
48
+ from functools import lru_cache
49
+ from datetime import datetime, timezone
50
+ from pathlib import Path
51
+ from urllib.parse import urlparse
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+ PROFILE_ENV = "AGENTOS_BROWSER_PROFILE"
56
+ BINARY_ENV = "AGENTOS_BROWSER_BINARY"
57
+ _SANDBOX_ENV = "AGENTOS_BROWSER_NO_SANDBOX"
58
+
59
+ # Chromium family only: ``--dump-dom`` is a Chrome flag and has no Firefox
60
+ # equivalent, and the whole point is one code path rather than a driver per
61
+ # engine. Order is preference — the operator's everyday Chrome first, since a
62
+ # site that works there is the one they will expect to work here.
63
+ _CANDIDATES = (
64
+ "google-chrome", "google-chrome-stable", "chromium", "chromium-browser",
65
+ "microsoft-edge", "microsoft-edge-stable", "brave-browser",
66
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
67
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
68
+ )
69
+
70
+ # A page is evidence, not an application: give it time to render and settle, then
71
+ # take what is there. `--virtual-time-budget` fast-forwards timers so this costs
72
+ # wall-clock only for the network, not for a site's animations.
73
+ _VIRTUAL_TIME_MS = 12_000
74
+ _FETCH_TIMEOUT_S = 60.0
75
+ _REAP_TIMEOUT_S = 3.0
76
+ _VERSION_TIMEOUT_S = 10.0
77
+
78
+
79
+ def profile_dir() -> Path:
80
+ return Path(os.environ.get(PROFILE_ENV, "") or
81
+ (Path.home() / ".agentos" / "browser-profile")).expanduser()
82
+
83
+
84
+ def sites_file() -> Path:
85
+ """Hosts the operator signed in to, RECORDED BY US as they do it.
86
+
87
+ Deliberately not derived from the profile's cookie database. `browser status`
88
+ needs to answer "what is this signed in to", and reading cookies to find out
89
+ — even our own — would put cookie-parsing code in the SDK, which is the thing
90
+ this design exists to avoid. A note we wrote ourselves is enough.
91
+ """
92
+ return profile_dir().parent / "browser-sites.json"
93
+
94
+
95
+ @dataclass
96
+ class Fetched:
97
+ """One headless read. ``html`` empty means it did not happen."""
98
+ html: str = ""
99
+ reason: str = ""
100
+
101
+ @property
102
+ def ok(self) -> bool:
103
+ return bool(self.html)
104
+
105
+
106
+ def find_browser() -> str:
107
+ """Path to a Chromium-family browser, or "". Honours ``AGENTOS_BROWSER_BINARY``."""
108
+ override = os.environ.get(BINARY_ENV, "").strip()
109
+ if override:
110
+ return override if (shutil.which(override) or Path(override).exists()) else ""
111
+ for name in _CANDIDATES:
112
+ found = shutil.which(name)
113
+ if found:
114
+ return found
115
+ if name.startswith("/") and Path(name).exists():
116
+ return name
117
+ return ""
118
+
119
+
120
+ def is_available() -> bool:
121
+ return bool(find_browser())
122
+
123
+
124
+ @lru_cache(maxsize=8)
125
+ def _version_of(binary: str) -> str:
126
+ """``149.0.7827.200`` from ``chrome --version``, or "".
127
+
128
+ Cached per binary: a browser does not change version inside one process, and
129
+ this is a subprocess on the path of every read. Keyed on the path rather than
130
+ stored in a flag so an override pointing somewhere else is not answered with
131
+ the previous browser's version.
132
+ """
133
+ try:
134
+ out = subprocess.run([binary, "--version"], capture_output=True, text=True,
135
+ timeout=_VERSION_TIMEOUT_S).stdout
136
+ except (OSError, subprocess.SubprocessError):
137
+ return ""
138
+ for word in out.split():
139
+ if word[:1].isdigit() and word.count(".") >= 2:
140
+ return word.strip()
141
+ return ""
142
+
143
+
144
+ def _version() -> str:
145
+ binary = find_browser()
146
+ return _version_of(binary) if binary else ""
147
+
148
+
149
+ def user_agent() -> str:
150
+ """A non-headless UA for the browser we actually have.
151
+
152
+ New headless still announces ``HeadlessChrome``, and enough sites refuse it
153
+ that a page would come back as a block notice rather than the evidence. Built
154
+ from the installed version rather than hard-coded so it does not quietly go
155
+ stale into exactly the mismatch that gets a request flagged.
156
+ """
157
+ version = _version() or "140.0.0.0"
158
+ system = sys.platform
159
+ if system == "darwin":
160
+ os_part = "Macintosh; Intel Mac OS X 10_15_7"
161
+ elif system.startswith("win"):
162
+ os_part = "Windows NT 10.0; Win64; x64"
163
+ else:
164
+ os_part = f"X11; Linux {platform.machine() or 'x86_64'}"
165
+ return (f"Mozilla/5.0 ({os_part}) AppleWebKit/537.36 (KHTML, like Gecko) "
166
+ f"Chrome/{version} Safari/537.36")
167
+
168
+
169
+ def _base_args() -> list[str]:
170
+ args = [
171
+ f"--user-data-dir={profile_dir()}",
172
+ "--no-first-run",
173
+ "--no-default-browser-check",
174
+ "--disable-background-networking",
175
+ "--disable-sync", # this profile is not anyone's identity
176
+ "--mute-audio",
177
+ ]
178
+ if os.environ.get(_SANDBOX_ENV, "").strip() in ("1", "true", "True"):
179
+ # Some containers cannot run the sandbox at all. Off by default: the
180
+ # sandbox is the thing standing between a hostile page and this machine.
181
+ args.append("--no-sandbox")
182
+ return args
183
+
184
+
185
+ def busy_pid() -> int | None:
186
+ """The pid holding this profile, or None.
187
+
188
+ Chrome keeps one instance per user-data-dir; a second launch hands its
189
+ arguments to the first and exits, which for a headless read means an empty
190
+ answer with no error. Checking first turns that into a sentence the operator
191
+ can act on. A stale lock from a crash names a pid that is gone, and is
192
+ correctly reported as not busy.
193
+ """
194
+ lock = profile_dir() / "SingletonLock"
195
+ try:
196
+ target = os.readlink(lock)
197
+ except (OSError, ValueError):
198
+ return None
199
+ # "<hostname>-<pid>"
200
+ _, _, raw = target.rpartition("-")
201
+ try:
202
+ pid = int(raw)
203
+ except ValueError:
204
+ return None
205
+ try:
206
+ os.kill(pid, 0)
207
+ except ProcessLookupError:
208
+ return None
209
+ except PermissionError:
210
+ return pid # alive, owned by someone else
211
+ except OSError:
212
+ return None
213
+ return pid
214
+
215
+
216
+ async def fetch_dom(url: str, *, timeout: float = _FETCH_TIMEOUT_S) -> Fetched:
217
+ """Render ``url`` headless in the signed-in profile and return its DOM.
218
+
219
+ ``--dump-dom`` prints the page AFTER scripts have run, which is what makes
220
+ this work on the single-page apps most internal tools are: the HTML a plain
221
+ GET returns is an empty shell, and the content only exists once the app has
222
+ fetched it with the session this profile holds.
223
+ """
224
+ binary = find_browser()
225
+ if not binary:
226
+ return Fetched(reason=(
227
+ "no Chrome, Chromium, Edge or Brave was found on this machine, so a "
228
+ "link behind a login cannot be opened here. Install one, or set "
229
+ f"{BINARY_ENV} to its path."))
230
+ if not profile_dir().exists():
231
+ return Fetched(reason=(
232
+ "the SDK's browser profile does not exist yet — nothing has been "
233
+ "signed in to. Run `agentos browser login <url>`, sign in, close the "
234
+ "window, and this link becomes readable."))
235
+ pid = busy_pid()
236
+ if pid is not None:
237
+ return Fetched(reason=(
238
+ f"the SDK's browser profile is open in another window (pid {pid}). "
239
+ "Close the window `agentos browser login` opened and try again."))
240
+
241
+ # `user_agent` shells out for the version. Cached, but the first call per
242
+ # binary is a subprocess — off the loop, or one read stalls every other
243
+ # coroutine in the process for as long as the browser takes to answer.
244
+ agent = await asyncio.to_thread(user_agent)
245
+ cmd = [binary, "--headless=new", "--disable-gpu", *_base_args(),
246
+ f"--user-agent={agent}",
247
+ f"--virtual-time-budget={_VIRTUAL_TIME_MS}",
248
+ "--dump-dom", url]
249
+ try:
250
+ proc = await asyncio.create_subprocess_exec(
251
+ *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
252
+ except OSError as exc:
253
+ return Fetched(reason=f"could not start {Path(binary).name} — {exc}")
254
+ try:
255
+ out, err = await asyncio.wait_for(proc.communicate(), timeout=timeout)
256
+ except asyncio.TimeoutError:
257
+ proc.kill()
258
+ # Reap it. Without the wait the transport is still open when the loop
259
+ # closes, which surfaces as a "Event loop is closed" warning from
260
+ # somewhere entirely unrelated to the browser.
261
+ with suppress(Exception):
262
+ # BOUNDED. A killed process whose orphaned child still holds the
263
+ # pipes never reports exit, and an unbounded wait here would hang the
264
+ # very call the timeout above exists to bound.
265
+ await asyncio.wait_for(proc.wait(), timeout=_REAP_TIMEOUT_S)
266
+ return Fetched(reason=f"timed out rendering in the browser after {int(timeout)}s.")
267
+
268
+ html = out.decode(errors="replace").strip()
269
+ if not html:
270
+ detail = err.decode(errors="replace").strip().splitlines()
271
+ tail = detail[-1][:200] if detail else f"exit {proc.returncode}"
272
+ return Fetched(reason=f"was opened in the browser but rendered nothing — {tail}")
273
+ return Fetched(html=html)
274
+
275
+
276
+ # ── Signing in ────────────────────────────────────────────────────────────────
277
+
278
+ def _load_sites() -> dict:
279
+ try:
280
+ data = json.loads(sites_file().read_text(encoding="utf-8"))
281
+ except Exception: # noqa: BLE001 — absent until the first login
282
+ return {}
283
+ return data if isinstance(data, dict) else {}
284
+
285
+
286
+ def record_login(url: str) -> str:
287
+ """Note that a login window was opened for this URL's host. Returns the host."""
288
+ host = (urlparse(url).hostname or "").lower()
289
+ if not host:
290
+ return ""
291
+ sites = _load_sites()
292
+ sites[host] = datetime.now(timezone.utc).isoformat(timespec="seconds")
293
+ try:
294
+ sites_file().parent.mkdir(parents=True, exist_ok=True)
295
+ sites_file().write_text(json.dumps(sites, indent=2, sort_keys=True), encoding="utf-8")
296
+ except OSError as exc:
297
+ logger.warning("browser_profile.sites_write_failed: %s", exc)
298
+ return host
299
+
300
+
301
+ def logged_in_hosts() -> dict[str, str]:
302
+ """``{host: when the login window was opened}`` — what we were told, not proof.
303
+
304
+ A session can expire, and a person can open the window and never sign in. So
305
+ this is a record of intent; `agentos report check <url>` is what actually
306
+ answers whether a host is readable right now.
307
+ """
308
+ return dict(sorted(_load_sites().items()))
309
+
310
+
311
+ def open_login(url: str) -> tuple[bool, str]:
312
+ """Open a VISIBLE window on the SDK's profile at ``url``. ``(ok, message)``.
313
+
314
+ Returns as soon as the browser is launched — the point is for a person to
315
+ take it from there, and waiting for them to finish typing a password is not
316
+ this function's business.
317
+ """
318
+ binary = find_browser()
319
+ if not binary:
320
+ return False, ("no Chrome, Chromium, Edge or Brave found. Install one, or "
321
+ f"set {BINARY_ENV} to its path.")
322
+ pid = busy_pid()
323
+ if pid is not None:
324
+ return False, (f"a window on this profile is already open (pid {pid}). Use it, "
325
+ "or close it and run this again.")
326
+ try:
327
+ profile_dir().mkdir(parents=True, exist_ok=True)
328
+ except OSError as exc:
329
+ return False, f"cannot create {profile_dir()} — {exc}"
330
+
331
+ cmd = [binary, *_base_args(), "--new-window", url]
332
+ try:
333
+ subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
334
+ start_new_session=True)
335
+ except OSError as exc:
336
+ return False, f"could not start {Path(binary).name} — {exc}"
337
+ host = record_login(url)
338
+ return True, host
339
+
340
+
341
+ def forget() -> tuple[bool, str]:
342
+ """Delete the profile — every session in it. ``(ok, message)``."""
343
+ target = profile_dir()
344
+ if not target.exists():
345
+ return True, f"nothing to remove: {target} does not exist."
346
+ pid = busy_pid()
347
+ if pid is not None:
348
+ return False, (f"a window on this profile is open (pid {pid}). Close it first, "
349
+ "or the deletion leaves a half-written profile behind.")
350
+ try:
351
+ shutil.rmtree(target)
352
+ sites_file().unlink(missing_ok=True)
353
+ except OSError as exc:
354
+ return False, f"could not remove {target} — {exc}"
355
+ return True, f"removed {target} and every session in it."
356
+
357
+
358
+ __all__ = [
359
+ "BINARY_ENV", "Fetched", "PROFILE_ENV", "busy_pid", "fetch_dom", "find_browser",
360
+ "forget", "is_available", "logged_in_hosts", "open_login", "profile_dir",
361
+ "record_login", "sites_file", "user_agent",
362
+ ]