devops-bot-sdk 1.6.52__tar.gz → 1.6.54__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.52 → devops_bot_sdk-1.6.54}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/__init__.py +1 -1
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/collectors/macos_eslogger.py +11 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agentd.py +48 -11
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/platform_compat.py +12 -3
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/run_auto.py +63 -17
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_activity_macos.py +31 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_run_auto_agent_only.py +86 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/README.md +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/tests/test_write_hook.py +0 -0
|
@@ -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.54" # 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
|
|
@@ -167,6 +167,17 @@ class EsloggerParser:
|
|
|
167
167
|
def flush(self) -> None:
|
|
168
168
|
"""Nothing is buffered — every eslogger line is a whole event."""
|
|
169
169
|
|
|
170
|
+
def flush_older_than(self, cutoff: float) -> None:
|
|
171
|
+
"""No-op, for the same reason as :meth:`flush`: nothing is held.
|
|
172
|
+
|
|
173
|
+
:class:`EsloggerCollector` reuses :class:`AuditdCollector`'s tail loop, which
|
|
174
|
+
calls this on the parser every poll to close auditd events whose trailing
|
|
175
|
+
records never arrived. An eslogger line is already a whole event, so there is
|
|
176
|
+
nothing to time-flush — but the method MUST exist, or the shared loop raises
|
|
177
|
+
``AttributeError`` on its first idle poll and the whole collector thread dies
|
|
178
|
+
before a single event is recorded (the tail starts at EOF, so the very first
|
|
179
|
+
poll reads nothing and goes straight here)."""
|
|
180
|
+
|
|
170
181
|
# ── sessions ─────────────────────────────────────────────────────────────
|
|
171
182
|
|
|
172
183
|
def _asid_session(self, asid: int, ts: float) -> str:
|
|
@@ -199,6 +199,11 @@ def _daemon_running() -> int | None:
|
|
|
199
199
|
return None
|
|
200
200
|
|
|
201
201
|
|
|
202
|
+
#: Set in the environment right before the macOS re-exec below, read back by the
|
|
203
|
+
#: fresh interpreter so it knows it IS the detached daemon and must not fork again.
|
|
204
|
+
_DAEMONIZED_ENV = "AGENTOS_AGENTD_DAEMONIZED"
|
|
205
|
+
|
|
206
|
+
|
|
202
207
|
def _daemonize() -> bool:
|
|
203
208
|
"""Detach the current process from the terminal (POSIX double-fork).
|
|
204
209
|
|
|
@@ -207,10 +212,23 @@ def _daemonize() -> bool:
|
|
|
207
212
|
_LOG_FILE and a PID file is written so `status` can find the daemon.
|
|
208
213
|
Returns False (stays in the foreground) on platforms without os.fork
|
|
209
214
|
(e.g. Windows).
|
|
215
|
+
|
|
216
|
+
On macOS the detached child re-execs a fresh interpreter instead of running
|
|
217
|
+
the daemon in-process (see below); ``main()`` re-enters here with the
|
|
218
|
+
``_DAEMONIZED_ENV`` marker set, where setsid, the redirected fds 0/1/2 and the
|
|
219
|
+
PID file already survive the exec, so we only re-point the stream wrappers.
|
|
210
220
|
"""
|
|
211
221
|
if not hasattr(os, "fork"):
|
|
212
222
|
return False # caller falls back to foreground
|
|
213
223
|
|
|
224
|
+
if os.environ.get(_DAEMONIZED_ENV) == "1":
|
|
225
|
+
# Don't leak the one-shot marker to subprocesses the daemon later spawns
|
|
226
|
+
# (claude, git, …) — they must never think THEY are the detached daemon.
|
|
227
|
+
os.environ.pop(_DAEMONIZED_ENV, None)
|
|
228
|
+
sys.stdout = open(_LOG_FILE, "a", buffering=1, encoding="utf-8")
|
|
229
|
+
sys.stderr = sys.stdout
|
|
230
|
+
return True
|
|
231
|
+
|
|
214
232
|
_AGENTOS_DIR.mkdir(parents=True, exist_ok=True)
|
|
215
233
|
|
|
216
234
|
# First fork: let the shell reap the parent and return the prompt.
|
|
@@ -232,13 +250,27 @@ def _daemonize() -> bool:
|
|
|
232
250
|
os.close(devnull)
|
|
233
251
|
os.close(logfd)
|
|
234
252
|
|
|
253
|
+
_PID_FILE.write_text(str(os.getpid()))
|
|
254
|
+
|
|
255
|
+
# macOS: re-exec a FRESH interpreter so the daemon's networking runs in a clean
|
|
256
|
+
# process image. A bare double-fork() with NO exec() leaves Apple's higher-level
|
|
257
|
+
# frameworks (Network.framework DNS/NAT64, the libsystem_trace os_log cache)
|
|
258
|
+
# half-initialised in the child, and the first getaddrinfo on the asyncio resolver
|
|
259
|
+
# thread then SIGSEGVs ("crashed on child side of fork pre-exec"). OBJC_DISABLE_
|
|
260
|
+
# INITIALIZE_FORK_SAFETY silences only the objc guard, not these — so it is not
|
|
261
|
+
# enough on macOS 26. The exec'd process keeps this PID, setsid session and fds.
|
|
262
|
+
if sys.platform == "darwin":
|
|
263
|
+
os.environ[_DAEMONIZED_ENV] = "1"
|
|
264
|
+
try:
|
|
265
|
+
os.execv(sys.executable, [sys.executable, sys.argv[0], *sys.argv[1:]])
|
|
266
|
+
except Exception: # noqa: BLE001 — exec failed; fall back to in-process daemon
|
|
267
|
+
os.environ.pop(_DAEMONIZED_ENV, None)
|
|
268
|
+
|
|
235
269
|
# Line-buffer stdout/stderr so progress reaches the log live rather than
|
|
236
270
|
# sitting in Python's block-buffer for a non-tty stream (same fix
|
|
237
271
|
# sdk/run_auto.py applies for the same reason).
|
|
238
272
|
sys.stdout = open(_LOG_FILE, "a", buffering=1, encoding="utf-8")
|
|
239
273
|
sys.stderr = sys.stdout
|
|
240
|
-
|
|
241
|
-
_PID_FILE.write_text(str(os.getpid()))
|
|
242
274
|
return True
|
|
243
275
|
|
|
244
276
|
|
|
@@ -736,17 +768,22 @@ class AgentDaemon:
|
|
|
736
768
|
|
|
737
769
|
def start(foreground: bool = False) -> None:
|
|
738
770
|
"""Start agentd — in the background by default, attached with --foreground."""
|
|
771
|
+
# macOS re-exec of the already-detached daemon (see _daemonize): this process
|
|
772
|
+
# IS the daemon — the duplicate guard, banner and stop-flag clear all ran in
|
|
773
|
+
# the original process before the fork, so skip straight to re-detaching.
|
|
774
|
+
resuming = os.environ.get(_DAEMONIZED_ENV) == "1"
|
|
739
775
|
if not foreground:
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
776
|
+
if not resuming:
|
|
777
|
+
pid = _daemon_running()
|
|
778
|
+
if pid:
|
|
779
|
+
print(f"agentd is already running in the background (pid {pid}).")
|
|
780
|
+
print(f" logs: {_LOG_FILE}")
|
|
781
|
+
return
|
|
745
782
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
783
|
+
print("agentd starting in the background — it keeps running after you "
|
|
784
|
+
"close this terminal.")
|
|
785
|
+
print(f" logs: {_LOG_FILE}")
|
|
786
|
+
_clear_stop() # a fresh start clears any stale stop flag
|
|
750
787
|
if not _daemonize():
|
|
751
788
|
print("note: background mode unavailable on this platform — running "
|
|
752
789
|
"in the foreground.")
|
|
@@ -80,12 +80,21 @@ def ensure_utf8_stdio() -> None:
|
|
|
80
80
|
def ensure_macos_fork_safety() -> None:
|
|
81
81
|
"""Stop macOS from killing our background daemons with a native abort.
|
|
82
82
|
|
|
83
|
-
``agentos-auto`` / ``agentos-agentd`` double-fork (fork + setsid
|
|
84
|
-
|
|
83
|
+
``agentos-auto`` / ``agentos-agentd`` double-fork (fork + setsid) into a
|
|
84
|
+
background daemon that then does networking (httpx → CFNetwork /
|
|
85
85
|
SystemConfiguration) and TLS. When the Objective-C runtime detects objc use
|
|
86
86
|
after a bare ``fork()`` with no ``exec()`` it calls ``abort()`` — which macOS
|
|
87
87
|
surfaces as the "Python quit unexpectedly" crash dialog. Exporting
|
|
88
|
-
``OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`` disables that guard.
|
|
88
|
+
``OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`` disables that *objc* guard.
|
|
89
|
+
|
|
90
|
+
It is NOT sufficient on its own: Apple's higher-level frameworks (Network.
|
|
91
|
+
framework's DNS/NAT64 path, the libsystem_trace ``os_log`` cache) are also
|
|
92
|
+
fork-unsafe and will SIGSEGV in the forked child on the first ``getaddrinfo``
|
|
93
|
+
— the env var does not cover them. The actual fix for that is in each daemon's
|
|
94
|
+
``_daemonize()``, which re-execs a fresh interpreter after the double-fork so
|
|
95
|
+
the daemon runs in a clean process image. This shim remains the belt to that
|
|
96
|
+
braces: it quiets the objc guard and short-circuits the ``_scproxy`` lookup for
|
|
97
|
+
any pre-fork or non-daemon objc use, and seeds the var for the exec'd child.
|
|
89
98
|
|
|
90
99
|
The objc runtime reads that variable when its dylib is first mapped (that is
|
|
91
100
|
interpreter startup on framework Python builds), so setting it via
|
|
@@ -330,6 +330,23 @@ def _daemon_running() -> int | None:
|
|
|
330
330
|
return None
|
|
331
331
|
|
|
332
332
|
|
|
333
|
+
#: Set in the environment right before the macOS re-exec below, read back by the
|
|
334
|
+
#: fresh interpreter so it knows it IS the detached daemon and must not fork again.
|
|
335
|
+
_DAEMONIZED_ENV = "AGENTOS_AUTO_DAEMONIZED"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _relog_line_buffered() -> None:
|
|
339
|
+
"""Point sys.stdout/stderr at _LOG_FILE line-buffered.
|
|
340
|
+
|
|
341
|
+
When stdout is a file (not a tty) Python block-buffers it, so progress printed
|
|
342
|
+
via _send() would sit in the buffer and never reach the log until the process
|
|
343
|
+
exits — making the daemon look idle. A line-buffered writer flushes every
|
|
344
|
+
progress line live, exactly like the foreground run.
|
|
345
|
+
"""
|
|
346
|
+
sys.stdout = open(_LOG_FILE, "a", buffering=1, encoding="utf-8")
|
|
347
|
+
sys.stderr = sys.stdout
|
|
348
|
+
|
|
349
|
+
|
|
333
350
|
def _daemonize() -> bool:
|
|
334
351
|
"""Detach the current process from the terminal (POSIX double-fork).
|
|
335
352
|
|
|
@@ -337,10 +354,23 @@ def _daemonize() -> bool:
|
|
|
337
354
|
os._exit() and never return. stdout/stderr are redirected to _LOG_FILE and a
|
|
338
355
|
PID file is written so `stop`/`status` can find the loop. Returns False (stays
|
|
339
356
|
in the foreground) on platforms without os.fork (e.g. Windows).
|
|
357
|
+
|
|
358
|
+
On macOS the detached child does NOT run the loop in-process: it re-execs a
|
|
359
|
+
fresh interpreter (see below) and ``main()`` re-enters here with the
|
|
360
|
+
``_DAEMONIZED_ENV`` marker set — at which point setsid, the redirected fds
|
|
361
|
+
0/1/2 and the PID file already survive from before the exec, so we only
|
|
362
|
+
re-point the Python stream wrappers and report "I am the daemon".
|
|
340
363
|
"""
|
|
341
364
|
if not hasattr(os, "fork"):
|
|
342
365
|
return False # caller falls back to foreground
|
|
343
366
|
|
|
367
|
+
if os.environ.get(_DAEMONIZED_ENV) == "1":
|
|
368
|
+
# Don't leak the one-shot marker to subprocesses the loop later spawns
|
|
369
|
+
# (claude, git, …) — they must never think THEY are the detached daemon.
|
|
370
|
+
os.environ.pop(_DAEMONIZED_ENV, None)
|
|
371
|
+
_relog_line_buffered()
|
|
372
|
+
return True
|
|
373
|
+
|
|
344
374
|
_AGENTOS_DIR.mkdir(parents=True, exist_ok=True)
|
|
345
375
|
|
|
346
376
|
# First fork: let the shell reap the parent and return the prompt.
|
|
@@ -362,15 +392,24 @@ def _daemonize() -> bool:
|
|
|
362
392
|
os.close(devnull)
|
|
363
393
|
os.close(logfd)
|
|
364
394
|
|
|
365
|
-
# CRITICAL: when stdout is a file (not a tty) Python block-buffers it, so
|
|
366
|
-
# progress printed via _send() would sit in the buffer and never reach the
|
|
367
|
-
# log until the process exits — making the daemon look idle. Replace the
|
|
368
|
-
# streams with line-buffered writers so every progress line is flushed live,
|
|
369
|
-
# exactly like the foreground run.
|
|
370
|
-
sys.stdout = open(_LOG_FILE, "a", buffering=1, encoding="utf-8")
|
|
371
|
-
sys.stderr = sys.stdout
|
|
372
|
-
|
|
373
395
|
_write_pid()
|
|
396
|
+
|
|
397
|
+
# macOS: re-exec a FRESH interpreter so the daemon that does networking runs in
|
|
398
|
+
# a clean process image. A bare double-fork() with NO exec() leaves Apple's
|
|
399
|
+
# higher-level frameworks (Network.framework's DNS/NAT64 path, the libsystem_trace
|
|
400
|
+
# os_log cache) half-initialised in the child, and the FIRST getaddrinfo on the
|
|
401
|
+
# asyncio resolver thread then SIGSEGVs ("crashed on child side of fork pre-exec").
|
|
402
|
+
# OBJC_DISABLE_INITIALIZE_FORK_SAFETY only silences the objc +initialize guard,
|
|
403
|
+
# NOT these frameworks — so the env var alone is not enough on macOS 26. The exec'd
|
|
404
|
+
# process keeps this PID, this setsid session and the fds 0/1/2 redirected above.
|
|
405
|
+
if sys.platform == "darwin":
|
|
406
|
+
os.environ[_DAEMONIZED_ENV] = "1"
|
|
407
|
+
try:
|
|
408
|
+
os.execv(sys.executable, [sys.executable, sys.argv[0], *sys.argv[1:]])
|
|
409
|
+
except Exception: # noqa: BLE001 — exec failed; fall back to in-process daemon
|
|
410
|
+
os.environ.pop(_DAEMONIZED_ENV, None)
|
|
411
|
+
|
|
412
|
+
_relog_line_buffered()
|
|
374
413
|
return True
|
|
375
414
|
|
|
376
415
|
|
|
@@ -1460,6 +1499,11 @@ def main() -> None:
|
|
|
1460
1499
|
# ticket K... → ONLY the named Jira keys (any status, in order)
|
|
1461
1500
|
# story [K...] → convert Stories → Tasks only (with PRD); NO coding.
|
|
1462
1501
|
# With keys: those Stories; without: every To Do Story.
|
|
1502
|
+
# macOS re-exec of the already-detached daemon (see _daemonize): this
|
|
1503
|
+
# process IS the loop owner — the update, the duplicate-loop guard and the
|
|
1504
|
+
# banner all ran in the original process before the fork, so skip them and
|
|
1505
|
+
# go straight to detaching (a no-op fork here) and running the loop.
|
|
1506
|
+
resuming = os.environ.get(_DAEMONIZED_ENV) == "1"
|
|
1463
1507
|
only_keys: list[str] | None = None
|
|
1464
1508
|
stories_only = cmd in ("story", "stories")
|
|
1465
1509
|
# `--agent-only` narrows whichever mode was chosen to the agent-tagged
|
|
@@ -1480,17 +1524,19 @@ def main() -> None:
|
|
|
1480
1524
|
# long-lived old daemon would pin `stop`/`status` to a stale version. Runs
|
|
1481
1525
|
# in the FOREGROUND so the user sees update output; re-execs on the new
|
|
1482
1526
|
# code. Skipped with --no-update or AGENTOS_NO_UPDATE=1.
|
|
1483
|
-
if "--no-update" not in argv:
|
|
1527
|
+
if not resuming and "--no-update" not in argv:
|
|
1484
1528
|
from sdk.updater import auto_update_and_reexec
|
|
1485
1529
|
auto_update_and_reexec("sdk.run_auto", sys.argv[1:])
|
|
1486
1530
|
|
|
1487
1531
|
# Refuse to start a second loop — one background daemon owns the queue.
|
|
1488
|
-
|
|
1489
|
-
if
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1532
|
+
# (The re-exec'd daemon IS that loop; its own live PID would trip this.)
|
|
1533
|
+
if not resuming:
|
|
1534
|
+
pid = _daemon_running()
|
|
1535
|
+
if pid:
|
|
1536
|
+
print(f"agentos-auto is already running in the background (pid {pid}).")
|
|
1537
|
+
print(f" logs: {_LOG_FILE}")
|
|
1538
|
+
print(" stop: agentos-auto stop")
|
|
1539
|
+
return
|
|
1494
1540
|
|
|
1495
1541
|
workspace_root = _parse_workspace_root(argv)
|
|
1496
1542
|
|
|
@@ -1513,7 +1559,7 @@ def main() -> None:
|
|
|
1513
1559
|
# the loop — it runs until `agentos-auto stop`. `--foreground`/`-f` keeps it
|
|
1514
1560
|
# attached (useful for debugging). On platforms without fork we stay attached.
|
|
1515
1561
|
foreground = "--foreground" in argv or "-f" in argv
|
|
1516
|
-
if not foreground and hasattr(os, "fork"):
|
|
1562
|
+
if not foreground and not resuming and hasattr(os, "fork"):
|
|
1517
1563
|
if stories_only:
|
|
1518
1564
|
target = (f" — stories→tasks: {', '.join(only_keys)}" if only_keys
|
|
1519
1565
|
else " — converting all To Do Stories → Tasks")
|
|
@@ -1527,7 +1573,7 @@ def main() -> None:
|
|
|
1527
1573
|
print(" status: agentos-auto status")
|
|
1528
1574
|
print(" stop: agentos-auto stop")
|
|
1529
1575
|
if not foreground and _daemonize():
|
|
1530
|
-
# detached daemon child only
|
|
1576
|
+
# detached daemon child only (on macOS, the re-exec'd process)
|
|
1531
1577
|
_run_loop(workspace_root, only_keys, stories_only, agent_only)
|
|
1532
1578
|
return
|
|
1533
1579
|
if not foreground:
|
|
@@ -158,6 +158,37 @@ class TestParser:
|
|
|
158
158
|
c = M.EsloggerCollector(str(log), parser=_parser(), state_file=tmp_path / "st.json")
|
|
159
159
|
assert c._state == tmp_path / "st.json"
|
|
160
160
|
|
|
161
|
+
def test_tail_loop_records_events_and_does_not_crash_on_an_idle_poll(
|
|
162
|
+
self, tmp_path, monkeypatch
|
|
163
|
+
):
|
|
164
|
+
"""Regression: EsloggerCollector reuses AuditdCollector's tail loop, which calls
|
|
165
|
+
``parser.flush_older_than`` on EVERY poll. EsloggerParser was missing it, so the
|
|
166
|
+
collector thread died with AttributeError on its first (idle) poll — the tail
|
|
167
|
+
starts at EOF, so the first poll reads nothing and goes straight to that call —
|
|
168
|
+
and not a single event was ever recorded (SDK v1.6.52 on the Mac VPS)."""
|
|
169
|
+
from sdk.activity.collectors import linux_auditd as L
|
|
170
|
+
|
|
171
|
+
log = tmp_path / "es.ndjson"
|
|
172
|
+
log.write_text(_exec(1, ["old"], tty="/dev/ttys001") + "\n") # pre-install: not backfilled
|
|
173
|
+
collector = M.EsloggerCollector(
|
|
174
|
+
str(log), parser=_parser(), state_file=tmp_path / "st.json", poll_s=0,
|
|
175
|
+
)
|
|
176
|
+
calls = {"n": 0}
|
|
177
|
+
|
|
178
|
+
def fake_sleep(_):
|
|
179
|
+
calls["n"] += 1
|
|
180
|
+
if calls["n"] == 1: # new activity lands after the first, empty poll
|
|
181
|
+
with log.open("a") as fh:
|
|
182
|
+
fh.write(_exec(2, ["whoami"], tty="/dev/ttys001") + "\n")
|
|
183
|
+
else:
|
|
184
|
+
collector.stop()
|
|
185
|
+
|
|
186
|
+
monkeypatch.setattr(L.time, "sleep", fake_sleep)
|
|
187
|
+
collector.run() # before the fix: raises AttributeError out of the loop
|
|
188
|
+
|
|
189
|
+
_, events = _queued()
|
|
190
|
+
assert [e["command"] for e in events] == ["whoami"]
|
|
191
|
+
|
|
161
192
|
|
|
162
193
|
# ─────────────────────────────── setup ───────────────────────────────
|
|
163
194
|
|
|
@@ -27,6 +27,7 @@ from __future__ import annotations
|
|
|
27
27
|
|
|
28
28
|
import asyncio
|
|
29
29
|
import os
|
|
30
|
+
import sys
|
|
30
31
|
|
|
31
32
|
import pytest
|
|
32
33
|
|
|
@@ -596,6 +597,91 @@ def test_the_running_daemon_guard_still_wins(monkeypatch, tmp_path, capsys):
|
|
|
596
597
|
assert "already running in the background (pid 4242)" in capsys.readouterr().out
|
|
597
598
|
|
|
598
599
|
|
|
600
|
+
# ── macOS fork-safety: the daemon re-execs a fresh interpreter ──────────────
|
|
601
|
+
# A bare double-fork() with no exec() leaves Apple's Network.framework / os_log
|
|
602
|
+
# half-initialised in the child, so the first getaddrinfo on the asyncio resolver
|
|
603
|
+
# thread SIGSEGVs ("crashed on child side of fork pre-exec"). _daemonize() re-execs
|
|
604
|
+
# the interpreter after detaching; main() re-enters with _DAEMONIZED_ENV set and
|
|
605
|
+
# must treat THIS process as the loop owner — not fork again, not trip its own guard.
|
|
606
|
+
|
|
607
|
+
def test_the_reexec_daemon_is_the_loop_not_a_second_one(monkeypatch, tmp_path, capsys):
|
|
608
|
+
"""The re-exec'd daemon keeps the PID written before the exec, so _daemon_running()
|
|
609
|
+
would report IT as 'already running' and the loop would exit on its own guard.
|
|
610
|
+
With the marker set that guard — and the update and the banner — must be skipped."""
|
|
611
|
+
calls = _stub_main(monkeypatch, tmp_path, resolved=None, forkable=True)
|
|
612
|
+
monkeypatch.setattr(run_auto, "_daemon_running", lambda: os.getpid()) # our own live PID
|
|
613
|
+
monkeypatch.setenv(run_auto._DAEMONIZED_ENV, "1")
|
|
614
|
+
monkeypatch.setattr("sys.argv", ["agentos-auto"]) # no --no-update either
|
|
615
|
+
|
|
616
|
+
run_auto.main()
|
|
617
|
+
|
|
618
|
+
assert calls["run_loop"] == [(None, None, False, False)] # the loop actually ran
|
|
619
|
+
out = capsys.readouterr().out
|
|
620
|
+
assert "already running" not in out and "started in the background" not in out
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def test_daemonize_marker_short_circuits_without_forking(monkeypatch, tmp_path):
|
|
624
|
+
"""Re-entered with the marker, _daemonize() must report 'I am the daemon' WITHOUT
|
|
625
|
+
forking (setsid/fds/PID already survive the exec) and clear the marker so the
|
|
626
|
+
children the loop later spawns don't inherit it and skip their own daemonizing."""
|
|
627
|
+
monkeypatch.setattr(run_auto, "_LOG_FILE", tmp_path / "auto.log")
|
|
628
|
+
monkeypatch.setenv(run_auto._DAEMONIZED_ENV, "1")
|
|
629
|
+
monkeypatch.setattr(os, "fork", lambda: (_ for _ in ()).throw(AssertionError("forked")))
|
|
630
|
+
|
|
631
|
+
saved_out, saved_err = sys.stdout, sys.stderr
|
|
632
|
+
try:
|
|
633
|
+
assert run_auto._daemonize() is True
|
|
634
|
+
finally:
|
|
635
|
+
sys.stdout.close()
|
|
636
|
+
sys.stdout, sys.stderr = saved_out, saved_err
|
|
637
|
+
|
|
638
|
+
assert run_auto._DAEMONIZED_ENV not in os.environ # not leaked to spawned children
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
class _ExecvReached(BaseException):
|
|
642
|
+
"""Marks that os.execv was called — a BaseException so _daemonize's
|
|
643
|
+
``except Exception`` fallback does not swallow it."""
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def test_macos_daemonize_reexecs_a_fresh_interpreter(monkeypatch, tmp_path):
|
|
647
|
+
"""On macOS the detached child does not run in-process: it re-execs the
|
|
648
|
+
interpreter (clean Network.framework state) with the marker set, carrying the
|
|
649
|
+
same argv so main() resumes the loop."""
|
|
650
|
+
monkeypatch.setattr(sys, "platform", "darwin")
|
|
651
|
+
monkeypatch.setattr(run_auto, "_LOG_FILE", tmp_path / "auto.log")
|
|
652
|
+
monkeypatch.setattr(run_auto, "_AGENTOS_DIR", tmp_path)
|
|
653
|
+
monkeypatch.setattr(run_auto, "_write_pid", lambda: None)
|
|
654
|
+
monkeypatch.delenv(run_auto._DAEMONIZED_ENV, raising=False)
|
|
655
|
+
monkeypatch.setattr(os, "fork", lambda: 0) # always the child
|
|
656
|
+
monkeypatch.setattr(os, "setsid", lambda: None)
|
|
657
|
+
monkeypatch.setattr(os, "open", lambda *a, **k: 3)
|
|
658
|
+
monkeypatch.setattr(os, "dup2", lambda *a: None)
|
|
659
|
+
monkeypatch.setattr(os, "close", lambda *a: None)
|
|
660
|
+
# Under pytest the captured std streams have no real fileno(); the fd shuffle
|
|
661
|
+
# is already a no-op above, so hand _daemonize plain stand-ins to flush/number.
|
|
662
|
+
fake_std = type("S", (), {"flush": lambda self: None, "fileno": lambda self: 0})()
|
|
663
|
+
monkeypatch.setattr(sys, "stdin", fake_std)
|
|
664
|
+
monkeypatch.setattr(sys, "stdout", fake_std)
|
|
665
|
+
monkeypatch.setattr(sys, "stderr", fake_std)
|
|
666
|
+
monkeypatch.setattr("sys.argv", ["agentos-auto", "ticket", "BBW-9"])
|
|
667
|
+
|
|
668
|
+
seen = {}
|
|
669
|
+
|
|
670
|
+
def fake_execv(exe, argv):
|
|
671
|
+
seen["exe"], seen["argv"] = exe, argv
|
|
672
|
+
seen["marker"] = os.environ.get(run_auto._DAEMONIZED_ENV)
|
|
673
|
+
raise _ExecvReached
|
|
674
|
+
|
|
675
|
+
monkeypatch.setattr(os, "execv", fake_execv)
|
|
676
|
+
|
|
677
|
+
with pytest.raises(_ExecvReached):
|
|
678
|
+
run_auto._daemonize()
|
|
679
|
+
|
|
680
|
+
assert seen["exe"] == sys.executable
|
|
681
|
+
assert seen["argv"] == [sys.executable, "agentos-auto", "ticket", "BBW-9"]
|
|
682
|
+
assert seen["marker"] == "1" # the fresh interpreter learns it is the daemon
|
|
683
|
+
|
|
684
|
+
|
|
599
685
|
def test_the_docstring_documents_the_flag_and_the_suffix():
|
|
600
686
|
doc = run_auto.__doc__ or ""
|
|
601
687
|
assert "--agent-only" in doc # Options block
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.6.52 → devops_bot_sdk-1.6.54}/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
|
|
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
|