devops-bot-sdk 1.6.70__tar.gz → 1.6.71__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/settings.py +21 -1
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/shipper.py +31 -4
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/spool.py +51 -16
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_activity_collectors.py +8 -1
- devops_bot_sdk-1.6.71/tests/test_activity_live_tracking.py +145 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_activity_macos.py +8 -1
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_activity_pipeline.py +18 -14
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/README.md +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/custom_agents.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/tickets.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/vendor/aidraftsman-cli-0.1.0.tgz +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/vps_identity.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_crud_cli.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_fallback_cli.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_scheduler_transient_retry.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_ticket_cli.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_vps_only_reporting.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_windows_task_paths.py +0 -0
- {devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/tests/test_write_hook.py +0 -0
|
@@ -144,6 +144,7 @@ sdk/ui/static/tickets.html
|
|
|
144
144
|
sdk/vendor/aidraftsman-cli-0.1.0.tgz
|
|
145
145
|
tests/test_activity_collectors.py
|
|
146
146
|
tests/test_activity_core.py
|
|
147
|
+
tests/test_activity_live_tracking.py
|
|
147
148
|
tests/test_activity_macos.py
|
|
148
149
|
tests/test_activity_os_setup.py
|
|
149
150
|
tests/test_activity_pipeline.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.71
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -34,7 +34,7 @@ Rules:
|
|
|
34
34
|
- All data egress through submit_webhook only
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "1.6.
|
|
37
|
+
__version__ = "1.6.71" # 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
|
|
@@ -23,8 +23,12 @@ from sdk.activity.spool import Spool
|
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger("sdk.activity.settings")
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
# Live tracking: one POST a minute is far under the relay's 60/min budget, and the
|
|
27
|
+
# five-minute default meant the newest activity was always up to five minutes stale.
|
|
28
|
+
DEFAULT_SHIP_INTERVAL_S = 60.0
|
|
27
29
|
MIN_SHIP_INTERVAL_S = 30.0
|
|
30
|
+
#: Events older than this are discarded instead of shipped (``0`` keeps everything).
|
|
31
|
+
DEFAULT_MAX_AGE_S = 3600.0
|
|
28
32
|
_FALSEY = {"0", "false", "no", "off"}
|
|
29
33
|
_TRUTHY = {"1", "true", "yes", "on"}
|
|
30
34
|
|
|
@@ -63,6 +67,22 @@ def ship_interval_seconds() -> float:
|
|
|
63
67
|
return DEFAULT_SHIP_INTERVAL_S
|
|
64
68
|
|
|
65
69
|
|
|
70
|
+
def max_age_seconds() -> float:
|
|
71
|
+
"""``AGENTOS_ACTIVITY_MAX_AGE_S`` — how old an event may be and still ship.
|
|
72
|
+
|
|
73
|
+
Default one hour: the trail is for live tracking, and a backlog shipped
|
|
74
|
+
oldest-first kept busy hosts days behind. ``0`` disables the cut-off.
|
|
75
|
+
"""
|
|
76
|
+
raw = setting("AGENTOS_ACTIVITY_MAX_AGE_S")
|
|
77
|
+
if not raw:
|
|
78
|
+
return DEFAULT_MAX_AGE_S
|
|
79
|
+
try:
|
|
80
|
+
return max(0.0, float(raw))
|
|
81
|
+
except ValueError:
|
|
82
|
+
logger.warning("AGENTOS_ACTIVITY_MAX_AGE_S=%r is not a number; using default", raw)
|
|
83
|
+
return DEFAULT_MAX_AGE_S
|
|
84
|
+
|
|
85
|
+
|
|
66
86
|
def spool_path() -> Path:
|
|
67
87
|
"""``~/.agentos/activity.db`` — resolved at call time so tests can redirect HOME."""
|
|
68
88
|
return Path.home() / ".agentos" / "activity.db"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Ship the activity spool to devops-bot-ai every
|
|
1
|
+
"""Ship the activity spool to devops-bot-ai every minute, newest first.
|
|
2
2
|
|
|
3
3
|
Same shape as ``sdk.vps_health``: an asyncio loop with a ``start()`` the UI
|
|
4
4
|
server's lifespan calls, cancelled by the host on shutdown.
|
|
@@ -25,8 +25,10 @@ from __future__ import annotations
|
|
|
25
25
|
|
|
26
26
|
import asyncio
|
|
27
27
|
import logging
|
|
28
|
+
import time
|
|
28
29
|
from collections.abc import Awaitable, Callable
|
|
29
30
|
from dataclasses import dataclass
|
|
31
|
+
from datetime import datetime, timezone
|
|
30
32
|
from typing import Any, Protocol
|
|
31
33
|
|
|
32
34
|
from sdk.activity import settings
|
|
@@ -188,17 +190,42 @@ def discard_spool(spool: Spool | None = None) -> int:
|
|
|
188
190
|
spool.ack(chunk)
|
|
189
191
|
|
|
190
192
|
|
|
193
|
+
def purge_stale_rows(spool: Spool | None = None, now: float | None = None) -> int:
|
|
194
|
+
"""Discard what is older than ``settings.max_age_seconds()``. Returns rows removed."""
|
|
195
|
+
max_age = settings.max_age_seconds()
|
|
196
|
+
if max_age <= 0:
|
|
197
|
+
return 0
|
|
198
|
+
spool = spool or settings.get_spool()
|
|
199
|
+
cutoff = (now if now is not None else time.time()) - max_age
|
|
200
|
+
cutoff_iso = (datetime.fromtimestamp(cutoff, timezone.utc)
|
|
201
|
+
.isoformat(timespec="milliseconds").replace("+00:00", "Z"))
|
|
202
|
+
dropped = spool.purge_stale(cutoff_iso)
|
|
203
|
+
if dropped:
|
|
204
|
+
logger.info("activity.stale_discarded rows=%d older_than_s=%.0f "
|
|
205
|
+
"(live tracking: only recent activity is shipped)", dropped, max_age)
|
|
206
|
+
return dropped
|
|
207
|
+
|
|
208
|
+
|
|
191
209
|
async def ship_tick(
|
|
192
210
|
is_vps: Callable[[], Awaitable[bool | None]] | None = None,
|
|
193
211
|
) -> FlushResult | None:
|
|
194
|
-
"""One cron tick: ship only from the caller's assigned VPS.
|
|
212
|
+
"""One cron tick: ship only from the caller's assigned VPS, newest first.
|
|
195
213
|
|
|
196
214
|
The relay files every batch under the TOKEN's VPS, so a laptop running the SDK
|
|
197
215
|
with the same token put its own events in the VPS's audit trail. On a machine
|
|
198
216
|
that is definitely not the VPS the queue is discarded — kept, it would only grow
|
|
199
217
|
forever, since that machine will never be allowed to send it. When the answer
|
|
200
|
-
is unknown (profile unreachable) everything is kept for the next tick.
|
|
218
|
+
is unknown (profile unreachable) everything recent is kept for the next tick.
|
|
219
|
+
|
|
220
|
+
Every tick first discards events older than ``AGENTOS_ACTIVITY_MAX_AGE_S``
|
|
221
|
+
(default one hour): the trail is for live tracking, and a backlog — a host
|
|
222
|
+
that was offline, or flooded by a brute-force attack — must never hold the
|
|
223
|
+
current activity back.
|
|
201
224
|
"""
|
|
225
|
+
try:
|
|
226
|
+
purge_stale_rows()
|
|
227
|
+
except Exception as exc: # noqa: BLE001 — a purge failure must not stop shipping
|
|
228
|
+
logger.warning("activity: stale purge failed: %s", exc)
|
|
202
229
|
if is_vps is None:
|
|
203
230
|
from sdk import vps_identity
|
|
204
231
|
is_vps = vps_identity.is_assigned_vps
|
|
@@ -222,7 +249,7 @@ async def ship_tick(
|
|
|
222
249
|
|
|
223
250
|
|
|
224
251
|
async def run_forever(interval_s: float | None = None, *, initial_delay_s: float = 30.0) -> None:
|
|
225
|
-
"""Flush every ``interval_s`` (default
|
|
252
|
+
"""Flush every ``interval_s`` (default 60 s) until cancelled."""
|
|
226
253
|
await asyncio.sleep(initial_delay_s)
|
|
227
254
|
while True:
|
|
228
255
|
try:
|
|
@@ -169,33 +169,68 @@ class Spool:
|
|
|
169
169
|
max_sessions: int = MAX_SESSIONS_PER_CHUNK,
|
|
170
170
|
max_bytes: int = MAX_CHUNK_BYTES,
|
|
171
171
|
) -> Chunk:
|
|
172
|
-
"""
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
"""Queued sessions, then the NEWEST events, within the per-request limits.
|
|
173
|
+
|
|
174
|
+
Live tracking needs the latest activity first. Shipping oldest-first left
|
|
175
|
+
busy hosts (a brute-forced RDP port alone is thousands of events an hour)
|
|
176
|
+
days behind, so the dashboard showed Tuesday's events on Thursday. Sessions
|
|
177
|
+
still go first, in arrival order: they are few, and an event must never
|
|
178
|
+
reach the relay ahead of the session it references. Does NOT remove
|
|
179
|
+
anything — call :meth:`ack` after delivery.
|
|
177
180
|
"""
|
|
178
181
|
chunk = Chunk()
|
|
179
|
-
|
|
182
|
+
size = 0
|
|
180
183
|
with self._lock:
|
|
181
|
-
|
|
182
|
-
"SELECT seq, rev, kind, payload FROM spool
|
|
183
|
-
|
|
184
|
+
sessions = self._db.execute(
|
|
185
|
+
"SELECT seq, rev, kind, payload FROM spool WHERE kind = 'session' "
|
|
186
|
+
"ORDER BY seq LIMIT ?",
|
|
187
|
+
(max_sessions,),
|
|
184
188
|
).fetchall()
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
events = self._db.execute(
|
|
190
|
+
"SELECT seq, rev, kind, payload FROM spool WHERE kind = 'event' "
|
|
191
|
+
"ORDER BY seq DESC LIMIT ?",
|
|
192
|
+
(max_events,),
|
|
193
|
+
).fetchall()
|
|
194
|
+
for seq, rev, kind, payload in [*sessions, *events]:
|
|
190
195
|
if chunk.rows and size + len(payload) > max_bytes:
|
|
191
196
|
break
|
|
192
197
|
# Re-fit on the way out too: rows queued by an older SDK predate the clamp.
|
|
193
198
|
chunk.rows.append(Row(seq, rev, kind, fit_wire_limits(kind, json.loads(payload))))
|
|
194
|
-
n_events += kind == "event"
|
|
195
|
-
n_sessions += kind == "session"
|
|
196
199
|
size += len(payload)
|
|
197
200
|
return chunk
|
|
198
201
|
|
|
202
|
+
def purge_stale(self, cutoff_iso: str) -> int:
|
|
203
|
+
"""Delete events that happened before ``cutoff_iso``, and sessions that ENDED
|
|
204
|
+
before it. Returns the rows removed.
|
|
205
|
+
|
|
206
|
+
For live tracking an event an hour old is history, and shipping it only
|
|
207
|
+
delays the current ones. Open sessions are kept whatever their age — a
|
|
208
|
+
login that is still going on is live state. ``cutoff_iso`` uses the wire
|
|
209
|
+
format (``2026-10-22T07:06:01.971Z``), so string order is time order.
|
|
210
|
+
"""
|
|
211
|
+
with self._lock:
|
|
212
|
+
try:
|
|
213
|
+
cur = self._db.execute(
|
|
214
|
+
"DELETE FROM spool WHERE "
|
|
215
|
+
"(kind = 'event' AND json_extract(payload, '$.occurredAt') < ?) OR "
|
|
216
|
+
"(kind = 'session' AND json_extract(payload, '$.endedAt') IS NOT NULL "
|
|
217
|
+
" AND json_extract(payload, '$.endedAt') < ?)",
|
|
218
|
+
(cutoff_iso, cutoff_iso),
|
|
219
|
+
)
|
|
220
|
+
return int(cur.rowcount or 0)
|
|
221
|
+
except sqlite3.OperationalError:
|
|
222
|
+
# SQLite built without JSON1: filter in Python instead.
|
|
223
|
+
stale = []
|
|
224
|
+
for seq, kind, payload in self._db.execute(
|
|
225
|
+
"SELECT seq, kind, payload FROM spool"
|
|
226
|
+
).fetchall():
|
|
227
|
+
wire = json.loads(payload)
|
|
228
|
+
when = wire.get("occurredAt") if kind == "event" else wire.get("endedAt")
|
|
229
|
+
if when and str(when) < cutoff_iso:
|
|
230
|
+
stale.append((seq,))
|
|
231
|
+
self._db.executemany("DELETE FROM spool WHERE seq = ?", stale)
|
|
232
|
+
return len(stale)
|
|
233
|
+
|
|
199
234
|
# ── ack ──────────────────────────────────────────────────────────────────
|
|
200
235
|
|
|
201
236
|
def ack(self, chunk: Chunk) -> None:
|
|
@@ -31,8 +31,15 @@ def _isolated(tmp_path, monkeypatch):
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def _queued():
|
|
34
|
+
"""Queued sessions and events in the order the collector RECORDED them.
|
|
35
|
+
|
|
36
|
+
The shipper sends newest-first (live tracking); these tests are about what a
|
|
37
|
+
collector recorded and in which order, so read the rows back by ``seq``.
|
|
38
|
+
"""
|
|
34
39
|
chunk = settings.get_spool().next_chunk()
|
|
35
|
-
|
|
40
|
+
rows = sorted(chunk.rows, key=lambda r: r.seq)
|
|
41
|
+
return ([r.wire for r in rows if r.kind == "session"],
|
|
42
|
+
[r.wire for r in rows if r.kind == "event"])
|
|
36
43
|
|
|
37
44
|
|
|
38
45
|
# ─────────────────────────────── Linux / auditd ───────────────────────────────
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Live tracking: the newest activity ships first, and stale backlog is discarded.
|
|
2
|
+
|
|
3
|
+
The incident: hosts that had been offline, or flooded by a brute-forced RDP port,
|
|
4
|
+
shipped their spool oldest-first. At 10:00 several VPS were delivering events from
|
|
5
|
+
two and three days earlier, so the dashboard was never live.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from sdk.activity import settings, shipper
|
|
16
|
+
from sdk.activity.spool import Spool
|
|
17
|
+
|
|
18
|
+
NOW = datetime(2026, 10, 22, 10, 0, 0, tzinfo=timezone.utc).timestamp()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _iso(minutes_ago: float) -> str:
|
|
22
|
+
t = datetime.fromtimestamp(NOW - minutes_ago * 60, timezone.utc)
|
|
23
|
+
return t.isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _ev(eid: str, minutes_ago: float) -> dict:
|
|
27
|
+
return {"eventId": eid, "occurredAt": _iso(minutes_ago), "os": "LINUX", "source": "AUDITD",
|
|
28
|
+
"category": "COMMAND", "action": "exec", "actorType": "HUMAN", "actorConfidence": 90}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _sess(key: str, started_ago: float, ended_ago: float | None = None) -> dict:
|
|
32
|
+
s = {"externalKey": key, "kind": "SSH", "actorType": "HUMAN", "actorConfidence": 90,
|
|
33
|
+
"startedAt": _iso(started_ago)}
|
|
34
|
+
if ended_ago is not None:
|
|
35
|
+
s["endedAt"] = _iso(ended_ago)
|
|
36
|
+
return s
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def spool(tmp_path):
|
|
41
|
+
s = Spool(tmp_path / "live.db")
|
|
42
|
+
yield s
|
|
43
|
+
s.close()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.fixture(autouse=True)
|
|
47
|
+
def hermetic(monkeypatch):
|
|
48
|
+
import sdk.config as cfg
|
|
49
|
+
monkeypatch.setattr(cfg, "_dotenv_value", lambda key: "")
|
|
50
|
+
monkeypatch.delenv("AGENTOS_ACTIVITY_MAX_AGE_S", raising=False)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestStaleBacklogIsDiscarded:
|
|
54
|
+
def test_events_older_than_an_hour_are_dropped(self, spool):
|
|
55
|
+
spool.add_event(_ev("days-old", 3 * 24 * 60))
|
|
56
|
+
spool.add_event(_ev("61-min", 61))
|
|
57
|
+
spool.add_event(_ev("59-min", 59))
|
|
58
|
+
spool.add_event(_ev("now", 0))
|
|
59
|
+
assert shipper.purge_stale_rows(spool, now=NOW) == 2
|
|
60
|
+
assert sorted(e["eventId"] for e in spool.next_chunk().events) == ["59-min", "now"]
|
|
61
|
+
|
|
62
|
+
def test_an_open_session_is_kept_however_old(self, spool):
|
|
63
|
+
spool.add_session(_sess("still-logged-in", started_ago=600))
|
|
64
|
+
spool.add_session(_sess("ended-long-ago", started_ago=600, ended_ago=300))
|
|
65
|
+
spool.add_session(_sess("ended-just-now", started_ago=600, ended_ago=5))
|
|
66
|
+
assert shipper.purge_stale_rows(spool, now=NOW) == 1
|
|
67
|
+
assert sorted(s["externalKey"] for s in spool.next_chunk().sessions) == [
|
|
68
|
+
"ended-just-now", "still-logged-in"]
|
|
69
|
+
|
|
70
|
+
def test_the_cut_off_is_configurable(self, spool, monkeypatch):
|
|
71
|
+
monkeypatch.setenv("AGENTOS_ACTIVITY_MAX_AGE_S", "86400")
|
|
72
|
+
spool.add_event(_ev("3h", 180))
|
|
73
|
+
spool.add_event(_ev("2d", 2 * 24 * 60))
|
|
74
|
+
assert shipper.purge_stale_rows(spool, now=NOW) == 1
|
|
75
|
+
assert [e["eventId"] for e in spool.next_chunk().events] == ["3h"]
|
|
76
|
+
|
|
77
|
+
def test_zero_keeps_everything(self, spool, monkeypatch):
|
|
78
|
+
monkeypatch.setenv("AGENTOS_ACTIVITY_MAX_AGE_S", "0")
|
|
79
|
+
spool.add_event(_ev("ancient", 10 * 24 * 60))
|
|
80
|
+
assert shipper.purge_stale_rows(spool, now=NOW) == 0
|
|
81
|
+
assert spool.count() == 1
|
|
82
|
+
|
|
83
|
+
def test_python_fallback_matches_json1(self, spool, monkeypatch):
|
|
84
|
+
"""SQLite without JSON1 filters in Python, to the same result."""
|
|
85
|
+
import sqlite3
|
|
86
|
+
|
|
87
|
+
spool.add_event(_ev("old", 120))
|
|
88
|
+
spool.add_event(_ev("new", 1))
|
|
89
|
+
real = spool._db
|
|
90
|
+
|
|
91
|
+
class NoJson:
|
|
92
|
+
def execute(self, sql, *a):
|
|
93
|
+
if "json_extract" in sql:
|
|
94
|
+
raise sqlite3.OperationalError("no such function: json_extract")
|
|
95
|
+
return real.execute(sql, *a)
|
|
96
|
+
|
|
97
|
+
def executemany(self, sql, *a):
|
|
98
|
+
return real.executemany(sql, *a)
|
|
99
|
+
monkeypatch.setattr(spool, "_db", NoJson())
|
|
100
|
+
assert shipper.purge_stale_rows(spool, now=NOW) == 1
|
|
101
|
+
monkeypatch.setattr(spool, "_db", real)
|
|
102
|
+
assert [e["eventId"] for e in spool.next_chunk().events] == ["new"]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestNewestFirst:
|
|
106
|
+
def test_a_backlog_never_holds_back_the_current_events(self, spool):
|
|
107
|
+
for i in range(1200): # a flood, oldest first
|
|
108
|
+
spool.add_event(_ev(f"old{i}", 50 - i * 0.01))
|
|
109
|
+
spool.add_event(_ev("current", 0))
|
|
110
|
+
first = spool.next_chunk(max_events=500)
|
|
111
|
+
assert first.events[0]["eventId"] == "current"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestShipTickPurgesFirst:
|
|
115
|
+
def test_stale_rows_are_gone_before_anything_ships(self, tmp_path, monkeypatch):
|
|
116
|
+
s = Spool(tmp_path / "tick.db")
|
|
117
|
+
s.add_event(_ev("stale", 120))
|
|
118
|
+
s.add_event(_ev("fresh", 1))
|
|
119
|
+
monkeypatch.setattr(settings, "get_spool", lambda: s)
|
|
120
|
+
monkeypatch.setattr(time, "time", lambda: NOW)
|
|
121
|
+
seen = []
|
|
122
|
+
|
|
123
|
+
async def fake_flush():
|
|
124
|
+
seen.extend(e["eventId"] for e in s.next_chunk().events)
|
|
125
|
+
return shipper.FlushResult()
|
|
126
|
+
monkeypatch.setattr(shipper, "flush_once", fake_flush)
|
|
127
|
+
|
|
128
|
+
async def yes():
|
|
129
|
+
return True
|
|
130
|
+
asyncio.run(shipper.ship_tick(yes))
|
|
131
|
+
s.close()
|
|
132
|
+
assert seen == ["fresh"]
|
|
133
|
+
|
|
134
|
+
def test_purge_also_runs_when_vps_identity_is_unknown(self, tmp_path, monkeypatch):
|
|
135
|
+
s = Spool(tmp_path / "tick2.db")
|
|
136
|
+
s.add_event(_ev("stale", 120))
|
|
137
|
+
monkeypatch.setattr(settings, "get_spool", lambda: s)
|
|
138
|
+
monkeypatch.setattr(time, "time", lambda: NOW)
|
|
139
|
+
|
|
140
|
+
async def unknown():
|
|
141
|
+
return None
|
|
142
|
+
asyncio.run(shipper.ship_tick(unknown))
|
|
143
|
+
n = s.count()
|
|
144
|
+
s.close()
|
|
145
|
+
assert n == 0
|
|
@@ -31,8 +31,15 @@ def _isolated(tmp_path, monkeypatch):
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def _queued():
|
|
34
|
+
"""Queued sessions and events in the order the collector RECORDED them.
|
|
35
|
+
|
|
36
|
+
The shipper sends newest-first (live tracking); these tests are about what a
|
|
37
|
+
collector recorded and in which order, so read the rows back by ``seq``.
|
|
38
|
+
"""
|
|
34
39
|
chunk = settings.get_spool().next_chunk()
|
|
35
|
-
|
|
40
|
+
rows = sorted(chunk.rows, key=lambda r: r.seq)
|
|
41
|
+
return ([r.wire for r in rows if r.kind == "session"],
|
|
42
|
+
[r.wire for r in rows if r.kind == "event"])
|
|
36
43
|
|
|
37
44
|
|
|
38
45
|
USERS = {501: "alice", 502: "agentos", 0: "root"}
|
|
@@ -83,10 +83,10 @@ class TestSpool:
|
|
|
83
83
|
|
|
84
84
|
spool.add_event(_ev("E1", metadata={"blob": "x" * (MAX_DICT_FIELD_BYTES * 2)}))
|
|
85
85
|
spool.add_event(_ev("E2", metadata={"small": 1}))
|
|
86
|
-
events = spool.next_chunk().events
|
|
87
|
-
assert events[
|
|
88
|
-
assert events[
|
|
89
|
-
assert events[
|
|
86
|
+
events = {e["eventId"]: e for e in spool.next_chunk().events}
|
|
87
|
+
assert events["E1"]["metadata"]["truncated"] is True
|
|
88
|
+
assert events["E1"]["metadata"]["originalBytes"] > MAX_DICT_FIELD_BYTES
|
|
89
|
+
assert events["E2"]["metadata"] == {"small": 1}
|
|
90
90
|
|
|
91
91
|
def test_a_single_worst_case_row_fits_comfortably_in_one_request(self, spool):
|
|
92
92
|
from sdk.activity.models import MAX_DICT_FIELD_BYTES
|
|
@@ -105,21 +105,23 @@ class TestSpool:
|
|
|
105
105
|
|
|
106
106
|
assert 1_000_000 < spool_mod.MAX_CHUNK_BYTES < 10 * 1024 * 1024
|
|
107
107
|
|
|
108
|
-
def
|
|
108
|
+
def test_sessions_first_then_the_newest_events(self, spool):
|
|
109
|
+
"""Live tracking: the latest activity ships first. Sessions still lead, so an
|
|
110
|
+
event never reaches the relay ahead of the session it references."""
|
|
109
111
|
spool.add_session(_sess("S1"))
|
|
110
112
|
spool.add_event(_ev("E1"))
|
|
111
113
|
spool.add_event(_ev("E2"))
|
|
112
114
|
chunk = spool.next_chunk()
|
|
113
115
|
assert [r.kind for r in chunk.rows] == ["session", "event", "event"]
|
|
114
|
-
assert [e["eventId"] for e in chunk.events] == ["
|
|
116
|
+
assert [e["eventId"] for e in chunk.events] == ["E2", "E1"]
|
|
115
117
|
|
|
116
118
|
def test_next_chunk_respects_the_event_limit_and_leaves_the_rest(self, spool):
|
|
117
119
|
for i in range(5):
|
|
118
120
|
spool.add_event(_ev(f"E{i}"))
|
|
119
121
|
chunk = spool.next_chunk(max_events=2)
|
|
120
|
-
assert [e["eventId"] for e in chunk.events] == ["
|
|
122
|
+
assert [e["eventId"] for e in chunk.events] == ["E4", "E3"]
|
|
121
123
|
spool.ack(chunk)
|
|
122
|
-
assert [e["eventId"] for e in spool.next_chunk(max_events=10).events] == ["E2", "
|
|
124
|
+
assert [e["eventId"] for e in spool.next_chunk(max_events=10).events] == ["E2", "E1", "E0"]
|
|
123
125
|
|
|
124
126
|
def test_next_chunk_respects_the_byte_limit(self, spool):
|
|
125
127
|
for i in range(10):
|
|
@@ -169,7 +171,7 @@ class TestSpool:
|
|
|
169
171
|
for i in range(5):
|
|
170
172
|
s.add_event(_ev(f"E{i}"))
|
|
171
173
|
assert s.count() == 3
|
|
172
|
-
assert [e["eventId"] for e in s.next_chunk().events] == ["
|
|
174
|
+
assert [e["eventId"] for e in s.next_chunk().events] == ["E4", "E3", "E2"]
|
|
173
175
|
finally:
|
|
174
176
|
s.close()
|
|
175
177
|
|
|
@@ -179,7 +181,7 @@ class TestSpool:
|
|
|
179
181
|
spool.add_event(_ev(f"E{i}"))
|
|
180
182
|
left, right = spool.next_chunk().split()
|
|
181
183
|
assert [r.kind for r in left.rows] == ["session", "event"]
|
|
182
|
-
assert [e["eventId"] for e in right.events] == ["
|
|
184
|
+
assert [e["eventId"] for e in right.events] == ["E2", "E1", "E0"]
|
|
183
185
|
|
|
184
186
|
|
|
185
187
|
class FakeSender:
|
|
@@ -355,7 +357,8 @@ class TestSettings:
|
|
|
355
357
|
def test_defaults(self):
|
|
356
358
|
assert settings.enabled() is True
|
|
357
359
|
assert settings.audit_enabled() is False
|
|
358
|
-
assert settings.ship_interval_seconds() ==
|
|
360
|
+
assert settings.ship_interval_seconds() == 60.0
|
|
361
|
+
assert settings.max_age_seconds() == 3600.0
|
|
359
362
|
|
|
360
363
|
def test_can_be_disabled(self, monkeypatch):
|
|
361
364
|
monkeypatch.setenv("AGENTOS_ACTIVITY", "off")
|
|
@@ -373,7 +376,7 @@ class TestSettings:
|
|
|
373
376
|
|
|
374
377
|
def test_garbage_interval_falls_back_to_default(self, monkeypatch):
|
|
375
378
|
monkeypatch.setenv("AGENTOS_ACTIVITY_SHIP_INTERVAL_S", "often")
|
|
376
|
-
assert settings.ship_interval_seconds() ==
|
|
379
|
+
assert settings.ship_interval_seconds() == settings.DEFAULT_SHIP_INTERVAL_S
|
|
377
380
|
|
|
378
381
|
def test_start_is_a_noop_when_disabled(self, monkeypatch):
|
|
379
382
|
monkeypatch.setenv("AGENTOS_ACTIVITY", "0")
|
|
@@ -438,8 +441,9 @@ class TestRecorder:
|
|
|
438
441
|
recorder.agent_run_finished(RUN, ok=False, detail="boom")
|
|
439
442
|
sessions, events = _queued()
|
|
440
443
|
assert len(sessions) == 1 and "endedAt" in sessions[0]
|
|
441
|
-
|
|
442
|
-
assert
|
|
444
|
+
by_action = {e["action"]: e for e in events} # shipped newest-first
|
|
445
|
+
assert sorted(by_action) == ["run.end", "run.start"]
|
|
446
|
+
assert by_action["run.end"]["exitCode"] == 1 and by_action["run.end"]["severity"] == "WARNING"
|
|
443
447
|
|
|
444
448
|
def test_a_non_uuid_agent_id_is_omitted_not_sent(self):
|
|
445
449
|
recorder.agent_run_started(RUN, agent_id="nightly-report")
|
|
File without changes
|
{devops_bot_sdk-1.6.70 → devops_bot_sdk-1.6.71}/devops_bot_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|