devops-bot-sdk 1.6.42__tar.gz → 1.6.46__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 (221) hide show
  1. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/SOURCES.txt +4 -0
  4. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/__init__.py +13 -4
  6. devops_bot_sdk-1.6.46/sdk/activity/collectors/macos_eslogger.py +356 -0
  7. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/windows_eventlog.py +28 -1
  8. devops_bot_sdk-1.6.46/sdk/activity/os_setup.py +428 -0
  9. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/cli.py +10 -1
  10. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/server.py +9 -0
  11. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/service.py +87 -3
  12. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/updater.py +29 -2
  13. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_activity_collectors.py +35 -3
  14. devops_bot_sdk-1.6.46/tests/test_activity_macos.py +262 -0
  15. devops_bot_sdk-1.6.46/tests/test_activity_os_setup.py +151 -0
  16. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_update_timer.py +94 -0
  17. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/README.md +0 -0
  18. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  19. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  20. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/requires.txt +0 -0
  21. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  22. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/pyproject.toml +0 -0
  23. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/__init__.py +0 -0
  24. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/classifier.py +0 -0
  25. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/common.py +0 -0
  26. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/collectors/linux_auditd.py +0 -0
  27. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/models.py +0 -0
  28. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/recorder.py +0 -0
  29. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/redact.py +0 -0
  30. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/settings.py +0 -0
  31. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/shipper.py +0 -0
  32. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/activity/spool.py +0 -0
  33. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agentd.py +0 -0
  34. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/__init__.py +0 -0
  35. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/activity.py +0 -0
  36. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/builder.py +0 -0
  37. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/chat.py +0 -0
  38. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/connectors.py +0 -0
  39. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/__init__.py +0 -0
  40. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/claude_code.py +0 -0
  41. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/http.py +0 -0
  42. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/mcp.py +0 -0
  43. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/n8n.py +0 -0
  44. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/engines/shell.py +0 -0
  45. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/integrations.py +0 -0
  46. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/inventory.py +0 -0
  47. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/library/__init__.py +0 -0
  48. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/manifest.py +0 -0
  49. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/prompt_studio.py +0 -0
  50. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/prompt_template.py +0 -0
  51. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/prompt_vars.py +0 -0
  52. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/run_log.py +0 -0
  53. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/schedules.py +0 -0
  54. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/secrets.py +0 -0
  55. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/ticket_run.py +0 -0
  56. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/usage.py +0 -0
  57. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/agents/varstore.py +0 -0
  58. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/browser_profile.py +0 -0
  59. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/client.py +0 -0
  60. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/__init__.py +0 -0
  61. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/files.py +0 -0
  62. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/process.py +0 -0
  63. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/collectors/screenshot.py +0 -0
  64. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/config.py +0 -0
  65. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/crucial.py +0 -0
  66. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/departments.py +0 -0
  67. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/__init__.py +0 -0
  68. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/assets.py +0 -0
  69. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/bootstrap.py +0 -0
  70. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/browser.py +0 -0
  71. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/compare.py +0 -0
  72. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/design_verify/loop.py +0 -0
  73. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/estimator.py +0 -0
  74. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/exceptions.py +0 -0
  75. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/firewall.py +0 -0
  76. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/formats.py +0 -0
  77. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/git_ops.py +0 -0
  78. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/github_fetch.py +0 -0
  79. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/github_ingest.py +0 -0
  80. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/graph_recon.py +0 -0
  81. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/graphify.py +0 -0
  82. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/hooks/__init__.py +0 -0
  83. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/hooks/crucial_guard.py +0 -0
  84. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/intake.py +0 -0
  85. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ipc/__init__.py +0 -0
  86. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ipc/electron_bridge.py +0 -0
  87. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ipc/handlers.py +0 -0
  88. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/journal.py +0 -0
  89. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/local_exec.py +0 -0
  90. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/memory_guard.py +0 -0
  91. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/__init__.py +0 -0
  92. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/envelope.py +0 -0
  93. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/requests.py +0 -0
  94. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/responses.py +0 -0
  95. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/models/snapshots.py +0 -0
  96. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/phase_timer.py +0 -0
  97. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/plan_run.py +0 -0
  98. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/platform_compat.py +0 -0
  99. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/prd_progress.py +0 -0
  100. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/proof_readers.py +0 -0
  101. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/providers.py +0 -0
  102. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/py.typed +0 -0
  103. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/resume.py +0 -0
  104. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/router.py +0 -0
  105. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/run_auto.py +0 -0
  106. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/scheduler.py +0 -0
  107. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/self_report.py +0 -0
  108. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/self_report_local.py +0 -0
  109. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/sse.py +0 -0
  110. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/swagger.py +0 -0
  111. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/task_file.py +0 -0
  112. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/task_types.py +0 -0
  113. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/test.py +0 -0
  114. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/test_pipeline.py +0 -0
  115. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ticket_fields.py +0 -0
  116. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/tiers/__init__.py +0 -0
  117. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/tiers/mini_agent.py +0 -0
  118. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/__init__.py +0 -0
  119. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/api.py +0 -0
  120. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/dashboard.py +0 -0
  121. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/health.py +0 -0
  122. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/ip_allowlist.py +0 -0
  123. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/jobs.py +0 -0
  124. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/models.py +0 -0
  125. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/runner.py +0 -0
  126. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/security.py +0 -0
  127. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/session.py +0 -0
  128. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/setup_manager.py +0 -0
  129. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/agents.html +0 -0
  130. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/app.js +0 -0
  131. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/chat.html +0 -0
  132. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/custom-agents.html +0 -0
  133. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/default-agents.html +0 -0
  134. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/index.html +0 -0
  135. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/report.html +0 -0
  136. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/runs.html +0 -0
  137. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/schedules.html +0 -0
  138. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/setup.html +0 -0
  139. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/styles.css +0 -0
  140. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/static/tickets.html +0 -0
  141. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/ui/swagger.py +0 -0
  142. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/url_access.py +0 -0
  143. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/vps_health.py +0 -0
  144. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/sdk/write_hook.py +0 -0
  145. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/setup.cfg +0 -0
  146. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_activity_core.py +0 -0
  147. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_activity_pipeline.py +0 -0
  148. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_activity.py +0 -0
  149. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_activity_tracking.py +0 -0
  150. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_chat_features.py +0 -0
  151. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_listing.py +0 -0
  152. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_prompt_async.py +0 -0
  153. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_prompt_models.py +0 -0
  154. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_run_ticket.py +0 -0
  155. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_schedules.py +0 -0
  156. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agent_workspace_settings.py +0 -0
  157. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_agents_connectors_builder.py +0 -0
  158. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_allowed_tools_config.py +0 -0
  159. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_auto_agent_dispatch.py +0 -0
  160. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_browser_profile.py +0 -0
  161. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_cheap_tier.py +0 -0
  162. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_client_jira_projects.py +0 -0
  163. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_config_token_diagnostics.py +0 -0
  164. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_configure_token_owner.py +0 -0
  165. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_console_encoding.py +0 -0
  166. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_crash_recovery.py +0 -0
  167. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_departments.py +0 -0
  168. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_done_transition.py +0 -0
  169. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_epic_decompose_run.py +0 -0
  170. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_epic_inherited_agent_id.py +0 -0
  171. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_epic_session_isolation.py +0 -0
  172. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_estimator.py +0 -0
  173. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_firewall_open_on_update.py +0 -0
  174. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_formats.py +0 -0
  175. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_git_action.py +0 -0
  176. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_github_fetch.py +0 -0
  177. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_github_ingest.py +0 -0
  178. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_graph_recon.py +0 -0
  179. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_graphify_build.py +0 -0
  180. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_graphify_hook_install.py +0 -0
  181. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_health.py +0 -0
  182. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_intake_job_outcome.py +0 -0
  183. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_intake_plan_review.py +0 -0
  184. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_journal.py +0 -0
  185. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_local_exec_provider.py +0 -0
  186. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_mcp_route_contract.py +0 -0
  187. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_memory_guard.py +0 -0
  188. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_phase_timer.py +0 -0
  189. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_plan_new_command.py +0 -0
  190. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_plan_run_activity.py +0 -0
  191. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_pr_identity.py +0 -0
  192. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_prd_artifact.py +0 -0
  193. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_project_path_resolution.py +0 -0
  194. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_proof_readers.py +0 -0
  195. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_providers.py +0 -0
  196. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_recon_session_isolation.py +0 -0
  197. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_resume.py +0 -0
  198. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_router.py +0 -0
  199. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_run_auto_agent_only.py +0 -0
  200. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_run_outputs.py +0 -0
  201. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_run_verdict_is_honest.py +0 -0
  202. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_schedule_api.py +0 -0
  203. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_schedule_outputs_and_plans.py +0 -0
  204. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_scheduler.py +0 -0
  205. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_self_report.py +0 -0
  206. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_service_token_warning.py +0 -0
  207. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_swagger_command.py +0 -0
  208. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_task_file_and_plan_run.py +0 -0
  209. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_task_file_format_doc.py +0 -0
  210. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ticket_fields.py +0 -0
  211. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ticket_run_resolve_by_id.py +0 -0
  212. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ticket_speed_fixes.py +0 -0
  213. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_tier_flag_parity.py +0 -0
  214. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ui_gate.py +0 -0
  215. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_ui_lifespan.py +0 -0
  216. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_updater_extras.py +0 -0
  217. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_updater_reports_the_truth.py +0 -0
  218. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_url_access.py +0 -0
  219. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_vps_health.py +0 -0
  220. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/tests/test_windows_loop_identity.py +0 -0
  221. {devops_bot_sdk-1.6.42 → devops_bot_sdk-1.6.46}/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.42
3
+ Version: 1.6.46
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.42
3
+ Version: 1.6.46
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
@@ -54,6 +54,7 @@ sdk/write_hook.py
54
54
  sdk/activity/__init__.py
55
55
  sdk/activity/classifier.py
56
56
  sdk/activity/models.py
57
+ sdk/activity/os_setup.py
57
58
  sdk/activity/recorder.py
58
59
  sdk/activity/redact.py
59
60
  sdk/activity/settings.py
@@ -62,6 +63,7 @@ sdk/activity/spool.py
62
63
  sdk/activity/collectors/__init__.py
63
64
  sdk/activity/collectors/common.py
64
65
  sdk/activity/collectors/linux_auditd.py
66
+ sdk/activity/collectors/macos_eslogger.py
65
67
  sdk/activity/collectors/windows_eventlog.py
66
68
  sdk/agents/__init__.py
67
69
  sdk/agents/activity.py
@@ -137,6 +139,8 @@ sdk/ui/static/styles.css
137
139
  sdk/ui/static/tickets.html
138
140
  tests/test_activity_collectors.py
139
141
  tests/test_activity_core.py
142
+ tests/test_activity_macos.py
143
+ tests/test_activity_os_setup.py
140
144
  tests/test_activity_pipeline.py
141
145
  tests/test_agent_activity.py
142
146
  tests/test_agent_activity_tracking.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.42
3
+ Version: 1.6.46
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.42" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.46" # 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
@@ -2,10 +2,8 @@
2
2
 
3
3
  Linux auditd sdk.activity.collectors.linux_auditd
4
4
  Windows Security log sdk.activity.collectors.windows_eventlog
5
- macOS not supported yet — a faithful collector needs an Endpoint Security
6
- client, which is a signed system extension rather than Python. See
7
- docs/ACTIVITY_AUDIT.md. (Agent runs are still recorded on macOS: that
8
- part is the SDK's own and needs no OS support.)
5
+ macOS Endpoint Security, through Apple's own ``eslogger`` (macOS 13+)
6
+ sdk.activity.collectors.macos_eslogger
9
7
 
10
8
  ``start()`` has the same contract as the other crons the UI lifespan starts
11
9
  (``sdk.vps_health.start`` …): it returns an asyncio task, or ``None`` when disabled
@@ -47,6 +45,17 @@ def make_collector(os_name: Os | None = None) -> Any | None:
47
45
  )
48
46
 
49
47
  return WindowsEventLogCollector(WindowsEventProcessor(agent_user=AGENT_OS_USER))
48
+ if os_name is Os.MACOS:
49
+ from sdk.activity.collectors.macos_eslogger import EsloggerCollector, EsloggerParser
50
+
51
+ mac_uid: int | None = None
52
+ try:
53
+ import pwd
54
+
55
+ mac_uid = pwd.getpwnam(AGENT_OS_USER).pw_uid
56
+ except Exception: # noqa: BLE001 — no such user (yet)
57
+ mac_uid = None
58
+ return EsloggerCollector(parser=EsloggerParser(agent_uid=mac_uid))
50
59
  return None
51
60
 
52
61
 
@@ -0,0 +1,356 @@
1
+ """macOS collector: turns Endpoint Security events (via Apple's ``eslogger``) into
2
+ sessions and events.
3
+
4
+ A faithful macOS collector needs an Endpoint Security client, and an ES client must
5
+ be a signed, entitled binary — which this Python SDK cannot be. Apple ships one:
6
+ ``/usr/bin/eslogger`` (macOS 13+) subscribes to ES events and prints each as a line
7
+ of JSON. So macOS gets the same shape as Linux:
8
+
9
+ eslogger (root LaunchDaemon, set up by sdk.activity.os_setup)
10
+ → /var/log/agentos/eslogger.ndjson (like auditd → audit.log)
11
+ → EsloggerCollector tails it in the panel (like AuditdCollector)
12
+
13
+ Events used:
14
+
15
+ * ``exec`` → a COMMAND event, one per exec
16
+ * ``openssh_login`` / ``openssh_logout`` → SSH LOGIN / LOGOUT and the login session
17
+ * ``login_login`` / ``login_logout``,
18
+ ``lw_session_login`` / ``lw_session_logout`` → CONSOLE LOGIN / LOGOUT
19
+ * ``sudo`` / ``su`` → a PRIVILEGE event
20
+
21
+ eslogger's JSON mirrors ``es_message_t``: ``event_type``, ``time``, ``process`` (with
22
+ ``audit_token`` → pid/euid/ruid/auid/asid, ``ppid``, ``executable.path``, ``tty``) and
23
+ ``event`` holding exactly one key named after the event. Field access here is
24
+ defensive (``.get`` everywhere): the format is Apple's and has grown between macOS
25
+ releases.
26
+
27
+ The parser is pure and stateful — feed it lines, it records through
28
+ :mod:`sdk.activity.recorder` — so it is testable without a Mac. Attribution follows
29
+ the Linux collector: agentos provenance (registry, or ``AGENTOS_RUN_ID`` in the exec
30
+ environment when eslogger includes it) → the ``agentos`` OS user → an interactive
31
+ session (a tty, or an audit session opened by a login) → launchd with no terminal =
32
+ SYSTEM. SYSTEM execs are skipped by default, as on Windows: a Mac runs a constant
33
+ stream of ``xpcproxy`` / ``mdworker`` housekeeping that is nobody's action.
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import json
38
+ import logging
39
+ from collections import OrderedDict
40
+ from collections.abc import Callable
41
+ from datetime import datetime
42
+ from pathlib import Path
43
+
44
+ from sdk.activity import recorder
45
+ from sdk.activity.classifier import AgentProcessRegistry, ProcessFacts, classify
46
+ from sdk.activity.collectors.common import SessionBook
47
+ from sdk.activity.collectors.linux_auditd import AuditdCollector
48
+ from sdk.activity.models import (
49
+ ActivityEvent,
50
+ ActorType,
51
+ Category,
52
+ Os,
53
+ SessionKind,
54
+ Severity,
55
+ Source,
56
+ )
57
+
58
+ logger = logging.getLogger("sdk.activity.collectors.macos")
59
+
60
+ DEFAULT_ES_LOG = "/var/log/agentos/eslogger.ndjson"
61
+
62
+ #: The ES events the LaunchDaemon subscribes to — kept here so os_setup and the
63
+ #: parser cannot disagree about them.
64
+ ES_EVENTS = (
65
+ "exec",
66
+ "openssh_login", "openssh_logout",
67
+ "login_login", "login_logout",
68
+ "lw_session_login", "lw_session_logout",
69
+ "sudo", "su",
70
+ )
71
+
72
+ #: ``auid`` when no login owns the process (AU_DEFAUDITID).
73
+ _UNSET_AUID = {4294967295, -1}
74
+ _MAX_PID_CACHE = 20_000
75
+
76
+
77
+ def _ts(msg: dict) -> float:
78
+ raw = msg.get("time")
79
+ if isinstance(raw, str):
80
+ try:
81
+ return datetime.fromisoformat(raw.replace("Z", "+00:00")).timestamp()
82
+ except ValueError:
83
+ pass
84
+ import time
85
+
86
+ return time.time()
87
+
88
+
89
+ def _token(proc: dict | None) -> dict:
90
+ return (proc or {}).get("audit_token") or {}
91
+
92
+
93
+ def _path(obj) -> str | None:
94
+ """``{"path": "/bin/ls", ...}`` → ``/bin/ls``; tolerates a bare string or null."""
95
+ if isinstance(obj, dict):
96
+ return obj.get("path") or None
97
+ return obj or None
98
+
99
+
100
+ def _uid(obj) -> int | None:
101
+ """eslogger nests uids as ``{"uid": 501}``; accept a bare int too."""
102
+ if isinstance(obj, dict):
103
+ obj = obj.get("uid")
104
+ try:
105
+ return int(obj) if obj is not None else None
106
+ except (TypeError, ValueError):
107
+ return None
108
+
109
+
110
+ def _default_user_name(uid: int | None) -> str | None:
111
+ if uid is None or uid in _UNSET_AUID:
112
+ return None
113
+ try:
114
+ import pwd
115
+
116
+ return pwd.getpwuid(uid).pw_name
117
+ except Exception: # noqa: BLE001 — unknown uid, not POSIX
118
+ return str(uid)
119
+
120
+
121
+ def _env_run_id(env) -> str | None:
122
+ """``AGENTOS_RUN_ID`` from an exec's environment, when eslogger included it."""
123
+ for item in env or []:
124
+ if isinstance(item, str) and item.startswith("AGENTOS_RUN_ID="):
125
+ return item.split("=", 1)[1] or None
126
+ return None
127
+
128
+
129
+ class EsloggerParser:
130
+ def __init__(
131
+ self,
132
+ *,
133
+ agent_uid: int | None = None,
134
+ registry: AgentProcessRegistry | None = None,
135
+ user_name: Callable[[int | None], str | None] = _default_user_name,
136
+ book: SessionBook | None = None,
137
+ ignore_system: bool = True,
138
+ ) -> None:
139
+ self._agent_uid = agent_uid
140
+ self._registry = registry or AgentProcessRegistry()
141
+ self._user_name = user_name
142
+ self._book = book or SessionBook()
143
+ self._ignore_system = ignore_system
144
+ self._ppid: OrderedDict[int, int] = OrderedDict()
145
+ self._asid_key: dict[int, str] = {} # audit session id → our session key
146
+ self._asid_kind: dict[int, SessionKind] = {}
147
+ self._ssh: dict[tuple[str, str], str] = {} # (user, source) → session key
148
+
149
+ # ── input ────────────────────────────────────────────────────────────────
150
+
151
+ def feed(self, line: str) -> None:
152
+ line = line.strip()
153
+ if not line.startswith("{"):
154
+ return
155
+ try:
156
+ msg = json.loads(line)
157
+ event = msg.get("event") or {}
158
+ if not isinstance(event, dict) or not event:
159
+ return
160
+ name, body = next(iter(event.items()))
161
+ handler = getattr(self, f"_on_{name}", None)
162
+ if handler is not None:
163
+ handler(msg, body or {})
164
+ except Exception as exc: # noqa: BLE001 — one bad line must not stop the tail
165
+ logger.warning("eslogger: could not process line: %s", exc)
166
+
167
+ def flush(self) -> None:
168
+ """Nothing is buffered — every eslogger line is a whole event."""
169
+
170
+ # ── sessions ─────────────────────────────────────────────────────────────
171
+
172
+ def _asid_session(self, asid: int, ts: float) -> str:
173
+ key = self._asid_key.get(asid)
174
+ if key is None:
175
+ # asids are reused after a reboot; the first-seen time keeps keys unique.
176
+ key = f"es-asid:{asid}:{int(ts)}"
177
+ self._asid_key[asid] = key
178
+ return key
179
+
180
+ def _login(self, msg: dict, *, kind: SessionKind, user: str | None, uid: int | None,
181
+ remote: str | None, ok: bool, action: str = "login") -> str | None:
182
+ ts = _ts(msg)
183
+ if not ok:
184
+ recorder.record_event(ActivityEvent(
185
+ os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.LOGIN,
186
+ action="login.failed", occurredAt=ts, osUser=user, target=remote,
187
+ severity=Severity.WARNING, actorType=ActorType.UNKNOWN, actorConfidence=0,
188
+ ))
189
+ return None
190
+ verdict = classify(ProcessFacts(
191
+ login_kind=kind, has_tty=True, is_agent_os_user=self._is_agent(uid),
192
+ ))
193
+ asid = _token(msg.get("process")).get("asid")
194
+ key = (self._asid_session(int(asid), ts) if isinstance(asid, int) and asid > 0
195
+ else f"es-login:{user}:{remote or 'local'}:{int(ts)}")
196
+ if isinstance(asid, int) and asid > 0:
197
+ self._asid_kind[int(asid)] = kind
198
+ info = self._book.ensure(key, started=ts, verdict=verdict, kind=kind,
199
+ os_user=user, remote_ip=remote)
200
+ recorder.record_event(ActivityEvent(
201
+ os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.LOGIN,
202
+ action=action, occurredAt=ts, sessionKey=key, osUser=user, target=remote,
203
+ agentRunId=info.agent_run_id,
204
+ ))
205
+ return key
206
+
207
+ def _logout(self, msg: dict, key: str | None, user: str | None) -> None:
208
+ if not key:
209
+ return
210
+ ts = _ts(msg)
211
+ recorder.record_event(ActivityEvent(
212
+ os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.LOGOUT,
213
+ action="logout", occurredAt=ts, sessionKey=key, osUser=user,
214
+ ))
215
+ self._book.end(key, ts)
216
+ for asid, k in list(self._asid_key.items()):
217
+ if k == key:
218
+ self._asid_key.pop(asid, None)
219
+ self._asid_kind.pop(asid, None)
220
+
221
+ # ── handlers (named _on_<eslogger event>) ───────────────────────────────
222
+
223
+ def _on_openssh_login(self, msg: dict, ev: dict) -> None:
224
+ user = ev.get("username") or None
225
+ remote = ev.get("source_address") or None
226
+ key = self._login(msg, kind=SessionKind.SSH, user=user, uid=_uid(ev.get("uid")),
227
+ remote=remote, ok=bool(ev.get("success", True)))
228
+ if key:
229
+ self._ssh[(str(user), str(remote))] = key
230
+
231
+ def _on_openssh_logout(self, msg: dict, ev: dict) -> None:
232
+ user = ev.get("username") or None
233
+ key = self._ssh.pop((str(user), str(ev.get("source_address") or None)), None)
234
+ self._logout(msg, key, user)
235
+
236
+ def _on_login_login(self, msg: dict, ev: dict) -> None:
237
+ self._login(msg, kind=SessionKind.CONSOLE, user=ev.get("username") or None,
238
+ uid=_uid(ev.get("uid")), remote=None, ok=bool(ev.get("success", True)))
239
+
240
+ def _on_lw_session_login(self, msg: dict, ev: dict) -> None:
241
+ self._login(msg, kind=SessionKind.CONSOLE, user=ev.get("username") or None,
242
+ uid=None, remote=None, ok=True)
243
+
244
+ def _console_logout(self, msg: dict, ev: dict) -> None:
245
+ asid = _token(msg.get("process")).get("asid")
246
+ key = self._asid_key.get(int(asid)) if isinstance(asid, int) else None
247
+ self._logout(msg, key, ev.get("username") or None)
248
+
249
+ _on_login_logout = _console_logout
250
+ _on_lw_session_logout = _console_logout
251
+
252
+ def _privilege(self, msg: dict, ev: dict, action: str) -> None:
253
+ ts = _ts(msg)
254
+ tok = _token(msg.get("process"))
255
+ asid = tok.get("asid")
256
+ ok = bool(ev.get("success", True))
257
+ recorder.record_event(ActivityEvent(
258
+ os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.PRIVILEGE,
259
+ action=action, occurredAt=ts,
260
+ sessionKey=self._asid_key.get(int(asid)) if isinstance(asid, int) else None,
261
+ osUser=ev.get("from_username") or self._user_name(_uid(ev.get("from_uid"))),
262
+ command=ev.get("command") or None,
263
+ target=ev.get("to_username") or None,
264
+ pid=tok.get("pid"),
265
+ severity=Severity.NOTICE if ok else Severity.WARNING,
266
+ exitCode=0 if ok else 1,
267
+ ))
268
+
269
+ def _on_sudo(self, msg: dict, ev: dict) -> None:
270
+ self._privilege(msg, ev, "sudo")
271
+
272
+ def _on_su(self, msg: dict, ev: dict) -> None:
273
+ self._privilege(msg, ev, "su")
274
+
275
+ def _on_exec(self, msg: dict, ev: dict) -> None:
276
+ ts = _ts(msg)
277
+ target = ev.get("target") or {}
278
+ tok = _token(target) or _token(msg.get("process"))
279
+ pid = int(tok.get("pid") or 0)
280
+ ppid = int(target.get("ppid") or (msg.get("process") or {}).get("ppid") or 0)
281
+ self._remember_ppid(pid, ppid)
282
+ euid, auid, asid = tok.get("euid"), tok.get("auid"), tok.get("asid")
283
+ login = isinstance(auid, int) and auid not in _UNSET_AUID
284
+ tty = _path(target.get("tty"))
285
+ has_tty = bool(tty)
286
+
287
+ root = self._registry.lookup(pid, self._ppid.get)
288
+ run_id = root.run_id if root else _env_run_id(ev.get("env"))
289
+ known_asid = isinstance(asid, int) and asid in self._asid_key
290
+
291
+ verdict = classify(ProcessFacts(
292
+ descends_from_agent=bool(run_id),
293
+ agent_run_id=run_id,
294
+ is_agent_os_user=self._is_agent(euid) or self._is_agent(auid),
295
+ has_tty=has_tty,
296
+ login_kind=self._asid_kind.get(asid) if isinstance(asid, int) else None,
297
+ spawned_by_service_manager=not login and not has_tty and not known_asid,
298
+ ))
299
+ if verdict.actor_type is ActorType.SYSTEM and self._ignore_system:
300
+ return
301
+
302
+ if run_id:
303
+ session_key, agent_run_id = recorder.agent_session_key(run_id), run_id
304
+ elif isinstance(asid, int) and asid > 0 and (login or has_tty or known_asid):
305
+ session_key = self._asid_session(asid, ts)
306
+ info = self._book.ensure(session_key, started=ts, verdict=verdict,
307
+ os_user=self._user_name(auid if login else euid))
308
+ agent_run_id = info.agent_run_id
309
+ elif verdict.actor_type is ActorType.AGENT:
310
+ session_key = f"os-user:{euid}:{int(ts) // 86400}"
311
+ info = self._book.ensure(session_key, started=ts, verdict=verdict,
312
+ os_user=self._user_name(euid))
313
+ agent_run_id = info.agent_run_id
314
+ else:
315
+ session_key, agent_run_id = None, None
316
+
317
+ args = ev.get("args")
318
+ command = " ".join(str(a) for a in args) if isinstance(args, list) else None
319
+ exe = _path(target.get("executable"))
320
+ recorder.record_event(ActivityEvent(
321
+ os=Os.MACOS, source=Source.ENDPOINT_SECURITY, category=Category.COMMAND,
322
+ action="exec", occurredAt=ts, sessionKey=session_key,
323
+ actorType=verdict.actor_type, actorConfidence=verdict.confidence,
324
+ agentRunId=agent_run_id,
325
+ osUser=self._user_name(auid if login else euid),
326
+ pid=pid or None, ppid=ppid or None,
327
+ processName=Path(exe).name if exe else None,
328
+ command=command or None, target=exe, exitCode=0,
329
+ ))
330
+
331
+ # ── helpers ──────────────────────────────────────────────────────────────
332
+
333
+ def _is_agent(self, uid) -> bool:
334
+ return self._agent_uid is not None and uid == self._agent_uid
335
+
336
+ def _remember_ppid(self, pid: int, ppid: int) -> None:
337
+ if not pid:
338
+ return
339
+ self._ppid[pid] = ppid
340
+ self._ppid.move_to_end(pid)
341
+ while len(self._ppid) > _MAX_PID_CACHE:
342
+ self._ppid.popitem(last=False)
343
+
344
+
345
+ class EsloggerCollector(AuditdCollector):
346
+ """Tails the eslogger log. The file-following (resume offset, partial lines,
347
+ rotation/truncation) is identical to the auditd tail, so it is reused as is."""
348
+
349
+ def __init__(self, path: str = DEFAULT_ES_LOG, *, parser: EsloggerParser | None = None,
350
+ state_file: Path | None = None, poll_s: float = 1.0) -> None:
351
+ super().__init__(
352
+ path,
353
+ parser=parser or EsloggerParser(), # type: ignore[arg-type] — same feed() contract
354
+ state_file=state_file or (Path.home() / ".agentos" / "activity_eslogger_state.json"),
355
+ poll_s=poll_s,
356
+ )
@@ -14,6 +14,11 @@ The event parser and the processor are pure; only :class:`WindowsEventLogCollect
14
14
  touches the OS. Processes created by SYSTEM / LOCAL SERVICE / NETWORK SERVICE are
15
15
  skipped by default (``ignore_system``): they are thousands of events an hour of
16
16
  Windows housekeeping that no human or agent did.
17
+
18
+ The collector's own polling is skipped too: every poll runs ``wevtutil.exe`` from the
19
+ panel process (as the panel's user, not SYSTEM), and each of those is itself a 4688.
20
+ Unfiltered, that is ~8,600 "Administrator ran wevtutil" events a day per VPS, each
21
+ poll recording the previous one.
17
22
  """
18
23
  from __future__ import annotations
19
24
 
@@ -47,6 +52,9 @@ logger = logging.getLogger("sdk.activity.collectors.windows")
47
52
  _NS = "{http://schemas.microsoft.com/win/2004/08/events/event}"
48
53
  EVENT_IDS = (4688, 4624, 4634, 4647, 4625)
49
54
  _MAX_PID_CACHE = 20_000
55
+ #: Executables the collector itself spawns to poll the log.
56
+ _OWN_TOOLS = ("wevtutil.exe",)
57
+ _MAX_OWN_PIDS = 1_000
50
58
 
51
59
  #: Logon types that are a PERSON at a keyboard / remote desktop.
52
60
  _INTERACTIVE = {2: SessionKind.CONSOLE, 11: SessionKind.CONSOLE, 10: SessionKind.RDP}
@@ -123,11 +131,14 @@ class WindowsEventProcessor:
123
131
  registry: AgentProcessRegistry | None = None,
124
132
  book: SessionBook | None = None,
125
133
  ignore_system: bool = True,
134
+ self_pid: int | None = None,
126
135
  ) -> None:
127
136
  self._agent_user = agent_user.lower() if agent_user else None
128
137
  self._registry = registry or AgentProcessRegistry()
129
138
  self._book = book or SessionBook()
130
139
  self._ignore_system = ignore_system
140
+ self._self_pid = os.getpid() if self_pid is None else self_pid
141
+ self._own: OrderedDict[int, None] = OrderedDict() # pids of our own polls + children
131
142
  self._ppid: OrderedDict[int, int] = OrderedDict()
132
143
  self._logons: dict[str, str] = {} # logon id → session key
133
144
  self._kinds: dict[str, SessionKind] = {} # logon id → login kind
@@ -202,6 +213,9 @@ class WindowsEventProcessor:
202
213
  d = ev.data
203
214
  pid, ppid = _hex(d.get("NewProcessId")), _hex(d.get("ProcessId"))
204
215
  self._remember_ppid(pid, ppid)
216
+ image = d.get("NewProcessName", "")
217
+ if self._is_own_poll(pid, ppid, image):
218
+ return
205
219
  logon_id = d.get("SubjectLogonId", "").lower()
206
220
  if self._ignore_system and logon_id in _SYSTEM_LOGON_IDS:
207
221
  return
@@ -232,7 +246,6 @@ class WindowsEventProcessor:
232
246
  session_key, started=ev.time, verdict=verdict, os_user=user,
233
247
  ).agent_run_id
234
248
 
235
- image = d.get("NewProcessName", "")
236
249
  recorder.record_event(ActivityEvent(
237
250
  os=Os.WINDOWS, source=Source.WIN_EVENTLOG, category=Category.COMMAND, action="exec",
238
251
  occurredAt=ev.time, sessionKey=session_key,
@@ -243,6 +256,20 @@ class WindowsEventProcessor:
243
256
  metadata={"parent": d.get("ParentProcessName") or None},
244
257
  ))
245
258
 
259
+ def _is_own_poll(self, pid: int, ppid: int, image: str) -> bool:
260
+ """A ``wevtutil`` this process spawned to poll, or a child of one (its ``conhost``).
261
+
262
+ Matched by parent pid AND image name, so anything else the panel spawns —
263
+ an agent run, ``git``, ``claude`` — is still recorded.
264
+ """
265
+ name = image.rsplit("\\", 1)[-1].lower()
266
+ if not ((ppid == self._self_pid and name in _OWN_TOOLS) or ppid in self._own):
267
+ return False
268
+ self._own[pid] = None
269
+ while len(self._own) > _MAX_OWN_PIDS:
270
+ self._own.popitem(last=False)
271
+ return True
272
+
246
273
  def _remember_ppid(self, pid: int, ppid: int) -> None:
247
274
  self._ppid[pid] = ppid
248
275
  self._ppid.move_to_end(pid)