devops-bot-sdk 1.6.46__tar.gz → 1.6.47__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 (222) hide show
  1. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/__init__.py +2 -2
  4. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/collectors/__init__.py +38 -2
  5. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/collectors/linux_auditd.py +11 -1
  6. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/os_setup.py +193 -7
  7. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_activity_collectors.py +91 -0
  8. devops_bot_sdk-1.6.47/tests/test_activity_os_setup.py +283 -0
  9. devops_bot_sdk-1.6.46/tests/test_activity_os_setup.py +0 -151
  10. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/README.md +0 -0
  11. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  12. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  13. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  14. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/devops_bot_sdk.egg-info/requires.txt +0 -0
  15. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  16. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/pyproject.toml +0 -0
  17. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/__init__.py +0 -0
  18. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/classifier.py +0 -0
  19. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/collectors/common.py +0 -0
  20. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/collectors/macos_eslogger.py +0 -0
  21. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/collectors/windows_eventlog.py +0 -0
  22. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/models.py +0 -0
  23. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/recorder.py +0 -0
  24. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/redact.py +0 -0
  25. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/settings.py +0 -0
  26. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/shipper.py +0 -0
  27. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/activity/spool.py +0 -0
  28. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agentd.py +0 -0
  29. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/__init__.py +0 -0
  30. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/activity.py +0 -0
  31. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/builder.py +0 -0
  32. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/chat.py +0 -0
  33. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/connectors.py +0 -0
  34. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/engines/__init__.py +0 -0
  35. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/engines/claude_code.py +0 -0
  36. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/engines/http.py +0 -0
  37. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/engines/mcp.py +0 -0
  38. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/engines/n8n.py +0 -0
  39. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/engines/shell.py +0 -0
  40. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/integrations.py +0 -0
  41. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/inventory.py +0 -0
  42. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/library/__init__.py +0 -0
  43. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/manifest.py +0 -0
  44. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/prompt_studio.py +0 -0
  45. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/prompt_template.py +0 -0
  46. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/prompt_vars.py +0 -0
  47. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/run_log.py +0 -0
  48. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/schedules.py +0 -0
  49. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/secrets.py +0 -0
  50. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/ticket_run.py +0 -0
  51. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/usage.py +0 -0
  52. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/agents/varstore.py +0 -0
  53. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/browser_profile.py +0 -0
  54. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/cli.py +0 -0
  55. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/client.py +0 -0
  56. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/collectors/__init__.py +0 -0
  57. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/collectors/files.py +0 -0
  58. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/collectors/process.py +0 -0
  59. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/collectors/screenshot.py +0 -0
  60. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/config.py +0 -0
  61. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/crucial.py +0 -0
  62. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/departments.py +0 -0
  63. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/design_verify/__init__.py +0 -0
  64. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/design_verify/assets.py +0 -0
  65. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/design_verify/bootstrap.py +0 -0
  66. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/design_verify/browser.py +0 -0
  67. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/design_verify/compare.py +0 -0
  68. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/design_verify/loop.py +0 -0
  69. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/estimator.py +0 -0
  70. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/exceptions.py +0 -0
  71. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/firewall.py +0 -0
  72. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/formats.py +0 -0
  73. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/git_ops.py +0 -0
  74. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/github_fetch.py +0 -0
  75. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/github_ingest.py +0 -0
  76. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/graph_recon.py +0 -0
  77. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/graphify.py +0 -0
  78. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/hooks/__init__.py +0 -0
  79. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/hooks/crucial_guard.py +0 -0
  80. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/intake.py +0 -0
  81. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ipc/__init__.py +0 -0
  82. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ipc/electron_bridge.py +0 -0
  83. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ipc/handlers.py +0 -0
  84. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/journal.py +0 -0
  85. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/local_exec.py +0 -0
  86. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/memory_guard.py +0 -0
  87. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/models/__init__.py +0 -0
  88. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/models/envelope.py +0 -0
  89. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/models/requests.py +0 -0
  90. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/models/responses.py +0 -0
  91. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/models/snapshots.py +0 -0
  92. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/phase_timer.py +0 -0
  93. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/plan_run.py +0 -0
  94. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/platform_compat.py +0 -0
  95. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/prd_progress.py +0 -0
  96. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/proof_readers.py +0 -0
  97. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/providers.py +0 -0
  98. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/py.typed +0 -0
  99. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/resume.py +0 -0
  100. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/router.py +0 -0
  101. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/run_auto.py +0 -0
  102. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/scheduler.py +0 -0
  103. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/self_report.py +0 -0
  104. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/self_report_local.py +0 -0
  105. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/sse.py +0 -0
  106. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/swagger.py +0 -0
  107. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/task_file.py +0 -0
  108. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/task_types.py +0 -0
  109. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/test.py +0 -0
  110. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/test_pipeline.py +0 -0
  111. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ticket_fields.py +0 -0
  112. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/tiers/__init__.py +0 -0
  113. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/tiers/mini_agent.py +0 -0
  114. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/__init__.py +0 -0
  115. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/api.py +0 -0
  116. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/dashboard.py +0 -0
  117. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/health.py +0 -0
  118. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/ip_allowlist.py +0 -0
  119. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/jobs.py +0 -0
  120. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/models.py +0 -0
  121. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/runner.py +0 -0
  122. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/security.py +0 -0
  123. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/server.py +0 -0
  124. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/service.py +0 -0
  125. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/session.py +0 -0
  126. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/setup_manager.py +0 -0
  127. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/agents.html +0 -0
  128. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/app.js +0 -0
  129. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/chat.html +0 -0
  130. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/custom-agents.html +0 -0
  131. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/default-agents.html +0 -0
  132. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/index.html +0 -0
  133. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/report.html +0 -0
  134. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/runs.html +0 -0
  135. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/schedules.html +0 -0
  136. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/setup.html +0 -0
  137. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/styles.css +0 -0
  138. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/static/tickets.html +0 -0
  139. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/ui/swagger.py +0 -0
  140. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/updater.py +0 -0
  141. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/url_access.py +0 -0
  142. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/vps_health.py +0 -0
  143. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/sdk/write_hook.py +0 -0
  144. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/setup.cfg +0 -0
  145. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_activity_core.py +0 -0
  146. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_activity_macos.py +0 -0
  147. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_activity_pipeline.py +0 -0
  148. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_activity.py +0 -0
  149. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_activity_tracking.py +0 -0
  150. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_chat_features.py +0 -0
  151. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_listing.py +0 -0
  152. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_prompt_async.py +0 -0
  153. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_prompt_models.py +0 -0
  154. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_run_ticket.py +0 -0
  155. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_schedules.py +0 -0
  156. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agent_workspace_settings.py +0 -0
  157. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_agents_connectors_builder.py +0 -0
  158. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_allowed_tools_config.py +0 -0
  159. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_auto_agent_dispatch.py +0 -0
  160. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_browser_profile.py +0 -0
  161. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_cheap_tier.py +0 -0
  162. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_client_jira_projects.py +0 -0
  163. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_config_token_diagnostics.py +0 -0
  164. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_configure_token_owner.py +0 -0
  165. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_console_encoding.py +0 -0
  166. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_crash_recovery.py +0 -0
  167. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_departments.py +0 -0
  168. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_done_transition.py +0 -0
  169. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_epic_decompose_run.py +0 -0
  170. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_epic_inherited_agent_id.py +0 -0
  171. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_epic_session_isolation.py +0 -0
  172. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_estimator.py +0 -0
  173. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_firewall_open_on_update.py +0 -0
  174. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_formats.py +0 -0
  175. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_git_action.py +0 -0
  176. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_github_fetch.py +0 -0
  177. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_github_ingest.py +0 -0
  178. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_graph_recon.py +0 -0
  179. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_graphify_build.py +0 -0
  180. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_graphify_hook_install.py +0 -0
  181. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_health.py +0 -0
  182. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_intake_job_outcome.py +0 -0
  183. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_intake_plan_review.py +0 -0
  184. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_journal.py +0 -0
  185. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_local_exec_provider.py +0 -0
  186. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_mcp_route_contract.py +0 -0
  187. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_memory_guard.py +0 -0
  188. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_phase_timer.py +0 -0
  189. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_plan_new_command.py +0 -0
  190. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_plan_run_activity.py +0 -0
  191. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_pr_identity.py +0 -0
  192. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_prd_artifact.py +0 -0
  193. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_project_path_resolution.py +0 -0
  194. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_proof_readers.py +0 -0
  195. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_providers.py +0 -0
  196. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_recon_session_isolation.py +0 -0
  197. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_resume.py +0 -0
  198. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_router.py +0 -0
  199. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_run_auto_agent_only.py +0 -0
  200. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_run_outputs.py +0 -0
  201. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_run_verdict_is_honest.py +0 -0
  202. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_schedule_api.py +0 -0
  203. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_schedule_outputs_and_plans.py +0 -0
  204. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_scheduler.py +0 -0
  205. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_self_report.py +0 -0
  206. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_service_token_warning.py +0 -0
  207. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_swagger_command.py +0 -0
  208. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_task_file_and_plan_run.py +0 -0
  209. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_task_file_format_doc.py +0 -0
  210. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_ticket_fields.py +0 -0
  211. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_ticket_run_resolve_by_id.py +0 -0
  212. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_ticket_speed_fixes.py +0 -0
  213. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_tier_flag_parity.py +0 -0
  214. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_ui_gate.py +0 -0
  215. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_ui_lifespan.py +0 -0
  216. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_update_timer.py +0 -0
  217. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_updater_extras.py +0 -0
  218. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_updater_reports_the_truth.py +0 -0
  219. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_url_access.py +0 -0
  220. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_vps_health.py +0 -0
  221. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_windows_loop_identity.py +0 -0
  222. {devops_bot_sdk-1.6.46 → devops_bot_sdk-1.6.47}/tests/test_write_hook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.46
3
+ Version: 1.6.47
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.46
3
+ Version: 1.6.47
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.46
3
+ Version: 1.6.47
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.46" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.47" # 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
@@ -1,4 +1,4 @@
1
- """OS-level collectors — opt-in (``AGENTOS_ACTIVITY_AUDIT=1``).
1
+ """OS-level collectors (``AGENTOS_ACTIVITY_AUDIT=1``, switched on by ``os_setup``).
2
2
 
3
3
  Linux auditd sdk.activity.collectors.linux_auditd
4
4
  Windows Security log sdk.activity.collectors.windows_eventlog
@@ -70,9 +70,45 @@ async def run_forever(collector: Any) -> None:
70
70
  logger.exception("activity collector crashed")
71
71
 
72
72
 
73
+ async def _setup_then_run() -> None:
74
+ """Turn the OS audit on (``os_setup.ensure``), then run the collector — no restart.
75
+
76
+ The panel is the one process that is always running, so it is where a box whose
77
+ install/update ran unprivileged gets set up: as root/elevated directly, or through
78
+ passwordless ``sudo`` (Linux). In a thread, because a first run may install auditd.
79
+ """
80
+ from sdk.activity import os_setup
81
+
82
+ try:
83
+ _, msg = await asyncio.to_thread(os_setup.ensure)
84
+ except Exception as exc: # noqa: BLE001 — ensure never raises, but be certain
85
+ logger.warning("activity audit setup crashed: %s", exc)
86
+ return
87
+ if msg:
88
+ logger.warning("%s", msg)
89
+ if not settings.audit_enabled():
90
+ return
91
+ try:
92
+ collector = make_collector()
93
+ except Exception as exc: # noqa: BLE001
94
+ logger.warning("activity collector could not be created: %s", exc)
95
+ return
96
+ if collector is not None:
97
+ logger.info("activity collector starting (%s)", type(collector).__name__)
98
+ await run_forever(collector)
99
+
100
+
73
101
  def start() -> asyncio.Task[None] | None:
74
- if not (settings.enabled() and settings.audit_enabled()):
102
+ if not settings.enabled():
75
103
  return None
104
+ if not settings.audit_enabled():
105
+ from sdk.activity import os_setup
106
+
107
+ # An explicit AGENTOS_ACTIVITY_AUDIT=0 / AGENTOS_NO_AUDIT_SETUP=1 is the
108
+ # operator's decision: respected, nothing touched.
109
+ if os_setup._opted_out():
110
+ return None
111
+ return asyncio.ensure_future(_setup_then_run())
76
112
  try:
77
113
  collector = make_collector()
78
114
  except Exception as exc: # noqa: BLE001
@@ -159,6 +159,11 @@ class AuditdParser:
159
159
  inner = re.search(r"msg='([^']*)'", body)
160
160
  if inner:
161
161
  rec.update({f"m_{k}": v for k, v in _kv(inner.group(1)).items()})
162
+ # acct is quoted when printable and hex-encoded otherwise; as with EXECVE
163
+ # args, only the raw match says which ("dead" is a name AND valid hex).
164
+ for key, raw in _KV.findall(inner.group(1)):
165
+ if key == "acct":
166
+ rec["m_acct"] = _unquote(raw) if raw[:1] in "\"'" else _unhex(raw)
162
167
  if rtype == "EXECVE":
163
168
  # Quoting is the ONLY thing that tells a literal argument from a hex-encoded
164
169
  # one ("beef" is valid hex), so read it from the raw match, not from _kv.
@@ -217,9 +222,14 @@ class AuditdParser:
217
222
  terminal = rec.get("m_terminal", "")
218
223
 
219
224
  if not ok:
225
+ # The attempted account name: a failed login has no uid to resolve (it is
226
+ # unset, or an invalid user), so without acct the record says nothing about
227
+ # which names an attacker is trying.
228
+ acct = rec.get("m_acct", "")
229
+ tried = acct if acct and acct not in ("(unknown)", "?") else user
220
230
  recorder.record_event(ActivityEvent(
221
231
  os=Os.LINUX, source=Source.AUDITD, category=Category.LOGIN, action="login.failed",
222
- occurredAt=ts, osUser=user, target=remote, severity=Severity.WARNING,
232
+ occurredAt=ts, osUser=tried, target=remote, severity=Severity.WARNING,
223
233
  actorType=ActorType.UNKNOWN, actorConfidence=0,
224
234
  ))
225
235
  return
@@ -19,6 +19,17 @@ the same contract:
19
19
  log anyway.)
20
20
  * **never raises** — an update must not fail because of the audit trail.
21
21
 
22
+ **A panel that runs as an ordinary user** (a user systemd unit, the common case on
23
+ a VPS set up without ``sudo``) was the gap that left most Linux boxes unaudited even
24
+ when that user had root through ``sudo``. Three things now close it:
25
+
26
+ * unprivileged, :func:`ensure` runs its privileged half through ``sudo -n`` when
27
+ sudo needs no password, so nobody has to type a command;
28
+ * under ``sudo`` the flag goes into the INVOKING user's ``~/.agentos/.env``
29
+ (``SUDO_USER``), not ``/root`` — the panel never reads root's home;
30
+ * ``auditd``'s ``log_group`` is set to that user's private group, because
31
+ ``audit.log`` is root-only and a collector that cannot read it records nothing.
32
+
22
33
  Opt out with ``AGENTOS_NO_AUDIT_SETUP=1``. An operator who set
23
34
  ``AGENTOS_ACTIVITY_AUDIT=0`` (or ``AGENTOS_ACTIVITY=0``) has decided already; that
24
35
  is respected and nothing is touched.
@@ -35,7 +46,9 @@ import logging
35
46
  import os
36
47
  import platform
37
48
  import shutil
49
+ import re
38
50
  import subprocess
51
+ import sys
39
52
  from pathlib import Path
40
53
 
41
54
  from sdk.activity import settings
@@ -52,6 +65,8 @@ RULES_TEXT = (
52
65
  f"-a always,exit -F arch=b64 -S execve -k {RULE_KEY}\n"
53
66
  f"-a always,exit -F arch=b32 -S execve -k {RULE_KEY}\n"
54
67
  )
68
+ AUDITD_CONF = Path("/etc/audit/auditd.conf")
69
+ AUDIT_LOG = Path("/var/log/audit/audit.log")
55
70
  _LINUX_MANUAL = (
56
71
  "sudo apt install auditd # or: dnf install audit\n"
57
72
  f"sudo tee {RULES_PATH} <<'EOF'\n{RULES_TEXT}EOF\n"
@@ -112,6 +127,34 @@ def _is_privileged() -> bool:
112
127
  return hasattr(os, "geteuid") and os.geteuid() == 0
113
128
 
114
129
 
130
+ def _panel_user() -> tuple[str, Path, int, int] | None:
131
+ """``(name, home, uid, gid)`` of the account the panel runs as, if it is NOT root.
132
+
133
+ Under ``sudo`` that is ``SUDO_USER`` — the person who typed the command, whose
134
+ ``~/.agentos`` the panel reads. Otherwise it is the current user. ``None`` when the
135
+ panel itself is root, or on a platform without ``pwd``: root needs no grant.
136
+ """
137
+ try:
138
+ import pwd
139
+ except ImportError: # Windows
140
+ return None
141
+ sudo_user = os.environ.get("SUDO_USER", "").strip()
142
+ try:
143
+ if _is_privileged():
144
+ # Privileged WITHOUT sudo is a root panel (a system unit): nobody to grant,
145
+ # and no other home to write into. Never guess one from the real uid.
146
+ if not sudo_user or sudo_user == "root":
147
+ return None
148
+ pw = pwd.getpwnam(sudo_user)
149
+ else:
150
+ pw = pwd.getpwuid(os.getuid())
151
+ except (KeyError, OSError):
152
+ return None
153
+ if pw.pw_uid == 0:
154
+ return None
155
+ return pw.pw_name, Path(pw.pw_dir), pw.pw_uid, pw.pw_gid
156
+
157
+
115
158
  def _run(cmd: list[str], timeout: float = 60.0, env: dict | None = None) -> tuple[int, str]:
116
159
  """``(returncode, output)``; ``(-1, reason)`` when the command could not run."""
117
160
  try:
@@ -123,7 +166,29 @@ def _run(cmd: list[str], timeout: float = 60.0, env: dict | None = None) -> tupl
123
166
 
124
167
 
125
168
  def _env_file() -> Path:
126
- return Path.home() / ".agentos" / ".env"
169
+ """The ``.env`` the PANEL reads: the sudo-invoking user's, never root's by accident."""
170
+ user = _panel_user() if _is_privileged() else None
171
+ home = user[1] if user else Path.home()
172
+ return home / ".agentos" / ".env"
173
+
174
+
175
+ def _flag_value() -> str:
176
+ """``AGENTOS_ACTIVITY_AUDIT`` as the panel will see it: process env, then its ``.env``.
177
+
178
+ ``settings.setting`` reads ``Path.home()``'s ``.env``, which under ``sudo`` is root's
179
+ — not the file the panel reads — so the panel's file is read here directly.
180
+ """
181
+ val = os.environ.get(AUDIT_FLAG, "").strip()
182
+ if val:
183
+ return val
184
+ try:
185
+ for line in _env_file().read_text(encoding="utf-8").splitlines():
186
+ key, sep, value = line.strip().partition("=")
187
+ if sep and key.strip() == AUDIT_FLAG:
188
+ return value.strip().strip('"').strip("'")
189
+ except OSError:
190
+ pass
191
+ return settings.setting(AUDIT_FLAG)
127
192
 
128
193
 
129
194
  def _opted_out() -> bool:
@@ -131,7 +196,7 @@ def _opted_out() -> bool:
131
196
  return True
132
197
  falsey = {"0", "false", "no", "off"}
133
198
  return (settings.setting("AGENTOS_ACTIVITY").lower() in falsey
134
- or settings.setting(AUDIT_FLAG).lower() in falsey)
199
+ or _flag_value().lower() in falsey)
135
200
 
136
201
 
137
202
  def _enable_flag() -> bool:
@@ -140,14 +205,23 @@ def _enable_flag() -> bool:
140
205
  Left alone when the flag is set ANYWHERE (process env or .env) — an explicit
141
206
  value is the operator's, including an explicit "on".
142
207
  """
143
- if settings.setting(AUDIT_FLAG):
208
+ if _flag_value():
144
209
  return False
145
210
  path = _env_file()
146
211
  try:
212
+ created_dir = not path.parent.exists()
147
213
  path.parent.mkdir(parents=True, exist_ok=True)
148
214
  existing = path.read_text(encoding="utf-8") if path.exists() else ""
149
215
  sep = "" if (not existing or existing.endswith("\n")) else "\n"
150
216
  path.write_text(f"{existing}{sep}{AUDIT_FLAG}=1\n", encoding="utf-8")
217
+ # Written as root into a user's home: hand it back, or the panel (that user)
218
+ # could no longer edit its own .env — and a new ~/.agentos would be root's.
219
+ user = _panel_user() if _is_privileged() else None
220
+ if user:
221
+ _, _, uid, gid = user
222
+ if created_dir:
223
+ os.chown(path.parent, uid, gid)
224
+ os.chown(path, uid, gid)
151
225
  return True
152
226
  except OSError as exc:
153
227
  logger.warning("activity.os_setup.env_write_failed path=%s err=%s", path, exc)
@@ -215,9 +289,76 @@ def _ensure_linux() -> tuple[bool, list[str], str]:
215
289
  # Typically a container without audit support in its kernel namespace.
216
290
  return bool(done), done, f"audit rules did not load: {out.strip()[-300:]}"
217
291
  done.append("loaded audit rules")
292
+
293
+ user = _panel_user()
294
+ if user:
295
+ steps, err = _grant_log_read(user)
296
+ done.extend(steps)
297
+ if err:
298
+ return bool(done), done, err
218
299
  return bool(done), done, ""
219
300
 
220
301
 
302
+ def _grant_log_read(user: tuple[str, Path, int, int]) -> tuple[list[str], str]:
303
+ """Let the panel's (non-root) user read ``audit.log``. ``(steps_done, error)``.
304
+
305
+ ``log_group`` in ``auditd.conf`` is what survives rotation (auditd applies it to
306
+ every new log file and the directory); the chgrp/chmod makes the CURRENT file
307
+ readable now, without waiting for a rotation. Only a USER-PRIVATE group (named
308
+ like the user) is used: granting a shared group such as ``users`` would hand the
309
+ log of every command on the box to every member of it.
310
+ """
311
+ import grp
312
+
313
+ name, _, _, gid = user
314
+ try:
315
+ group = grp.getgrgid(gid).gr_name
316
+ except KeyError:
317
+ group = ""
318
+ if group != name:
319
+ return [], (f"{name}'s primary group is {group or gid}, not a private group — "
320
+ f"set log_group in {AUDITD_CONF} to a group only {name} is in")
321
+ done: list[str] = []
322
+ try:
323
+ text = AUDITD_CONF.read_text(encoding="utf-8") if AUDITD_CONF.exists() else ""
324
+ lines = text.splitlines(keepends=True)
325
+ found = changed = False
326
+ for i, line in enumerate(lines):
327
+ if re.match(r"\s*log_group\s*=", line):
328
+ found = True
329
+ if line.split("=", 1)[1].strip() != group:
330
+ lines[i] = f"log_group = {group}\n"
331
+ changed = True
332
+ if not found:
333
+ sep = "" if (not lines or lines[-1].endswith("\n")) else "\n"
334
+ lines.append(f"{sep}log_group = {group}\n")
335
+ changed = True
336
+ if changed:
337
+ AUDITD_CONF.write_text("".join(lines), encoding="utf-8")
338
+ # auditd re-reads its config on SIGHUP; `auditctl --signal` is audit ≥ 3.
339
+ if _run(["auditctl", "--signal", "reload"])[0] != 0:
340
+ _run(["pkill", "-HUP", "-x", "auditd"])
341
+ done.append(f"log_group = {group}")
342
+ for path, bits in ((AUDIT_LOG.parent, 0o050), (AUDIT_LOG, 0o040)):
343
+ if not path.exists():
344
+ continue
345
+ st = path.stat()
346
+ if st.st_gid != gid:
347
+ os.chown(path, -1, gid)
348
+ if st.st_mode & bits != bits:
349
+ os.chmod(path, (st.st_mode & 0o7777) | bits)
350
+ if st.st_gid != gid or st.st_mode & bits != bits:
351
+ if f"{AUDIT_LOG} readable by {name}" not in done:
352
+ done.append(f"{AUDIT_LOG} readable by {name}")
353
+ except OSError as exc:
354
+ return done, f"could not make {AUDIT_LOG} readable by {name}: {exc}"
355
+ return done, ""
356
+
357
+
358
+ def _log_readable() -> bool:
359
+ return os.access(AUDIT_LOG, os.R_OK)
360
+
361
+
221
362
  # ── Windows steps ───────────────────────────────────────────────────────────
222
363
 
223
364
  def _ensure_windows() -> tuple[bool, list[str], str]:
@@ -389,10 +530,14 @@ def ensure(apply: bool = True) -> tuple[bool, str]:
389
530
  manual = {"linux": _LINUX_MANUAL, "windows": _WIN_MANUAL, "darwin": _MAC_MANUAL}[sysname]
390
531
 
391
532
  if not apply or not _is_privileged():
392
- if settings.audit_enabled():
393
- return False, "" # someone already set it up by hand
533
+ ready = settings.audit_enabled() and (sysname != "linux" or _log_readable())
534
+ if ready:
535
+ return False, "" # set up already (by hand, or by an earlier run)
536
+ if apply and sysname == "linux" and _sudo_without_password():
537
+ return _ensure_via_sudo()
394
538
  return False, ("activity audit is not set up — human activity on this VPS is "
395
- "not being recorded. Run as root/Administrator:\n" + manual)
539
+ f"not being recorded. Run once:\n sudo {_agentos_exe()} "
540
+ "activity-setup\nor by hand:\n" + manual)
396
541
 
397
542
  step = {"linux": _ensure_linux, "windows": _ensure_windows, "darwin": _ensure_macos}
398
543
  _, done, err = step[sysname]()
@@ -415,6 +560,41 @@ def ensure(apply: bool = True) -> tuple[bool, str]:
415
560
  return False, f"activity audit setup failed: {exc}"
416
561
 
417
562
 
563
+ def _sudo_without_password() -> bool:
564
+ """Can this (unprivileged) process become root with no prompt? Never prompts."""
565
+ if not shutil.which("sudo"):
566
+ return False
567
+ return _run(["sudo", "-n", "true"], timeout=15)[0] == 0
568
+
569
+
570
+ def _ensure_via_sudo() -> tuple[bool, str]:
571
+ """Run the privileged half as root through ``sudo -n``, as THIS user's ``SUDO_USER``.
572
+
573
+ The root run writes the flag into this user's ``.env`` and grants this user the
574
+ audit log, so the panel (this user) picks both up. Same interpreter, so the same
575
+ installation is what runs — never whatever ``agentos`` is first on root's PATH.
576
+ """
577
+ before = settings.audit_enabled()
578
+ rc, out = _run(["sudo", "-n", sys.executable, "-m", "sdk.activity.os_setup"], timeout=1800)
579
+ lines = [ln.strip() for ln in out.splitlines() if ln.strip()]
580
+ # cli() prints the outcome first ("activity audit …") and a restart hint after it.
581
+ last = next((ln for ln in lines if ln.startswith("activity audit")), lines[-1] if lines else "")
582
+ if rc != 0:
583
+ logger.warning("activity.os_setup.sudo_failed rc=%s out=%s", rc, out[-300:])
584
+ return False, f"activity audit setup incomplete (via sudo): {last or f'exit {rc}'}"
585
+ changed = settings.audit_enabled() and not before
586
+ return changed, (last if last and "already set up" not in last else
587
+ ("activity audit enabled (via sudo)" if changed else ""))
588
+
589
+
590
+ def _agentos_exe() -> str:
591
+ """Absolute ``agentos`` path, so ``sudo`` finds the venv / ~/.local/bin install."""
592
+ exe = Path(sys.executable).parent / ("agentos.exe" if _system() == "windows" else "agentos")
593
+ if exe.exists():
594
+ return str(exe)
595
+ return shutil.which("agentos") or "agentos"
596
+
597
+
418
598
  def cli(argv: list[str]) -> int:
419
599
  """``agentos activity-setup [--check]`` — run (or just report) the OS audit setup."""
420
600
  changed, msg = ensure(apply="--check" not in argv)
@@ -424,5 +604,11 @@ def cli(argv: list[str]) -> int:
424
604
  print("activity audit: already set up" if settings.audit_enabled()
425
605
  else "activity audit: nothing to do on this platform")
426
606
  if changed:
427
- print("restart the panel for it to take effect: sudo systemctl restart agentos-ui")
607
+ print("restart the panel for it to take effect: systemctl --user restart agentos-ui "
608
+ "(or sudo systemctl restart agentos-ui for a system service). A running panel "
609
+ "also turns the collector on by itself at its next start.")
428
610
  return 1 if "incomplete" in msg or "failed" in msg else 0
611
+
612
+
613
+ if __name__ == "__main__": # `sudo -n python -m sdk.activity.os_setup` (see _ensure_via_sudo)
614
+ sys.exit(cli(sys.argv[1:]))
@@ -216,6 +216,36 @@ class TestAuditdParsing:
216
216
  assert (events[0]["action"], events[0]["severity"]) == ("login.failed", "WARNING")
217
217
  assert events[0]["target"] == "198.51.100.9"
218
218
 
219
+ @pytest.mark.parametrize("acct, expected", [
220
+ ('"root"', "root"),
221
+ ('"admin"', "admin"),
222
+ ('"dead"', "dead"), # quoted: a name, even though it is valid hex
223
+ ("6F7261636C65", "oracle"), # unquoted hex: auditd's encoding
224
+ ])
225
+ def test_a_failed_login_records_the_account_that_was_tried(self, acct, expected):
226
+ """Without this every failed SSH login had osUser=None, so a brute force showed
227
+ which IPs attacked but never which accounts they were guessing."""
228
+ p = _parser()
229
+ _feed(p, [
230
+ "type=USER_LOGIN msg=audit(1696852800.000:11): pid=2000 uid=0 auid=4294967295 "
231
+ "ses=4294967295 msg='op=login id=4294967295 acct=" + acct + " exe=\"/usr/sbin/sshd\" "
232
+ "hostname=? addr=198.51.100.9 terminal=ssh res=failed'",
233
+ "type=EOE msg=audit(1696852800.000:11): ",
234
+ ])
235
+ _, events = _queued()
236
+ assert events[0]["action"] == "login.failed" and events[0]["osUser"] == expected
237
+
238
+ def test_an_unknown_account_on_a_failed_login_is_not_recorded_as_a_name(self):
239
+ p = _parser(user_name=L._default_user_name) # the real resolver: unset uid → None
240
+ _feed(p, [
241
+ "type=USER_LOGIN msg=audit(1696852800.000:12): pid=2000 uid=0 auid=4294967295 "
242
+ "ses=4294967295 msg='op=login acct=\"(unknown)\" exe=\"/usr/sbin/sshd\" "
243
+ "hostname=? addr=198.51.100.9 terminal=ssh res=failed'",
244
+ "type=EOE msg=audit(1696852800.000:12): ",
245
+ ])
246
+ _, events = _queued()
247
+ assert events[0]["action"] == "login.failed" and "osUser" not in events[0]
248
+
219
249
  def test_garbage_lines_are_ignored(self):
220
250
  p = _parser()
221
251
  for junk in ("", "not an audit line", "type=SYSCALL garbage", "type=EOE msg=audit(x:y):"):
@@ -539,3 +569,64 @@ class TestRunner:
539
569
 
540
570
  monkeypatch.setattr(collectors, "make_collector", boom)
541
571
  assert collectors.start() is None
572
+
573
+
574
+ class TestSetupAtPanelStart:
575
+ """A box whose install/update ran unprivileged is set up by the always-running panel,
576
+ then the collector starts in the same process — no restart, no command typed."""
577
+
578
+ def test_an_explicit_opt_out_starts_nothing(self, monkeypatch):
579
+ monkeypatch.setenv("AGENTOS_NO_AUDIT_SETUP", "1")
580
+ assert collectors.start() is None
581
+
582
+ def test_an_explicit_off_starts_nothing(self, monkeypatch):
583
+ monkeypatch.delenv("AGENTOS_NO_AUDIT_SETUP", raising=False)
584
+ monkeypatch.setenv("AGENTOS_ACTIVITY_AUDIT", "0")
585
+ assert collectors.start() is None
586
+
587
+ def test_setup_then_the_collector_runs_in_the_same_process(self, monkeypatch):
588
+ import asyncio
589
+
590
+ from sdk.activity import os_setup
591
+
592
+ monkeypatch.delenv("AGENTOS_NO_AUDIT_SETUP", raising=False)
593
+ monkeypatch.delenv("AGENTOS_ACTIVITY_AUDIT", raising=False)
594
+ monkeypatch.setattr(os_setup, "_opted_out", lambda: False)
595
+ ran = []
596
+
597
+ def fake_ensure(apply=True):
598
+ monkeypatch.setenv("AGENTOS_ACTIVITY_AUDIT", "1") # what setup achieves
599
+ return True, "activity audit enabled — installed auditd"
600
+
601
+ class Collector:
602
+ def run(self):
603
+ ran.append("run")
604
+
605
+ def stop(self):
606
+ pass
607
+
608
+ monkeypatch.setattr(os_setup, "ensure", fake_ensure)
609
+ monkeypatch.setattr(collectors, "make_collector", lambda os_name=None: Collector())
610
+
611
+ async def go():
612
+ task = collectors.start()
613
+ assert task is not None
614
+ await task
615
+ asyncio.run(go())
616
+ assert ran == ["run"]
617
+
618
+ def test_no_collector_when_setup_could_not_finish(self, monkeypatch):
619
+ import asyncio
620
+
621
+ from sdk.activity import os_setup
622
+
623
+ monkeypatch.delenv("AGENTOS_NO_AUDIT_SETUP", raising=False)
624
+ monkeypatch.delenv("AGENTOS_ACTIVITY_AUDIT", raising=False)
625
+ monkeypatch.setattr(os_setup, "_opted_out", lambda: False)
626
+ monkeypatch.setattr(os_setup, "ensure", lambda apply=True: (False, "run: sudo agentos activity-setup"))
627
+ monkeypatch.setattr(collectors, "make_collector",
628
+ lambda os_name=None: pytest.fail("collector built without audit"))
629
+
630
+ async def go():
631
+ await collectors.start()
632
+ asyncio.run(go())