devops-bot-sdk 1.6.49__tar.gz → 1.6.52__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.49 → devops_bot_sdk-1.6.52}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/devops_bot_sdk.egg-info/SOURCES.txt +2 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/collectors/linux_auditd.py +31 -2
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/server.py +6 -1
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/service.py +69 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/setup_manager.py +3 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/updater.py +31 -4
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/vps_health.py +17 -1
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_activity_collectors.py +62 -0
- devops_bot_sdk-1.6.52/tests/test_stale_panel_restart.py +93 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_ui_lifespan.py +3 -1
- devops_bot_sdk-1.6.52/tests/test_updater_pep668.py +53 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_vps_health.py +11 -2
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/README.md +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/tests/test_write_hook.py +0 -0
|
@@ -201,6 +201,7 @@ tests/test_schedule_outputs_and_plans.py
|
|
|
201
201
|
tests/test_scheduler.py
|
|
202
202
|
tests/test_self_report.py
|
|
203
203
|
tests/test_service_token_warning.py
|
|
204
|
+
tests/test_stale_panel_restart.py
|
|
204
205
|
tests/test_swagger_command.py
|
|
205
206
|
tests/test_task_file_and_plan_run.py
|
|
206
207
|
tests/test_task_file_format_doc.py
|
|
@@ -212,6 +213,7 @@ tests/test_ui_gate.py
|
|
|
212
213
|
tests/test_ui_lifespan.py
|
|
213
214
|
tests/test_update_timer.py
|
|
214
215
|
tests/test_updater_extras.py
|
|
216
|
+
tests/test_updater_pep668.py
|
|
215
217
|
tests/test_updater_reports_the_truth.py
|
|
216
218
|
tests/test_url_access.py
|
|
217
219
|
tests/test_vps_health.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.52
|
|
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.52" # 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
|
|
@@ -52,6 +52,11 @@ DEFAULT_AUDIT_LOG = "/var/log/audit/audit.log"
|
|
|
52
52
|
_UNSET = {"4294967295", "-1", "unset"}
|
|
53
53
|
#: Records of one event share a serial; bound how many half-seen events we hold.
|
|
54
54
|
_MAX_PENDING = 512
|
|
55
|
+
#: An event whose records stopped arriving this long ago is complete. auditd 4.x
|
|
56
|
+
#: (Ubuntu 26.04) writes NO `EOE` records at all, so this, `PROCTITLE` and the
|
|
57
|
+
#: single-record user-space types are what close an event there. Mirrors auparse's
|
|
58
|
+
#: own `eoe_timeout` (2 s by default).
|
|
59
|
+
EOE_TIMEOUT_S = 2.0
|
|
55
60
|
_MAX_PID_CACHE = 20_000
|
|
56
61
|
#: ``acct`` values on a failed login that are placeholders, not an account name.
|
|
57
62
|
_NOT_A_NAME = ("(unknown)", "(invalid user)", "?")
|
|
@@ -139,6 +144,7 @@ class AuditdParser:
|
|
|
139
144
|
self._user_name = user_name
|
|
140
145
|
self._book = book or SessionBook()
|
|
141
146
|
self._pending: OrderedDict[str, dict[str, dict[str, str]]] = OrderedDict()
|
|
147
|
+
self._pending_ts: dict[str, float] = {} # serial → event time
|
|
142
148
|
self._ppid: OrderedDict[int, int] = OrderedDict()
|
|
143
149
|
self._ses_key: dict[str, str] = {} # auditd ses id → our session key
|
|
144
150
|
self._ses_kind: dict[str, SessionKind] = {}
|
|
@@ -177,6 +183,15 @@ class AuditdParser:
|
|
|
177
183
|
rec["_args"] = " ".join(args[i] for i in sorted(args))
|
|
178
184
|
|
|
179
185
|
self._pending.setdefault(serial, {})[rtype] = rec
|
|
186
|
+
self._pending_ts.setdefault(serial, ts)
|
|
187
|
+
# Close the event as soon as it is known to be complete, rather than only on
|
|
188
|
+
# EOE: auditd 4.x never writes EOE, and an event held for one waited for up to
|
|
189
|
+
# 512 later events (a quiet box shipped nothing; a restart lost what was held).
|
|
190
|
+
if rtype.startswith("USER_") or rtype == "PROCTITLE":
|
|
191
|
+
# USER_* come from user space as one self-contained record; PROCTITLE is
|
|
192
|
+
# the kernel's last record of a syscall event.
|
|
193
|
+
self._finalize(serial)
|
|
194
|
+
self.flush_older_than(ts - EOE_TIMEOUT_S)
|
|
180
195
|
while len(self._pending) > _MAX_PENDING:
|
|
181
196
|
self._finalize(next(iter(self._pending)))
|
|
182
197
|
|
|
@@ -185,10 +200,21 @@ class AuditdParser:
|
|
|
185
200
|
for serial in list(self._pending):
|
|
186
201
|
self._finalize(serial)
|
|
187
202
|
|
|
203
|
+
def flush_older_than(self, cutoff: float) -> None:
|
|
204
|
+
"""Finalize events whose time is before ``cutoff`` — their records are all in.
|
|
205
|
+
|
|
206
|
+
Called with the newest event time on every record, and with the wall clock by
|
|
207
|
+
the tail loop when the log is idle, so the last event before a quiet spell is
|
|
208
|
+
not held until the next one arrives.
|
|
209
|
+
"""
|
|
210
|
+
for serial in [s for s, t in self._pending_ts.items() if t < cutoff]:
|
|
211
|
+
self._finalize(serial)
|
|
212
|
+
|
|
188
213
|
# ── one event ────────────────────────────────────────────────────────────
|
|
189
214
|
|
|
190
215
|
def _finalize(self, serial: str) -> None:
|
|
191
216
|
recs = self._pending.pop(serial, None)
|
|
217
|
+
self._pending_ts.pop(serial, None)
|
|
192
218
|
if not recs:
|
|
193
219
|
return
|
|
194
220
|
try:
|
|
@@ -396,9 +422,12 @@ class AuditdCollector:
|
|
|
396
422
|
for raw in data[:cut].splitlines():
|
|
397
423
|
self._parser.feed(raw.decode("utf-8", "replace"))
|
|
398
424
|
offset += cut
|
|
399
|
-
# No parser.flush() here:
|
|
400
|
-
#
|
|
425
|
+
# No parser.flush() here: flushing mid-read could finalize an event whose
|
|
426
|
+
# lines straddle two reads. Only events older than EOE_TIMEOUT_S are
|
|
427
|
+
# closed below — their records are all in the log by then.
|
|
401
428
|
self._save(inode, offset)
|
|
429
|
+
# Wall-clock close for the last event before a quiet spell (no EOE on auditd 4.x).
|
|
430
|
+
self._parser.flush_older_than(time.time() - EOE_TIMEOUT_S)
|
|
402
431
|
time.sleep(self._poll_s)
|
|
403
432
|
|
|
404
433
|
def _load(self) -> tuple[int | None, int]:
|
|
@@ -140,7 +140,12 @@ def create_app():
|
|
|
140
140
|
|
|
141
141
|
@app.get("/healthz")
|
|
142
142
|
async def healthz():
|
|
143
|
-
|
|
143
|
+
# `version` is what the updater compares against the installed SDK: a panel
|
|
144
|
+
# started before an upgrade keeps running the old code until it restarts,
|
|
145
|
+
# and "already up to date" on disk says nothing about the running process.
|
|
146
|
+
from sdk import __version__ as _v
|
|
147
|
+
|
|
148
|
+
return {"ok": True, "version": _v}
|
|
144
149
|
|
|
145
150
|
# ── Swagger: public shell, token-gated schema ───────────────────────────────
|
|
146
151
|
# Registered HERE, before the static mount below: Starlette matches in insertion
|
|
@@ -678,6 +678,71 @@ def refresh() -> tuple[bool, str]:
|
|
|
678
678
|
else f"service definition regenerated ({host}:{port}) but restart needs attention: {rmsg}")
|
|
679
679
|
|
|
680
680
|
|
|
681
|
+
def running_version(timeout: float = 3.0) -> str | None:
|
|
682
|
+
"""The SDK version the RUNNING panel reports, from its own ``/healthz``.
|
|
683
|
+
|
|
684
|
+
``""`` when it answers without a version (a panel older than this check: stale
|
|
685
|
+
by definition), ``None`` when it cannot be reached (unknown: do not act on it).
|
|
686
|
+
Loopback unless the panel was bound to one specific address.
|
|
687
|
+
"""
|
|
688
|
+
import json
|
|
689
|
+
import urllib.request
|
|
690
|
+
|
|
691
|
+
host, port = _installed_host_port()
|
|
692
|
+
if host in ("0.0.0.0", "::", ""):
|
|
693
|
+
host = "127.0.0.1"
|
|
694
|
+
url = f"http://{'[' + host + ']' if ':' in host else host}:{port}/healthz"
|
|
695
|
+
try:
|
|
696
|
+
with urllib.request.urlopen(url, timeout=timeout) as resp: # noqa: S310 — loopback/own panel
|
|
697
|
+
body = json.loads(resp.read().decode("utf-8", "replace") or "{}")
|
|
698
|
+
except Exception: # noqa: BLE001 — not up yet, other port, IP gate …
|
|
699
|
+
return None
|
|
700
|
+
return str(body.get("version") or "")
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def _wait_for_version(want: str, attempts: int = 10, delay: float = 2.0) -> str | None:
|
|
704
|
+
"""Poll the restarted panel until it answers; its version, or None if it never did."""
|
|
705
|
+
import time
|
|
706
|
+
|
|
707
|
+
seen: str | None = None
|
|
708
|
+
for _ in range(attempts):
|
|
709
|
+
seen = running_version()
|
|
710
|
+
if seen == want:
|
|
711
|
+
return seen
|
|
712
|
+
time.sleep(delay)
|
|
713
|
+
return seen
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _restart_if_stale() -> tuple[bool, str] | None:
|
|
717
|
+
"""Restart a running panel whose code is older than the installed SDK.
|
|
718
|
+
|
|
719
|
+
The gap this closes: when ``agentos-auto update`` finds the disk already current
|
|
720
|
+
it never restarted the panel, so a panel started BEFORE the upgrade landed (by
|
|
721
|
+
an earlier update run, a manual pip install, a second install) kept running the
|
|
722
|
+
old code indefinitely. On a VPS that meant the activity collector never started.
|
|
723
|
+
``None`` when there is nothing to do (current, or the version is unknowable).
|
|
724
|
+
"""
|
|
725
|
+
from sdk import __version__ as installed
|
|
726
|
+
|
|
727
|
+
running = running_version()
|
|
728
|
+
if running is None or running == installed:
|
|
729
|
+
return None
|
|
730
|
+
was = running or "a version older than 1.6.50"
|
|
731
|
+
rok, rmsg = refresh()
|
|
732
|
+
if not rok:
|
|
733
|
+
return False, f"panel runs {was} but {installed} is installed; restart failed: {rmsg}"
|
|
734
|
+
now = _wait_for_version(installed)
|
|
735
|
+
if now == installed:
|
|
736
|
+
return True, f"panel was running {was}; restarted on {installed}."
|
|
737
|
+
# Restarted, and STILL not this version: the service runs another installation
|
|
738
|
+
# (a different interpreter / prefix) that this updater did not touch.
|
|
739
|
+
unit = _service_definition_path()
|
|
740
|
+
return False, (f"panel restarted but reports {now or 'no version'}, not {installed}: the "
|
|
741
|
+
f"service runs a different SDK installation than this one "
|
|
742
|
+
f"({sys.executable}). Check the service definition"
|
|
743
|
+
f"{f' ({unit})' if unit else ''} and update that installation.")
|
|
744
|
+
|
|
745
|
+
|
|
681
746
|
def _service_disabled() -> bool:
|
|
682
747
|
return os.environ.get("AGENTOS_NO_SERVICE", "").strip().lower() in ("1", "true", "yes", "on")
|
|
683
748
|
|
|
@@ -719,6 +784,10 @@ def ensure(restart_running: bool = False) -> tuple[bool, str]:
|
|
|
719
784
|
rok, rmsg = refresh()
|
|
720
785
|
return rok, (_with_token_warning(rmsg) if rok else rmsg)
|
|
721
786
|
if _is_active():
|
|
787
|
+
stale = _restart_if_stale()
|
|
788
|
+
if stale is not None:
|
|
789
|
+
ok, msg = stale
|
|
790
|
+
return ok, (_with_token_warning(msg) if ok else msg)
|
|
722
791
|
return True, _with_token_warning("service already running.")
|
|
723
792
|
rok, rmsg = restart() # installed but down → bring it back up
|
|
724
793
|
return rok, (_with_token_warning(f"service was down — started; {rmsg}") if rok else rmsg)
|
|
@@ -102,9 +102,12 @@ async def reinstall_sdk(upgrade: bool = True):
|
|
|
102
102
|
which is the managed ``agentsystem`` venv in normal operation. Used by
|
|
103
103
|
``/setup`` for a repair/reinstall.
|
|
104
104
|
"""
|
|
105
|
+
from sdk.updater import _pip_flags
|
|
106
|
+
|
|
105
107
|
argv = [sys.executable, "-m", "pip", "install"]
|
|
106
108
|
if upgrade:
|
|
107
109
|
argv.append("--upgrade")
|
|
110
|
+
argv += _pip_flags()
|
|
108
111
|
argv.append(SDK_GIT_SPEC)
|
|
109
112
|
|
|
110
113
|
yield f"$ {' '.join(argv)}\n"
|
|
@@ -8,7 +8,8 @@ Upgrade priority:
|
|
|
8
8
|
(backend returns the user's stored GitHub OAuth token — no extra config)
|
|
9
9
|
4. PyPI dist name as last resort
|
|
10
10
|
|
|
11
|
-
Adds --break-system-packages automatically on
|
|
11
|
+
Adds --break-system-packages automatically on any externally-managed Python
|
|
12
|
+
(PEP 668: Ubuntu 22.04+/Debian 12+, Homebrew Python on macOS).
|
|
12
13
|
Disable entirely with AGENTOS_NO_UPDATE=1.
|
|
13
14
|
"""
|
|
14
15
|
from __future__ import annotations
|
|
@@ -54,9 +55,35 @@ def _with_extras(target: str) -> str:
|
|
|
54
55
|
|
|
55
56
|
# ── Platform helpers ──────────────────────────────────────────────────────────
|
|
56
57
|
|
|
58
|
+
def _externally_managed() -> bool:
|
|
59
|
+
"""True when pip will refuse this interpreter under PEP 668.
|
|
60
|
+
|
|
61
|
+
The marker is an ``EXTERNALLY-MANAGED`` file in the stdlib directory, and a
|
|
62
|
+
venv is exempt. Checking for it, rather than the OS, is what covers Homebrew
|
|
63
|
+
Python on macOS: a Mac VPS on ``/opt/homebrew/.../python3.14`` was refused on
|
|
64
|
+
every upgrade attempt and stayed on an old version indefinitely, because the
|
|
65
|
+
flag below used to be added on Linux only.
|
|
66
|
+
"""
|
|
67
|
+
if sys.prefix != getattr(sys, "base_prefix", sys.prefix):
|
|
68
|
+
return False
|
|
69
|
+
try:
|
|
70
|
+
import sysconfig
|
|
71
|
+
return os.path.isfile(
|
|
72
|
+
os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED"))
|
|
73
|
+
except Exception: # noqa: BLE001 — an unreadable layout is not "managed"
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
57
77
|
def _pip_flags() -> list[str]:
|
|
58
|
-
"""--break-system-packages
|
|
59
|
-
|
|
78
|
+
"""--break-system-packages where PEP 668 applies; empty elsewhere.
|
|
79
|
+
|
|
80
|
+
Linux keeps the flag unconditionally, as before (every supported distro pip
|
|
81
|
+
accepts it). Elsewhere it is added only when the marker is present, since an
|
|
82
|
+
old pip that predates PEP 668 rejects the option outright.
|
|
83
|
+
"""
|
|
84
|
+
if platform.system() == "Linux" or _externally_managed():
|
|
85
|
+
return ["--break-system-packages"]
|
|
86
|
+
return []
|
|
60
87
|
|
|
61
88
|
|
|
62
89
|
def _vtuple(v: str) -> tuple:
|
|
@@ -589,7 +616,7 @@ def _report(result: UpgradeResult, latest: str | None) -> None:
|
|
|
589
616
|
_say(f" pip said: {result.detail}")
|
|
590
617
|
_say(" fix it by hand with:\n"
|
|
591
618
|
f' "{sys.executable}" -m pip install --upgrade --force-reinstall '
|
|
592
|
-
f'--no-cache-dir "{_DIST}{_EXTRAS}"')
|
|
619
|
+
f'--no-cache-dir {" ".join(_pip_flags() + [""])}"{_DIST}{_EXTRAS}"')
|
|
593
620
|
return
|
|
594
621
|
|
|
595
622
|
if result.ran:
|
|
@@ -33,6 +33,7 @@ ping just waits for the next tick.
|
|
|
33
33
|
from __future__ import annotations
|
|
34
34
|
|
|
35
35
|
import asyncio
|
|
36
|
+
import ipaddress
|
|
36
37
|
import logging
|
|
37
38
|
import os
|
|
38
39
|
import socket
|
|
@@ -120,6 +121,21 @@ def _outbound_ip() -> str:
|
|
|
120
121
|
pass
|
|
121
122
|
|
|
122
123
|
|
|
124
|
+
def _is_public(ip: str) -> bool:
|
|
125
|
+
"""Only a globally-routable address may overwrite the allocation's IP.
|
|
126
|
+
|
|
127
|
+
Behind NAT (an AWS instance, a laptop on Wi-Fi) the route-to-internet address
|
|
128
|
+
is private — ``172.31.x.x``, ``192.168.x.x`` — and sending it replaced the
|
|
129
|
+
VPS's real public IP on ``contabo_allocation``, after which the backend could
|
|
130
|
+
no longer reach the panel at all. Omitting the key leaves the stored value
|
|
131
|
+
alone, the same rule every other unavailable metric here follows.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
return bool(ip) and ipaddress.ip_address(ip).is_global
|
|
135
|
+
except ValueError:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
123
139
|
def _bandwidth_used_mb() -> float | None:
|
|
124
140
|
"""Total bytes in+out since boot, in MB. None when psutil is unavailable.
|
|
125
141
|
|
|
@@ -183,7 +199,7 @@ def build_payload(health: dict | None = None) -> dict:
|
|
|
183
199
|
payload["bandwidthUsed"] = bw # MB, cumulative
|
|
184
200
|
|
|
185
201
|
ip = _outbound_ip()
|
|
186
|
-
if ip:
|
|
202
|
+
if _is_public(ip):
|
|
187
203
|
payload["ipAddress"] = ip
|
|
188
204
|
|
|
189
205
|
return payload
|
|
@@ -647,3 +647,65 @@ class TestSetupAtPanelStart:
|
|
|
647
647
|
async def go():
|
|
648
648
|
await collectors.start()
|
|
649
649
|
asyncio.run(go())
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
class TestNoEoeRecords:
|
|
653
|
+
"""auditd 4.x (Ubuntu 26.04) writes NO `EOE` records. The parser closed events only
|
|
654
|
+
on EOE (or after 512 more piled up), so a quiet VPS shipped nothing at all and a busy
|
|
655
|
+
one shipped ~10% of its commands, late, losing whatever was held at a restart."""
|
|
656
|
+
|
|
657
|
+
# Verbatim from a live Ubuntu 26.04 / auditctl 4.1.2 VPS (log_format = ENRICHED; the
|
|
658
|
+
# enriched tail is separated by \x1d), serial 761 — no EOE follows it.
|
|
659
|
+
JAVAID_EXEC = [
|
|
660
|
+
'type=SYSCALL msg=audit(1791570760.040:761): arch=c000003e syscall=59 success=yes exit=0 '
|
|
661
|
+
'a0=7bd5038c3130 a1=7bd5038c30d0 a2=7bd5038f77b0 a3=8 items=2 ppid=1387309 pid=1388791 '
|
|
662
|
+
'auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) '
|
|
663
|
+
'ses=4294967295 comm="auditctl" exe="/usr/sbin/auditctl" subj=unconfined '
|
|
664
|
+
'key="agentos_exec"\x1dARCH=x86_64 SYSCALL=execve AUID="unset" UID="root"',
|
|
665
|
+
'type=EXECVE msg=audit(1791570760.040:761): argc=2 a0="auditctl" a1="-l"',
|
|
666
|
+
'type=CWD msg=audit(1791570760.040:761): cwd="/root"',
|
|
667
|
+
'type=PATH msg=audit(1791570760.040:761): item=0 name="/usr/sbin/auditctl" inode=60036 '
|
|
668
|
+
'dev=fd:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL',
|
|
669
|
+
'type=PROCTITLE msg=audit(1791570760.040:761): proctitle=2F7573722F7362696E2F61756469746374'
|
|
670
|
+
'6C002D6C',
|
|
671
|
+
]
|
|
672
|
+
|
|
673
|
+
def test_a_command_is_recorded_without_an_eoe_line(self):
|
|
674
|
+
p = _parser()
|
|
675
|
+
_feed(p, self.JAVAID_EXEC)
|
|
676
|
+
_, events = _queued()
|
|
677
|
+
assert [(e["action"], e["command"]) for e in events] == [("exec", "auditctl -l")]
|
|
678
|
+
|
|
679
|
+
def test_without_proctitle_the_event_closes_once_a_later_event_is_two_seconds_on(self):
|
|
680
|
+
p = _parser()
|
|
681
|
+
_feed(p, self.JAVAID_EXEC[:4]) # no PROCTITLE, no EOE
|
|
682
|
+
assert _queued()[1] == [] # may still be arriving
|
|
683
|
+
_feed(p, ['type=CWD msg=audit(1791570762.500:900): cwd="/"']) # 2.46 s later
|
|
684
|
+
_, events = _queued()
|
|
685
|
+
assert [e["command"] for e in events] == ["auditctl -l"]
|
|
686
|
+
|
|
687
|
+
def test_the_idle_tail_closes_the_last_event_by_wall_clock(self):
|
|
688
|
+
p = _parser()
|
|
689
|
+
_feed(p, self.JAVAID_EXEC[:4])
|
|
690
|
+
assert _queued()[1] == []
|
|
691
|
+
p.flush_older_than(1791570760.040 + L.EOE_TIMEOUT_S + 0.1)
|
|
692
|
+
assert [e["command"] for e in _queued()[1]] == ["auditctl -l"]
|
|
693
|
+
|
|
694
|
+
def test_a_failed_login_is_recorded_on_its_own_record(self):
|
|
695
|
+
"""USER_* records are single, self-contained user-space events: no EOE ever."""
|
|
696
|
+
p = _parser(user_name=L._default_user_name)
|
|
697
|
+
_feed(p, [
|
|
698
|
+
"type=USER_LOGIN msg=audit(1791568160.154:240124): pid=1828747 uid=0 "
|
|
699
|
+
"auid=4294967295 ses=4294967295 subj=unconfined msg='op=login "
|
|
700
|
+
"acct=\"root\" exe=\"/usr/lib/openssh/sshd-session\" "
|
|
701
|
+
"hostname=? addr=80.94.92.55 terminal=sshd res=failed'\x1dUID=\"root\" AUID=\"unset\"",
|
|
702
|
+
])
|
|
703
|
+
_, events = _queued()
|
|
704
|
+
assert [(e["action"], e["osUser"], e["target"]) for e in events] == [
|
|
705
|
+
("login.failed", "root", "80.94.92.55")]
|
|
706
|
+
|
|
707
|
+
def test_an_event_is_recorded_once_even_if_an_eoe_does_follow(self):
|
|
708
|
+
"""Older auditd still writes EOE after PROCTITLE: no double record."""
|
|
709
|
+
p = _parser()
|
|
710
|
+
_feed(p, [*self.JAVAID_EXEC, "type=EOE msg=audit(1791570760.040:761): "])
|
|
711
|
+
assert len(_queued()[1]) == 1
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""A panel started before an upgrade landed is restarted by the next update.
|
|
2
|
+
|
|
3
|
+
Regression (2026-10-09, several VPSs): `agentos-auto update` said "already up to date
|
|
4
|
+
(v1.6.49)" and `activity-setup --check` said "already set up", yet the box shipped no
|
|
5
|
+
activity — the panel had been running since before the upgrade, on old code, and
|
|
6
|
+
the "already current" path never restarted it.
|
|
7
|
+
"""
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
from sdk import __version__ as INSTALLED
|
|
11
|
+
from sdk.ui import service
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _ensure_running(monkeypatch, versions, refresh=(True, "service refreshed")):
|
|
15
|
+
"""ensure() on an installed, active service; `versions` = successive /healthz answers."""
|
|
16
|
+
answers = iter(versions)
|
|
17
|
+
monkeypatch.setattr(service, "running_version", lambda timeout=3.0: next(answers, versions[-1]))
|
|
18
|
+
monkeypatch.setattr(service, "_wait_for_version",
|
|
19
|
+
lambda want, attempts=10, delay=2.0: next(answers, versions[-1]))
|
|
20
|
+
calls = []
|
|
21
|
+
|
|
22
|
+
def fake_refresh():
|
|
23
|
+
calls.append("refresh")
|
|
24
|
+
return refresh
|
|
25
|
+
|
|
26
|
+
with patch.object(service, "_service_disabled", return_value=False), \
|
|
27
|
+
patch.object(service, "is_installed", return_value=True), \
|
|
28
|
+
patch.object(service, "update_timer_installed", return_value=True), \
|
|
29
|
+
patch.object(service, "_needs_root_for_existing", return_value=False), \
|
|
30
|
+
patch.object(service, "_is_active", return_value=True), \
|
|
31
|
+
patch.object(service, "_with_token_warning", side_effect=lambda m: m), \
|
|
32
|
+
patch.object(service, "refresh", side_effect=fake_refresh):
|
|
33
|
+
ok, msg = service.ensure()
|
|
34
|
+
return ok, msg, calls
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_a_current_panel_is_left_alone(monkeypatch):
|
|
38
|
+
ok, msg, calls = _ensure_running(monkeypatch, [INSTALLED])
|
|
39
|
+
assert calls == [] and ok and msg == "service already running."
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_an_older_panel_is_restarted_onto_the_installed_version(monkeypatch):
|
|
43
|
+
ok, msg, calls = _ensure_running(monkeypatch, ["1.6.44", INSTALLED])
|
|
44
|
+
assert calls == ["refresh"] and ok
|
|
45
|
+
assert "1.6.44" in msg and INSTALLED in msg
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_a_panel_too_old_to_report_a_version_is_stale(monkeypatch):
|
|
49
|
+
"""Panels before this fix answer /healthz with no version at all."""
|
|
50
|
+
ok, msg, calls = _ensure_running(monkeypatch, ["", INSTALLED])
|
|
51
|
+
assert calls == ["refresh"] and ok
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_an_unreachable_panel_is_never_restarted_on_a_guess(monkeypatch):
|
|
55
|
+
ok, msg, calls = _ensure_running(monkeypatch, [None])
|
|
56
|
+
assert calls == [] and msg == "service already running."
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_still_old_after_restart_names_the_second_installation(monkeypatch):
|
|
60
|
+
"""The VPS case where the service ran /usr/local python3.12 and the update had
|
|
61
|
+
upgraded a different install: say so instead of claiming success."""
|
|
62
|
+
ok, msg, calls = _ensure_running(monkeypatch, ["1.6.44", "1.6.44"])
|
|
63
|
+
assert calls == ["refresh"] and ok is False
|
|
64
|
+
assert "different SDK installation" in msg
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_a_failed_restart_is_reported(monkeypatch):
|
|
68
|
+
ok, msg, calls = _ensure_running(monkeypatch, ["1.6.44"], refresh=(False, "permission denied"))
|
|
69
|
+
assert ok is False and "permission denied" in msg
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_the_probe_reads_the_version_from_healthz(monkeypatch):
|
|
73
|
+
import io
|
|
74
|
+
import json
|
|
75
|
+
|
|
76
|
+
seen = {}
|
|
77
|
+
|
|
78
|
+
class Resp(io.BytesIO):
|
|
79
|
+
def __enter__(self):
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def __exit__(self, *a):
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
def fake_urlopen(url, timeout):
|
|
86
|
+
seen["url"] = url
|
|
87
|
+
return Resp(json.dumps({"ok": True, "version": "9.9.9"}).encode())
|
|
88
|
+
|
|
89
|
+
monkeypatch.undo() # drop conftest's running_version stub: test the real probe
|
|
90
|
+
monkeypatch.setattr(service, "_installed_host_port", lambda: ("0.0.0.0", 8765))
|
|
91
|
+
monkeypatch.setattr("urllib.request.urlopen", fake_urlopen)
|
|
92
|
+
assert service.running_version() == "9.9.9"
|
|
93
|
+
assert seen["url"] == "http://127.0.0.1:8765/healthz"
|
|
@@ -99,4 +99,6 @@ class TestLifespanCrons:
|
|
|
99
99
|
# Entering the context manager runs the REAL lifespan, so a cron that
|
|
100
100
|
# raises there would surface as a failed startup rather than a served page.
|
|
101
101
|
with TestClient(create_app()) as c:
|
|
102
|
-
|
|
102
|
+
from sdk import __version__
|
|
103
|
+
# `version` lets the updater spot a panel still running pre-upgrade code.
|
|
104
|
+
assert c.get("/healthz").json() == {"ok": True, "version": __version__}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""PEP 668 is a property of the interpreter, not of Linux.
|
|
2
|
+
|
|
3
|
+
Homebrew Python on macOS ships an EXTERNALLY-MANAGED marker, and the updater
|
|
4
|
+
used to add --break-system-packages on Linux only — so a Mac VPS was refused on
|
|
5
|
+
every upgrade path and stayed on an old SDK indefinitely.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from sdk import updater
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _stdlib(monkeypatch, tmp_path, *, marker: bool, venv: bool = False):
|
|
15
|
+
import sysconfig
|
|
16
|
+
if marker:
|
|
17
|
+
(tmp_path / "EXTERNALLY-MANAGED").write_text("[externally-managed]\n")
|
|
18
|
+
monkeypatch.setattr(sysconfig, "get_path", lambda name: str(tmp_path))
|
|
19
|
+
monkeypatch.setattr(sys, "base_prefix", "/base" if venv else sys.prefix)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_homebrew_macos_gets_the_flag(monkeypatch, tmp_path):
|
|
23
|
+
monkeypatch.setattr(updater.platform, "system", lambda: "Darwin")
|
|
24
|
+
_stdlib(monkeypatch, tmp_path, marker=True)
|
|
25
|
+
assert updater._pip_flags() == ["--break-system-packages"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_unmanaged_macos_python_does_not(monkeypatch, tmp_path):
|
|
29
|
+
"""An old pip that predates PEP 668 rejects the option outright."""
|
|
30
|
+
monkeypatch.setattr(updater.platform, "system", lambda: "Darwin")
|
|
31
|
+
_stdlib(monkeypatch, tmp_path, marker=False)
|
|
32
|
+
assert updater._pip_flags() == []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_a_venv_is_exempt(monkeypatch, tmp_path):
|
|
36
|
+
monkeypatch.setattr(updater.platform, "system", lambda: "Windows")
|
|
37
|
+
_stdlib(monkeypatch, tmp_path, marker=True, venv=True)
|
|
38
|
+
assert updater._pip_flags() == []
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_linux_keeps_the_flag_unconditionally(monkeypatch, tmp_path):
|
|
42
|
+
monkeypatch.setattr(updater.platform, "system", lambda: "Linux")
|
|
43
|
+
_stdlib(monkeypatch, tmp_path, marker=False)
|
|
44
|
+
assert updater._pip_flags() == ["--break-system-packages"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_the_hand_fix_hint_carries_the_flag(monkeypatch, tmp_path, capsys):
|
|
48
|
+
monkeypatch.setattr(updater.platform, "system", lambda: "Darwin")
|
|
49
|
+
_stdlib(monkeypatch, tmp_path, marker=True)
|
|
50
|
+
res = updater.UpgradeResult(before="1.6.44", after="1.6.44", changed=False,
|
|
51
|
+
ran=True, detail="externally-managed-environment")
|
|
52
|
+
updater._report(res, latest="1.6.51")
|
|
53
|
+
assert "--break-system-packages" in capsys.readouterr().out
|
|
@@ -73,7 +73,7 @@ class TestBuildPayload:
|
|
|
73
73
|
def test_maps_every_field(self, monkeypatch):
|
|
74
74
|
monkeypatch.delenv("AGENTOS_VPS_HEALTH_STATUS", raising=False)
|
|
75
75
|
monkeypatch.setattr(vps_health, "_bandwidth_used_mb", lambda: 1024.5)
|
|
76
|
-
monkeypatch.setattr(vps_health, "_outbound_ip", lambda: "
|
|
76
|
+
monkeypatch.setattr(vps_health, "_outbound_ip", lambda: "77.68.16.238")
|
|
77
77
|
|
|
78
78
|
p = vps_health.build_payload(_HEALTH)
|
|
79
79
|
|
|
@@ -85,7 +85,7 @@ class TestBuildPayload:
|
|
|
85
85
|
assert p["diskUsage"] == 30.2
|
|
86
86
|
assert p["totalDisk"] == 500.0
|
|
87
87
|
assert p["bandwidthUsed"] == 1024.5
|
|
88
|
-
assert p["ipAddress"] == "
|
|
88
|
+
assert p["ipAddress"] == "77.68.16.238"
|
|
89
89
|
assert p["uptimeSeconds"] == 86400
|
|
90
90
|
# ISO-8601 with millis + Z, as the backend sample shows.
|
|
91
91
|
assert p["lastPingedAt"].endswith("Z") and "T" in p["lastPingedAt"]
|
|
@@ -118,6 +118,15 @@ class TestBuildPayload:
|
|
|
118
118
|
"totalDisk", "uptimeSeconds", "bandwidthUsed", "ipAddress"):
|
|
119
119
|
assert absent not in p
|
|
120
120
|
|
|
121
|
+
@pytest.mark.parametrize("ip", ["172.31.44.234", "192.168.18.16", "10.0.0.5",
|
|
122
|
+
"127.0.0.1", "not-an-ip"])
|
|
123
|
+
def test_a_private_ip_never_overwrites_the_allocation(self, monkeypatch, ip):
|
|
124
|
+
"""Behind NAT the outbound address is private; sending it replaced the
|
|
125
|
+
VPS's public IP and left the backend unable to reach the panel."""
|
|
126
|
+
monkeypatch.setattr(vps_health, "_bandwidth_used_mb", lambda: None)
|
|
127
|
+
monkeypatch.setattr(vps_health, "_outbound_ip", lambda: ip)
|
|
128
|
+
assert "ipAddress" not in vps_health.build_payload(_HEALTH)
|
|
129
|
+
|
|
121
130
|
|
|
122
131
|
class _FakeClient:
|
|
123
132
|
def __init__(self, ok: bool = True) -> None:
|
|
File without changes
|
{devops_bot_sdk-1.6.49 → devops_bot_sdk-1.6.52}/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
|