devops-bot-sdk 1.6.38__tar.gz → 1.6.40__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.38 → devops_bot_sdk-1.6.40}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/devops_bot_sdk.egg-info/SOURCES.txt +9 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/__init__.py +1 -1
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agentd.py +26 -2
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/client.py +139 -19
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/git_ops.py +36 -3
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ipc/handlers.py +426 -40
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/local_exec.py +14 -0
- devops_bot_sdk-1.6.40/sdk/memory_guard.py +159 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/platform_compat.py +31 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/run_auto.py +228 -26
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/updater.py +227 -67
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_auto_agent_dispatch.py +10 -2
- devops_bot_sdk-1.6.40/tests/test_crash_recovery.py +160 -0
- devops_bot_sdk-1.6.40/tests/test_done_transition.py +200 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_firewall_open_on_update.py +40 -6
- devops_bot_sdk-1.6.40/tests/test_memory_guard.py +189 -0
- devops_bot_sdk-1.6.40/tests/test_pr_identity.py +271 -0
- devops_bot_sdk-1.6.40/tests/test_project_path_resolution.py +260 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_run_auto_agent_only.py +20 -1
- devops_bot_sdk-1.6.40/tests/test_run_verdict_is_honest.py +147 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_task_file_and_plan_run.py +6 -2
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_ticket_speed_fixes.py +8 -6
- devops_bot_sdk-1.6.40/tests/test_updater_reports_the_truth.py +328 -0
- devops_bot_sdk-1.6.40/tests/test_windows_loop_identity.py +193 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/README.md +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.38 → devops_bot_sdk-1.6.40}/tests/test_write_hook.py +0 -0
|
@@ -26,6 +26,7 @@ sdk/graphify.py
|
|
|
26
26
|
sdk/intake.py
|
|
27
27
|
sdk/journal.py
|
|
28
28
|
sdk/local_exec.py
|
|
29
|
+
sdk/memory_guard.py
|
|
29
30
|
sdk/phase_timer.py
|
|
30
31
|
sdk/plan_run.py
|
|
31
32
|
sdk/platform_compat.py
|
|
@@ -140,7 +141,9 @@ tests/test_client_jira_projects.py
|
|
|
140
141
|
tests/test_config_token_diagnostics.py
|
|
141
142
|
tests/test_configure_token_owner.py
|
|
142
143
|
tests/test_console_encoding.py
|
|
144
|
+
tests/test_crash_recovery.py
|
|
143
145
|
tests/test_departments.py
|
|
146
|
+
tests/test_done_transition.py
|
|
144
147
|
tests/test_epic_decompose_run.py
|
|
145
148
|
tests/test_epic_inherited_agent_id.py
|
|
146
149
|
tests/test_epic_session_isolation.py
|
|
@@ -158,10 +161,13 @@ tests/test_intake_job_outcome.py
|
|
|
158
161
|
tests/test_intake_plan_review.py
|
|
159
162
|
tests/test_journal.py
|
|
160
163
|
tests/test_local_exec_provider.py
|
|
164
|
+
tests/test_memory_guard.py
|
|
161
165
|
tests/test_phase_timer.py
|
|
162
166
|
tests/test_plan_new_command.py
|
|
163
167
|
tests/test_plan_run_activity.py
|
|
168
|
+
tests/test_pr_identity.py
|
|
164
169
|
tests/test_prd_artifact.py
|
|
170
|
+
tests/test_project_path_resolution.py
|
|
165
171
|
tests/test_proof_readers.py
|
|
166
172
|
tests/test_providers.py
|
|
167
173
|
tests/test_recon_session_isolation.py
|
|
@@ -169,6 +175,7 @@ tests/test_resume.py
|
|
|
169
175
|
tests/test_router.py
|
|
170
176
|
tests/test_run_auto_agent_only.py
|
|
171
177
|
tests/test_run_outputs.py
|
|
178
|
+
tests/test_run_verdict_is_honest.py
|
|
172
179
|
tests/test_schedule_api.py
|
|
173
180
|
tests/test_scheduler.py
|
|
174
181
|
tests/test_self_report.py
|
|
@@ -184,6 +191,8 @@ tests/test_ui_gate.py
|
|
|
184
191
|
tests/test_ui_lifespan.py
|
|
185
192
|
tests/test_update_timer.py
|
|
186
193
|
tests/test_updater_extras.py
|
|
194
|
+
tests/test_updater_reports_the_truth.py
|
|
187
195
|
tests/test_url_access.py
|
|
188
196
|
tests/test_vps_health.py
|
|
197
|
+
tests/test_windows_loop_identity.py
|
|
189
198
|
tests/test_write_hook.py
|
|
@@ -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.40" # 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
|
|
@@ -438,7 +438,31 @@ async def _dispatch_agent_run(
|
|
|
438
438
|
from sdk import write_hook
|
|
439
439
|
from sdk.agents.engines.claude_code import ensure_agent_workspace
|
|
440
440
|
|
|
441
|
-
|
|
441
|
+
# Where the agent works. A TICKET-triggered run belongs in the repo the
|
|
442
|
+
# ticket names — this call site had the `task` all along and never passed
|
|
443
|
+
# it, so `claude_code.run` fell through to `ensure_agent_workspace()` and
|
|
444
|
+
# every agent-routed ticket wrote into `~/.agentos/runs/<agent>` on any
|
|
445
|
+
# drive and any OS, then reported success. A ticket that names nowhere,
|
|
446
|
+
# and every scheduled run, still gets the per-agent scratch directory:
|
|
447
|
+
# "summarise my PRs daily" has no repo and should not be given one.
|
|
448
|
+
_workspace = ""
|
|
449
|
+
if task:
|
|
450
|
+
try:
|
|
451
|
+
from sdk.ipc.handlers import resolve_project_path
|
|
452
|
+
_resolved = resolve_project_path(task, {})
|
|
453
|
+
if _resolved.source == "instructions":
|
|
454
|
+
_workspace = _resolved.path
|
|
455
|
+
elif _resolved.unresolved:
|
|
456
|
+
logger.warning(
|
|
457
|
+
"agentd.project_path_unresolved agent=%s task=%s named=%s",
|
|
458
|
+
manifest.name, task_id, _resolved.unresolved,
|
|
459
|
+
)
|
|
460
|
+
except Exception as exc: # noqa: BLE001 — never block a run on this
|
|
461
|
+
logger.warning("agentd.project_path_failed task=%s err=%s",
|
|
462
|
+
task_id, exc)
|
|
463
|
+
if not _workspace:
|
|
464
|
+
_workspace = str(ensure_agent_workspace(manifest.name))
|
|
465
|
+
|
|
442
466
|
_record_write = write_hook.make_recorder(
|
|
443
467
|
task_id=task_id or f"agent-{manifest.name}",
|
|
444
468
|
repo_path=_workspace,
|
|
@@ -452,7 +476,7 @@ async def _dispatch_agent_run(
|
|
|
452
476
|
_started = time.monotonic()
|
|
453
477
|
local_result = await claude_code_engine.run(
|
|
454
478
|
manifest, skill_text, mcp_config, extra_env=effective_secrets or None,
|
|
455
|
-
on_event=_tee,
|
|
479
|
+
on_event=_tee, project_path=_workspace,
|
|
456
480
|
)
|
|
457
481
|
run_log.finish_run(_log_path, local_result)
|
|
458
482
|
# Backstop for writes the stream did not surface (a resumed turn,
|
|
@@ -926,12 +926,18 @@ class BackendClient:
|
|
|
926
926
|
except Exception:
|
|
927
927
|
return "pending"
|
|
928
928
|
|
|
929
|
-
async def
|
|
930
|
-
"""
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
929
|
+
async def github_token_detail(self) -> tuple[dict | None, str]:
|
|
930
|
+
"""``(record, reason)`` — the GitHub link record, and WHY when there is none.
|
|
931
|
+
|
|
932
|
+
The reason exists because collapsing every outcome to ``None`` made a whole
|
|
933
|
+
class of failure indistinguishable from "this user has no GitHub account".
|
|
934
|
+
A run then pushes with the box's SSH key instead of the person's token, and —
|
|
935
|
+
if ``gh`` is not installed — records ``pr_unavailable``, which the compliance
|
|
936
|
+
gate treats as an explicit non-issue. The ticket completes with no PR of
|
|
937
|
+
theirs, and nothing anywhere says why. Per-user by construction, which is how
|
|
938
|
+
one employee ends up with 20 tickets and one PR while everyone else is fine.
|
|
939
|
+
|
|
940
|
+
Best-effort: never raises.
|
|
935
941
|
"""
|
|
936
942
|
from sdk.config import BE_DEFAULT_URL
|
|
937
943
|
|
|
@@ -945,30 +951,69 @@ class BackendClient:
|
|
|
945
951
|
try:
|
|
946
952
|
async with httpx.AsyncClient(timeout=15.0) as client:
|
|
947
953
|
resp = await client.get(url, headers=headers)
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
return (resp.json() or {}).get("data") or None
|
|
951
|
-
except Exception:
|
|
952
|
-
return None
|
|
954
|
+
except Exception as exc: # noqa: BLE001
|
|
955
|
+
return (None, f"the GitHub token service was unreachable ({type(exc).__name__})")
|
|
953
956
|
|
|
954
|
-
|
|
955
|
-
|
|
957
|
+
if resp.status_code in (401, 403):
|
|
958
|
+
return (None, (f"the GitHub token request was rejected "
|
|
959
|
+
f"({resp.status_code}) — the co_ token may be wrong or "
|
|
960
|
+
"the link revoked"))
|
|
961
|
+
if resp.status_code == 404:
|
|
962
|
+
return (None, "no GitHub account is linked to this user")
|
|
963
|
+
if resp.status_code >= 400:
|
|
964
|
+
return (None, f"the GitHub token service returned {resp.status_code}")
|
|
965
|
+
try:
|
|
966
|
+
record = (resp.json() or {}).get("data") or None
|
|
967
|
+
except Exception: # noqa: BLE001 — a proxy answering 2xx with HTML
|
|
968
|
+
return (None, "the GitHub token service returned an unreadable body")
|
|
969
|
+
if not record:
|
|
970
|
+
return (None, "no GitHub account is linked to this user")
|
|
971
|
+
if not (record.get("accessToken") or "").strip():
|
|
972
|
+
return (None, "the linked GitHub account has no access token stored")
|
|
973
|
+
|
|
974
|
+
# The scope is on the record and has never been looked at. A token without
|
|
975
|
+
# `repo` can often still clone, so the run gets all the way to the push or
|
|
976
|
+
# the PR call before failing — with the reason buried in a step line.
|
|
977
|
+
scope = str(record.get("scope") or "")
|
|
978
|
+
if scope and not any(s in scope for s in ("repo", "public_repo", "write")):
|
|
979
|
+
return (record, (f"the GitHub token's scope ('{scope}') may not allow "
|
|
980
|
+
"pushing or opening PRs"))
|
|
981
|
+
return (record, "")
|
|
956
982
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
983
|
+
async def github_token_record(self) -> dict | None:
|
|
984
|
+
"""The GitHub link record, or None. See :meth:`github_token_detail` for why."""
|
|
985
|
+
record, _reason = await self.github_token_detail()
|
|
986
|
+
return record
|
|
987
|
+
|
|
988
|
+
async def github_token_with_reason(self) -> tuple[str | None, str]:
|
|
989
|
+
"""``(token, reason)`` — the user's GitHub access token, and why when absent.
|
|
990
|
+
|
|
991
|
+
Only successes are cached, deliberately: a cached failure would make one bad
|
|
992
|
+
minute look like an unlinked account for the next half hour.
|
|
961
993
|
"""
|
|
962
994
|
import time as _time
|
|
963
995
|
|
|
964
996
|
now = _time.monotonic()
|
|
965
997
|
cached = _GITHUB_TOKEN_CACHE.get(self._token)
|
|
966
998
|
if cached and now < cached[1]:
|
|
967
|
-
return cached[0]
|
|
968
|
-
record = await self.
|
|
999
|
+
return (cached[0], "")
|
|
1000
|
+
record, reason = await self.github_token_detail()
|
|
969
1001
|
token = (record or {}).get("accessToken") or None
|
|
970
1002
|
if token:
|
|
971
1003
|
_GITHUB_TOKEN_CACHE[self._token] = (token, now + _GITHUB_TOKEN_TTL_S)
|
|
1004
|
+
return (token, reason)
|
|
1005
|
+
|
|
1006
|
+
async def github_token(self) -> str | None:
|
|
1007
|
+
"""GET /ml-api/github/token → the user's GitHub access token (gho_…).
|
|
1008
|
+
|
|
1009
|
+
Used by the LOCAL agent to authenticate git/gh for clone/push/PR. The
|
|
1010
|
+
endpoint lives on the ML backend (devopsbot-be), authorised by the same
|
|
1011
|
+
co_ token. Best-effort: returns None on any failure so a run without a
|
|
1012
|
+
connected GitHub account still proceeds (clone/PR steps just won't auth) —
|
|
1013
|
+
callers that report to a human should use :meth:`github_token_with_reason`,
|
|
1014
|
+
because "no token" and "the token call failed" need different fixes.
|
|
1015
|
+
"""
|
|
1016
|
+
token, _reason = await self.github_token_with_reason()
|
|
972
1017
|
return token
|
|
973
1018
|
|
|
974
1019
|
async def ingest_github_prs(self, since: str | None = None) -> dict | None:
|
|
@@ -1153,6 +1198,81 @@ class BackendClient:
|
|
|
1153
1198
|
logger.warning("client.mark_in_progress error task=%s err=%s", task_id, exc)
|
|
1154
1199
|
return False
|
|
1155
1200
|
|
|
1201
|
+
async def get_task_status(self, task_id: str) -> str | None:
|
|
1202
|
+
"""GET {BE}/tasks-ml/{id} → the ticket's CURRENT board status, or None.
|
|
1203
|
+
|
|
1204
|
+
``None`` means "could not read it", which is deliberately NOT the same as
|
|
1205
|
+
"not Done". The Done safety net in ``handlers.handle_pipeline_approve``
|
|
1206
|
+
forces a status only when it can prove the board has not caught up;
|
|
1207
|
+
guessing on an unreadable ticket would be worse than leaving it alone.
|
|
1208
|
+
|
|
1209
|
+
This method is the one the safety net has always called — and it has never
|
|
1210
|
+
existed. Its absence raised ``AttributeError`` one line before
|
|
1211
|
+
``set_task_status(task_id, "Done")``, outside that call's ``try``, on every
|
|
1212
|
+
ticket that had just been committed, pushed and PR'd; the auto loop's
|
|
1213
|
+
per-ticket handler then moved the finished ticket to Backlog. So every
|
|
1214
|
+
delivered ticket failed, and only the delivered ones.
|
|
1215
|
+
|
|
1216
|
+
The single-record endpoint is not guaranteed to be served — the backend's
|
|
1217
|
+
own reader documents the same uncertainty and carries the same fallback
|
|
1218
|
+
(``devops-bot-ai/modules/agents_tools/jira/agent.py``, ``get_task``) — so a
|
|
1219
|
+
non-200 or an unparseable body falls back to scanning the list endpoint for
|
|
1220
|
+
this id. Best-effort throughout: never raises.
|
|
1221
|
+
"""
|
|
1222
|
+
from sdk.config import BE_DEFAULT_URL
|
|
1223
|
+
|
|
1224
|
+
if not task_id:
|
|
1225
|
+
return None
|
|
1226
|
+
headers = {**self._headers, "accept": "*/*", "developer-name": "null", "product": ""}
|
|
1227
|
+
|
|
1228
|
+
def _status_of(rec: object) -> str | None:
|
|
1229
|
+
if not isinstance(rec, dict):
|
|
1230
|
+
return None
|
|
1231
|
+
val = str(rec.get("status") or "").strip()
|
|
1232
|
+
return val or None
|
|
1233
|
+
|
|
1234
|
+
# 1. Direct single-record read. The body is `{data: {record: {...}}}` or
|
|
1235
|
+
# `{data: {...}}` depending on the endpoint — accept both.
|
|
1236
|
+
url = f"{BE_DEFAULT_URL.rstrip('/')}/tasks-ml/{task_id}"
|
|
1237
|
+
try:
|
|
1238
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
1239
|
+
resp = await client.get(url, headers=headers)
|
|
1240
|
+
if resp.status_code < 400:
|
|
1241
|
+
data = (resp.json() or {}).get("data") or {}
|
|
1242
|
+
found = _status_of(data.get("record") if isinstance(data, dict) else None)
|
|
1243
|
+
if found is None and isinstance(data, dict) and data.get("id"):
|
|
1244
|
+
found = _status_of(data)
|
|
1245
|
+
if found:
|
|
1246
|
+
return found
|
|
1247
|
+
except Exception as exc: # noqa: BLE001 — fall through to the list scan
|
|
1248
|
+
logger.warning("client.get_task_status direct failed task=%s err=%s", task_id, exc)
|
|
1249
|
+
|
|
1250
|
+
# 2. Fallback: find the record in the list endpoint. Unfiltered, because
|
|
1251
|
+
# the point of the read is to learn which status the ticket is in.
|
|
1252
|
+
try:
|
|
1253
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
1254
|
+
resp = await client.get(
|
|
1255
|
+
f"{BE_DEFAULT_URL.rstrip('/')}/tasks-ml",
|
|
1256
|
+
params={"limit": 10000}, headers=headers,
|
|
1257
|
+
)
|
|
1258
|
+
if resp.status_code >= 400:
|
|
1259
|
+
return None
|
|
1260
|
+
records = ((resp.json() or {}).get("data") or {}).get("records") or []
|
|
1261
|
+
want = str(task_id).strip().upper()
|
|
1262
|
+
for rec in records:
|
|
1263
|
+
if not isinstance(rec, dict):
|
|
1264
|
+
continue
|
|
1265
|
+
ident = {
|
|
1266
|
+
str(rec.get("id") or "").strip().upper(),
|
|
1267
|
+
str(rec.get("jiraIssueKey") or "").strip().upper(),
|
|
1268
|
+
str(rec.get("jiraKey") or "").strip().upper(),
|
|
1269
|
+
}
|
|
1270
|
+
if want in ident:
|
|
1271
|
+
return _status_of(rec)
|
|
1272
|
+
except Exception as exc: # noqa: BLE001
|
|
1273
|
+
logger.warning("client.get_task_status list failed task=%s err=%s", task_id, exc)
|
|
1274
|
+
return None
|
|
1275
|
+
|
|
1156
1276
|
async def set_task_status(self, task_id: str, status: str) -> bool:
|
|
1157
1277
|
"""PUT {BE}/tasks-ml/{id} status=<status> (best-effort, arbitrary target).
|
|
1158
1278
|
|
|
@@ -283,8 +283,16 @@ async def _create_pr_via_api(
|
|
|
283
283
|
)
|
|
284
284
|
if resp.status_code == 201:
|
|
285
285
|
return resp.json().get("html_url")
|
|
286
|
-
# 422 = a PR already exists for this head (or validation issue).
|
|
287
|
-
#
|
|
286
|
+
# 422 = a PR already exists for this head (or validation issue). Surfacing
|
|
287
|
+
# the existing open PR is right when the head really is THIS run's branch —
|
|
288
|
+
# a re-run pushing more commits to its own branch should report its own PR.
|
|
289
|
+
#
|
|
290
|
+
# It was catastrophic when the branch was shared: a constant branch name from
|
|
291
|
+
# boilerplate instructions or a placeholder-free template made every ticket
|
|
292
|
+
# push to one branch, and each got handed the first ticket's PR URL, passed
|
|
293
|
+
# compliance and raised nothing. Branch names are per-ticket now
|
|
294
|
+
# (`handlers._uniquify_branch`); this check is the second line of defence, so
|
|
295
|
+
# a PR is only ever claimed when its head ref is the branch we pushed.
|
|
288
296
|
if resp.status_code == 422:
|
|
289
297
|
async with httpx.AsyncClient(timeout=30.0) as c:
|
|
290
298
|
r2 = await c.get(
|
|
@@ -292,7 +300,15 @@ async def _create_pr_via_api(
|
|
|
292
300
|
params={"head": f"{owner}:{head}", "state": "open"},
|
|
293
301
|
)
|
|
294
302
|
if r2.status_code == 200 and r2.json():
|
|
295
|
-
|
|
303
|
+
existing = r2.json()[0]
|
|
304
|
+
found_head = ((existing.get("head") or {}).get("ref") or "").strip()
|
|
305
|
+
if found_head and found_head != head:
|
|
306
|
+
logger.warning(
|
|
307
|
+
"git_ops.pr_head_mismatch asked=%s got=%s — not claiming it",
|
|
308
|
+
head, found_head,
|
|
309
|
+
)
|
|
310
|
+
else:
|
|
311
|
+
return existing.get("html_url")
|
|
296
312
|
logger.warning(
|
|
297
313
|
"git_ops.pr_api_failed owner=%s repo=%s head=%s status=%s body=%s",
|
|
298
314
|
owner, repo, head, resp.status_code, resp.text[:300],
|
|
@@ -302,6 +318,23 @@ async def _create_pr_via_api(
|
|
|
302
318
|
return None
|
|
303
319
|
|
|
304
320
|
|
|
321
|
+
async def has_local_changes(path: str) -> bool:
|
|
322
|
+
"""True when the working tree has staged, unstaged or untracked changes.
|
|
323
|
+
|
|
324
|
+
The tool-level counters miss an entire class of real work: `_write_count` only
|
|
325
|
+
counts Write/Edit/MultiEdit/NotebookEdit, and `files_changed` only events that
|
|
326
|
+
carry a tool-result filePath. An agent that edits through **Bash** — `sed`,
|
|
327
|
+
`patch`, `git apply`, a scaffolding CLI — scores zero on both, so no commit,
|
|
328
|
+
push or PR was ever attempted and the ticket was then failed for "produced 0
|
|
329
|
+
committed changes". Git is the authority on whether the tree moved.
|
|
330
|
+
"""
|
|
331
|
+
env = _git_env(None)
|
|
332
|
+
rc, out, _err = await _run(["git", "status", "--porcelain"], path, env, timeout=60.0)
|
|
333
|
+
if rc != 0:
|
|
334
|
+
return False
|
|
335
|
+
return bool(out.strip())
|
|
336
|
+
|
|
337
|
+
|
|
305
338
|
async def finish_pr(
|
|
306
339
|
project_path: str, branch: str, base: str, title: str, body: str,
|
|
307
340
|
github_token: str | None,
|