devops-bot-sdk 1.6.54__tar.gz → 1.6.56__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.54 → devops_bot_sdk-1.6.56}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/devops_bot_sdk.egg-info/SOURCES.txt +2 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/pyproject.toml +1 -1
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/__init__.py +1 -1
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ipc/handlers.py +5 -2
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/local_exec.py +303 -1
- devops_bot_sdk-1.6.56/sdk/vendor/aidraftsman-cli-0.1.0.tgz +0 -0
- devops_bot_sdk-1.6.56/tests/test_fallback_cli.py +175 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_project_path_resolution.py +12 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/README.md +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/tests/test_write_hook.py +0 -0
|
@@ -137,6 +137,7 @@ sdk/ui/static/schedules.html
|
|
|
137
137
|
sdk/ui/static/setup.html
|
|
138
138
|
sdk/ui/static/styles.css
|
|
139
139
|
sdk/ui/static/tickets.html
|
|
140
|
+
sdk/vendor/aidraftsman-cli-0.1.0.tgz
|
|
140
141
|
tests/test_activity_collectors.py
|
|
141
142
|
tests/test_activity_core.py
|
|
142
143
|
tests/test_activity_macos.py
|
|
@@ -167,6 +168,7 @@ tests/test_epic_decompose_run.py
|
|
|
167
168
|
tests/test_epic_inherited_agent_id.py
|
|
168
169
|
tests/test_epic_session_isolation.py
|
|
169
170
|
tests/test_estimator.py
|
|
171
|
+
tests/test_fallback_cli.py
|
|
170
172
|
tests/test_firewall_open_on_update.py
|
|
171
173
|
tests/test_formats.py
|
|
172
174
|
tests/test_git_action.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.56" # 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
|
|
@@ -3920,12 +3920,15 @@ _HOME_PATH_RE = re.compile(r"(?<![\w.])~(?:/[A-Za-z0-9._\- ]+)+")
|
|
|
3920
3920
|
# `[\\/]+` in the separator position, not a single `\\`: a doubled separator is a
|
|
3921
3921
|
# thing people write (`D://Nexus-AI` — the exact form in one bug report) and the
|
|
3922
3922
|
# old pattern rejected it outright, sending the ticket to the ~/AgentOS fallback.
|
|
3923
|
-
|
|
3923
|
+
# The drive letter must not follow a word character: in `https://host/...` the
|
|
3924
|
+
# `s:` of the scheme otherwise reads as drive S:, so any ticket quoting a web link
|
|
3925
|
+
# "named" the folder `s://host/...` and was refused as unresolvable.
|
|
3926
|
+
_WIN_PATH_RE = re.compile(r"(?<![A-Za-z0-9_])[A-Za-z]:[\\/]+(?:[^\\/:*?\"<>|\r\n]+[\\/]*)+")
|
|
3924
3927
|
# Windows path written with FORWARD slashes (e.g. C:/Users/me/new_tasks) — common on
|
|
3925
3928
|
# Windows. Segments here also allow spaces, for the same reason the backslash form
|
|
3926
3929
|
# does; over-capture is settled afterwards by `_trim_to_existing_dir`, not by trying
|
|
3927
3930
|
# to tell a folder name from prose with a regex.
|
|
3928
|
-
_WIN_FS_PATH_RE = re.compile(r"[A-Za-z]:/+(?:[^\\/:*?\"<>|\r\n]+/*)+")
|
|
3931
|
+
_WIN_FS_PATH_RE = re.compile(r"(?<![A-Za-z0-9_])[A-Za-z]:/+(?:[^\\/:*?\"<>|\r\n]+/*)+")
|
|
3929
3932
|
# A QUOTED path (single/double/back quotes) — the reliable way to give a path that
|
|
3930
3933
|
# contains spaces, on any OS. Accepts a drive letter with EITHER separator (C:\ or C:/).
|
|
3931
3934
|
_QUOTED_PATH_RE = re.compile(r"""["'`]\s*((?:[A-Za-z]:[\\/]|~/|/)[^"'`\r\n]+?)\s*["'`]""")
|
|
@@ -552,7 +552,7 @@ def _local_mcp_servers() -> dict[str, dict]:
|
|
|
552
552
|
return servers
|
|
553
553
|
|
|
554
554
|
|
|
555
|
-
async def
|
|
555
|
+
async def _run_claude_cli(
|
|
556
556
|
prompt: str,
|
|
557
557
|
project_path: str,
|
|
558
558
|
*,
|
|
@@ -1042,3 +1042,305 @@ async def run_claude_local(
|
|
|
1042
1042
|
provider=provider,
|
|
1043
1043
|
model=cli_model["name"] or None,
|
|
1044
1044
|
))
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
# ── Fallback: the AI Draftsman CLI ─────────────────────────────────────────
|
|
1048
|
+
# When the `claude` CLI is not working — not installed, logged out, out of
|
|
1049
|
+
# usage, or dying before it even starts a session — the ticket is re-run on
|
|
1050
|
+
# our own AI Draftsman CLI (`aidraftsman`, npm package @aidraftsman/cli) instead
|
|
1051
|
+
# of failing or parking. It is the same job against the same project dir: the
|
|
1052
|
+
# prompt goes in on stdin, `-p --output-format json` prints one JSON object.
|
|
1053
|
+
# The CLI is bundled in sdk/vendor/ and npm-installed into ~/.agentos/aidraftsman
|
|
1054
|
+
# the first time it is needed; one already on PATH is used instead. It needs
|
|
1055
|
+
# its own login on the box (`ad auth login`, or AD_API_KEY in the env).
|
|
1056
|
+
#
|
|
1057
|
+
# What it does NOT do: honour the PreToolUse approval guard, resume a Claude
|
|
1058
|
+
# session, or stream progress events. So a resumed run never falls back (the
|
|
1059
|
+
# prompt would be "continue" with no context behind it), and the permission
|
|
1060
|
+
# mode defaults to acceptEdits — file edits apply, but a shell command that is
|
|
1061
|
+
# not read-only is denied rather than run unguarded. Set
|
|
1062
|
+
# AGENTOS_FALLBACK_PERMISSION_MODE=bypass to let it run every command.
|
|
1063
|
+
#
|
|
1064
|
+
# Settings (process env or ~/.agentos/.env):
|
|
1065
|
+
# AGENTOS_FALLBACK_CLI path to the CLI (default: PATH, then bundled), or "off"
|
|
1066
|
+
# AGENTOS_FALLBACK_AGENT agent id/name for `--agent` (default: gateway's choice)
|
|
1067
|
+
# AGENTOS_FALLBACK_PERMISSION_MODE default | acceptEdits | plan | bypass
|
|
1068
|
+
|
|
1069
|
+
_FALLBACK_MODES = ("default", "acceptEdits", "plan", "bypass")
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def _setting(key: str) -> str:
|
|
1073
|
+
value = os.environ.get(key, "").strip()
|
|
1074
|
+
if value:
|
|
1075
|
+
return value
|
|
1076
|
+
try:
|
|
1077
|
+
from sdk.config import _dotenv_value
|
|
1078
|
+
return (_dotenv_value(key) or "").strip()
|
|
1079
|
+
except Exception: # noqa: BLE001 — a config hiccup must never break resolution
|
|
1080
|
+
return ""
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
#: The CLI ships inside the SDK (sdk/vendor/*.tgz) so every box has it without
|
|
1084
|
+
#: a separate install step; it is npm-installed from there on first use.
|
|
1085
|
+
_VENDOR_DIR = Path(__file__).parent / "vendor"
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
def _fallback_disabled() -> bool:
|
|
1089
|
+
return _setting("AGENTOS_FALLBACK_CLI").lower() in ("off", "0", "false", "no", "none")
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
def _bundled_tarball() -> Path | None:
|
|
1093
|
+
tarballs = sorted(_VENDOR_DIR.glob("aidraftsman-cli-*.tgz"))
|
|
1094
|
+
return tarballs[-1] if tarballs else None
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
def _bundled_bin(tarball: Path) -> Path:
|
|
1098
|
+
"""Where the bundled tarball's `aidraftsman` lands once installed.
|
|
1099
|
+
|
|
1100
|
+
One prefix per tarball name, so an SDK update that ships a new CLI version
|
|
1101
|
+
installs it fresh rather than reusing the old one.
|
|
1102
|
+
"""
|
|
1103
|
+
prefix = config._CONFIG_DIR / "aidraftsman" / tarball.name.removesuffix(".tgz")
|
|
1104
|
+
name = "aidraftsman.cmd" if sys.platform == "win32" else "aidraftsman"
|
|
1105
|
+
return prefix / "node_modules" / ".bin" / name
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def fallback_cli_path() -> str | None:
|
|
1109
|
+
"""Absolute path to an installed `aidraftsman` CLI, or None if absent or disabled.
|
|
1110
|
+
|
|
1111
|
+
Resolution order: ``AGENTOS_FALLBACK_CLI``, then ``PATH``, then the copy
|
|
1112
|
+
installed from the bundled tarball. Never installs anything — see
|
|
1113
|
+
:func:`ensure_fallback_cli` for that.
|
|
1114
|
+
"""
|
|
1115
|
+
if _fallback_disabled():
|
|
1116
|
+
return None
|
|
1117
|
+
override = _setting("AGENTOS_FALLBACK_CLI")
|
|
1118
|
+
if override and Path(override).exists():
|
|
1119
|
+
return override
|
|
1120
|
+
found = shutil.which("aidraftsman")
|
|
1121
|
+
if found:
|
|
1122
|
+
return found
|
|
1123
|
+
tarball = _bundled_tarball()
|
|
1124
|
+
if tarball and _bundled_bin(tarball).exists():
|
|
1125
|
+
return str(_bundled_bin(tarball))
|
|
1126
|
+
return None
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def _install_bundled(tarball: Path) -> str | None:
|
|
1130
|
+
"""``npm install`` the bundled tarball into a private prefix (no sudo, no -g)."""
|
|
1131
|
+
npm = shutil.which("npm")
|
|
1132
|
+
if not npm:
|
|
1133
|
+
print("aidraftsman fallback unavailable: npm not found (Node.js 20+ required)", flush=True)
|
|
1134
|
+
return None
|
|
1135
|
+
bin_path = _bundled_bin(tarball)
|
|
1136
|
+
prefix = bin_path.parents[2]
|
|
1137
|
+
prefix.mkdir(parents=True, exist_ok=True)
|
|
1138
|
+
cmd = [npm, "install", "--prefix", str(prefix), "--no-audit", "--no-fund",
|
|
1139
|
+
"--omit=dev", str(tarball)]
|
|
1140
|
+
if sys.platform == "win32" and npm.lower().endswith((".cmd", ".bat")):
|
|
1141
|
+
cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
|
|
1142
|
+
print(f"installing bundled {tarball.name} into {prefix}", flush=True)
|
|
1143
|
+
try:
|
|
1144
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, errors="replace",
|
|
1145
|
+
timeout=600)
|
|
1146
|
+
except Exception as exc: # noqa: BLE001 — never raise into a ticket run
|
|
1147
|
+
print(f"aidraftsman install failed: {exc}", flush=True)
|
|
1148
|
+
return None
|
|
1149
|
+
if proc.returncode != 0 or not bin_path.exists():
|
|
1150
|
+
print(f"aidraftsman install failed: {(proc.stderr or proc.stdout).strip()[-300:]}",
|
|
1151
|
+
flush=True)
|
|
1152
|
+
return None
|
|
1153
|
+
return str(bin_path)
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
async def ensure_fallback_cli() -> str | None:
|
|
1157
|
+
""":func:`fallback_cli_path`, installing the bundled CLI first if needed."""
|
|
1158
|
+
found = fallback_cli_path()
|
|
1159
|
+
if found or _fallback_disabled():
|
|
1160
|
+
return found
|
|
1161
|
+
tarball = _bundled_tarball()
|
|
1162
|
+
if not tarball:
|
|
1163
|
+
return None
|
|
1164
|
+
return await asyncio.to_thread(_install_bundled, tarball)
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def _claude_not_working(result: dict[str, Any]) -> bool:
|
|
1168
|
+
"""True when the run failed because of the `claude` CLI, not the task.
|
|
1169
|
+
|
|
1170
|
+
A task that ran and failed on its own merits (an error result, a full
|
|
1171
|
+
context window, a capped response) would fail the same way elsewhere, so
|
|
1172
|
+
those stay with the caller's existing recovery.
|
|
1173
|
+
"""
|
|
1174
|
+
if result.get("ok"):
|
|
1175
|
+
return False
|
|
1176
|
+
if result.get("auth_expired") or result.get("limit_exceeded"):
|
|
1177
|
+
return True
|
|
1178
|
+
if result.get("context_exceeded") or result.get("output_cap_exceeded"):
|
|
1179
|
+
return False
|
|
1180
|
+
# No session id means the CLI never got as far as its init event: missing,
|
|
1181
|
+
# crashed on start-up, or hung before doing anything.
|
|
1182
|
+
return not result.get("session_id")
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
def _git_changed_files(path: Path) -> set[str] | None:
|
|
1186
|
+
"""Paths `git status` reports as changed/untracked, or None outside a repo."""
|
|
1187
|
+
try:
|
|
1188
|
+
proc = subprocess.run(["git", "status", "--porcelain", "-uall"], cwd=str(path),
|
|
1189
|
+
capture_output=True, text=True, timeout=30)
|
|
1190
|
+
except Exception: # noqa: BLE001 — no git, or a slow repo: just skip the list
|
|
1191
|
+
return None
|
|
1192
|
+
if proc.returncode != 0:
|
|
1193
|
+
return None
|
|
1194
|
+
out = set()
|
|
1195
|
+
for line in proc.stdout.splitlines():
|
|
1196
|
+
name = line[3:].split(" -> ")[-1].strip().strip('"')
|
|
1197
|
+
if name:
|
|
1198
|
+
out.add(str(path / name))
|
|
1199
|
+
return out
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
async def run_fallback_cli(
|
|
1203
|
+
prompt: str,
|
|
1204
|
+
project_path: str,
|
|
1205
|
+
*,
|
|
1206
|
+
timeout: float = _DEFAULT_TIMEOUT_S,
|
|
1207
|
+
github_token: str | None = None,
|
|
1208
|
+
extra_env: dict[str, str] | None = None,
|
|
1209
|
+
tier: str | None = None,
|
|
1210
|
+
provider: str | None = None,
|
|
1211
|
+
) -> dict[str, Any]:
|
|
1212
|
+
"""Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run."""
|
|
1213
|
+
cli = await ensure_fallback_cli()
|
|
1214
|
+
if not cli:
|
|
1215
|
+
return _result(ok=False, error="AI Draftsman CLI not available (needs Node.js 20+ "
|
|
1216
|
+
"and npm to install the bundled copy).",
|
|
1217
|
+
tier=tier, provider=provider)
|
|
1218
|
+
|
|
1219
|
+
path = Path(project_path).expanduser()
|
|
1220
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
1221
|
+
|
|
1222
|
+
mode = _setting("AGENTOS_FALLBACK_PERMISSION_MODE") or "acceptEdits"
|
|
1223
|
+
if mode not in _FALLBACK_MODES:
|
|
1224
|
+
mode = "acceptEdits"
|
|
1225
|
+
cmd = [cli, "-p", "--output-format", "json", "--permission-mode", mode]
|
|
1226
|
+
agent = _setting("AGENTOS_FALLBACK_AGENT")
|
|
1227
|
+
if agent:
|
|
1228
|
+
cmd += ["--agent", agent]
|
|
1229
|
+
# npm installs a .cmd shim on Windows — same reason as the claude spawn.
|
|
1230
|
+
if sys.platform == "win32" and cli.lower().endswith((".cmd", ".bat")):
|
|
1231
|
+
cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
|
|
1232
|
+
|
|
1233
|
+
env = dict(os.environ)
|
|
1234
|
+
if extra_env:
|
|
1235
|
+
env.update({k: str(v) for k, v in extra_env.items() if v is not None})
|
|
1236
|
+
if github_token:
|
|
1237
|
+
env["GH_TOKEN"] = github_token
|
|
1238
|
+
env["GITHUB_TOKEN"] = github_token
|
|
1239
|
+
env["GIT_CONFIG_COUNT"] = "1"
|
|
1240
|
+
env["GIT_CONFIG_KEY_0"] = (
|
|
1241
|
+
f"url.https://x-access-token:{github_token}@github.com/.insteadOf"
|
|
1242
|
+
)
|
|
1243
|
+
env["GIT_CONFIG_VALUE_0"] = "https://github.com/"
|
|
1244
|
+
|
|
1245
|
+
before = _git_changed_files(path)
|
|
1246
|
+
print(f"aidraftsman fallback spawn | mode={mode} | {path}", flush=True)
|
|
1247
|
+
proc = await asyncio.create_subprocess_exec(
|
|
1248
|
+
*cmd, cwd=str(path), env=env,
|
|
1249
|
+
stdin=asyncio.subprocess.PIPE,
|
|
1250
|
+
stdout=asyncio.subprocess.PIPE,
|
|
1251
|
+
stderr=asyncio.subprocess.PIPE,
|
|
1252
|
+
)
|
|
1253
|
+
try:
|
|
1254
|
+
stdout, stderr = await asyncio.wait_for(
|
|
1255
|
+
proc.communicate((prompt or "").encode("utf-8")), timeout=timeout)
|
|
1256
|
+
except asyncio.TimeoutError:
|
|
1257
|
+
proc.kill()
|
|
1258
|
+
return _result(ok=False, error=f"aidraftsman CLI timed out after {timeout:.0f}s",
|
|
1259
|
+
tier=tier, provider=provider, model="aidraftsman")
|
|
1260
|
+
except asyncio.CancelledError:
|
|
1261
|
+
try:
|
|
1262
|
+
proc.kill()
|
|
1263
|
+
except ProcessLookupError:
|
|
1264
|
+
pass
|
|
1265
|
+
raise
|
|
1266
|
+
|
|
1267
|
+
after = _git_changed_files(path)
|
|
1268
|
+
files_changed = sorted(after - before) if before is not None and after is not None else []
|
|
1269
|
+
err_text = stderr.decode("utf-8", errors="replace").strip()
|
|
1270
|
+
|
|
1271
|
+
data: dict[str, Any] = {}
|
|
1272
|
+
for line in reversed(stdout.decode("utf-8", errors="replace").splitlines()):
|
|
1273
|
+
try:
|
|
1274
|
+
data = json.loads(line)
|
|
1275
|
+
break
|
|
1276
|
+
except json.JSONDecodeError:
|
|
1277
|
+
continue
|
|
1278
|
+
if proc.returncode != 0 or not isinstance(data, dict) or "result" not in data:
|
|
1279
|
+
err = err_text[-500:] or f"aidraftsman exited with code {proc.returncode}"
|
|
1280
|
+
return _result(ok=False, error=err, files_changed=files_changed,
|
|
1281
|
+
auth_expired=proc.returncode == 2 or _is_auth_error(err),
|
|
1282
|
+
tier=tier, provider=provider, model="aidraftsman")
|
|
1283
|
+
|
|
1284
|
+
usage = data.get("usage") or {}
|
|
1285
|
+
in_tok = int(usage.get("prompt_tokens", 0) or 0)
|
|
1286
|
+
out_tok = int(usage.get("completion_tokens", 0) or 0)
|
|
1287
|
+
text = str(data.get("result") or "")
|
|
1288
|
+
return _result(
|
|
1289
|
+
ok=True,
|
|
1290
|
+
result=text,
|
|
1291
|
+
longest_text=text,
|
|
1292
|
+
input_tokens=in_tok,
|
|
1293
|
+
output_tokens=out_tok,
|
|
1294
|
+
total_tokens=int(usage.get("total_tokens", 0) or 0) or in_tok + out_tok,
|
|
1295
|
+
files_changed=files_changed,
|
|
1296
|
+
tier=tier,
|
|
1297
|
+
provider=provider,
|
|
1298
|
+
model=f"aidraftsman:{data.get('agent') or 'auto'}",
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
async def run_claude_local(
|
|
1303
|
+
prompt: str,
|
|
1304
|
+
project_path: str,
|
|
1305
|
+
*,
|
|
1306
|
+
allowed_tools: list[str] | None = None,
|
|
1307
|
+
on_event: EventHandler | None = None,
|
|
1308
|
+
timeout: float = _DEFAULT_TIMEOUT_S,
|
|
1309
|
+
github_token: str | None = None,
|
|
1310
|
+
resume_session_id: str | None = None,
|
|
1311
|
+
approval_dir: str | None = None,
|
|
1312
|
+
idle_timeout: float = _IDLE_TIMEOUT_S,
|
|
1313
|
+
max_output_tokens: int | None = None,
|
|
1314
|
+
extra_env: dict[str, str] | None = None,
|
|
1315
|
+
unset_env: list[str] | None = None,
|
|
1316
|
+
settings_env: dict[str, str] | None = None,
|
|
1317
|
+
tier: str | None = None,
|
|
1318
|
+
provider: str | None = None,
|
|
1319
|
+
) -> dict[str, Any]:
|
|
1320
|
+
"""Run the local `claude` CLI; if the CLI itself is not working, re-run on
|
|
1321
|
+
the AI Draftsman CLI. See ``_run_claude_cli`` for the arguments and the
|
|
1322
|
+
result shape, and the fallback notes above ``run_fallback_cli``.
|
|
1323
|
+
"""
|
|
1324
|
+
result = await _run_claude_cli(
|
|
1325
|
+
prompt, project_path,
|
|
1326
|
+
allowed_tools=allowed_tools, on_event=on_event, timeout=timeout,
|
|
1327
|
+
github_token=github_token, resume_session_id=resume_session_id,
|
|
1328
|
+
approval_dir=approval_dir, idle_timeout=idle_timeout,
|
|
1329
|
+
max_output_tokens=max_output_tokens, extra_env=extra_env,
|
|
1330
|
+
unset_env=unset_env, settings_env=settings_env, tier=tier, provider=provider,
|
|
1331
|
+
)
|
|
1332
|
+
if resume_session_id or not _claude_not_working(result) or _fallback_disabled():
|
|
1333
|
+
return result
|
|
1334
|
+
|
|
1335
|
+
print(f"claude CLI not working ({(result.get('error') or '')[:200]}) — "
|
|
1336
|
+
f"falling back to aidraftsman", flush=True)
|
|
1337
|
+
fallback = await run_fallback_cli(
|
|
1338
|
+
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1339
|
+
extra_env=extra_env, tier=tier, provider=provider,
|
|
1340
|
+
)
|
|
1341
|
+
if fallback["ok"]:
|
|
1342
|
+
return fallback
|
|
1343
|
+
# Both failed: keep the claude result (its limit/auth flags drive the
|
|
1344
|
+
# caller's parking and alerts) and say what the fallback hit too.
|
|
1345
|
+
result["error"] = f"{result.get('error')} | aidraftsman fallback: {fallback.get('error')}"
|
|
1346
|
+
return result
|
|
Binary file
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""`run_claude_local` falls back to the AI Draftsman CLI when claude is not working."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from sdk import local_exec
|
|
11
|
+
|
|
12
|
+
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="POSIX script stand-in")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _fake_cli(tmp_path, *, exit_code=0, payload=None, stderr=""):
|
|
16
|
+
"""An `aidraftsman` stand-in: records argv/stdin/cwd, writes a file, prints JSON."""
|
|
17
|
+
record = tmp_path / "record.json"
|
|
18
|
+
script = tmp_path / "aidraftsman"
|
|
19
|
+
script.write_text(f"""#!{sys.executable}
|
|
20
|
+
import json, os, sys
|
|
21
|
+
prompt = sys.stdin.read()
|
|
22
|
+
json.dump({{"argv": sys.argv[1:], "prompt": prompt, "cwd": os.getcwd()}}, open({str(record)!r}, "w"))
|
|
23
|
+
open("made_by_fallback.txt", "w").write("x")
|
|
24
|
+
sys.stderr.write({stderr!r})
|
|
25
|
+
if {payload!r} is not None:
|
|
26
|
+
print(json.dumps({payload!r}))
|
|
27
|
+
sys.exit({exit_code})
|
|
28
|
+
""")
|
|
29
|
+
script.chmod(0o755)
|
|
30
|
+
return script, record
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture()
|
|
34
|
+
def no_claude(monkeypatch):
|
|
35
|
+
monkeypatch.setattr(local_exec, "claude_cli_path", lambda: None)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_falls_back_when_claude_is_missing(tmp_path, monkeypatch, no_claude):
|
|
39
|
+
project = tmp_path / "proj"
|
|
40
|
+
cli, record = _fake_cli(tmp_path, payload={
|
|
41
|
+
"result": "fixed it", "session_id": "ad-1", "agent": "dev",
|
|
42
|
+
"usage": {"prompt_tokens": 7, "completion_tokens": 3, "total_tokens": 10},
|
|
43
|
+
"permission_denials": []})
|
|
44
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
45
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_AGENT", "dev")
|
|
46
|
+
|
|
47
|
+
result = asyncio.run(local_exec.run_claude_local("do the ticket", str(project), tier="t2"))
|
|
48
|
+
|
|
49
|
+
assert result["ok"] is True
|
|
50
|
+
assert result["result"] == "fixed it"
|
|
51
|
+
assert result["total_tokens"] == 10
|
|
52
|
+
assert result["model"] == "aidraftsman:dev"
|
|
53
|
+
assert result["tier"] == "t2"
|
|
54
|
+
seen = json.loads(record.read_text())
|
|
55
|
+
assert seen["prompt"] == "do the ticket"
|
|
56
|
+
assert seen["cwd"] == str(project)
|
|
57
|
+
assert seen["argv"] == ["-p", "--output-format", "json",
|
|
58
|
+
"--permission-mode", "acceptEdits", "--agent", "dev"]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_falls_back_on_usage_limit(tmp_path, monkeypatch):
|
|
62
|
+
cli, _ = _fake_cli(tmp_path, payload={"result": "ok", "usage": {}})
|
|
63
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
64
|
+
|
|
65
|
+
async def _limited(*a, **k):
|
|
66
|
+
return local_exec._result(ok=False, error="usage limit reached",
|
|
67
|
+
limit_exceeded=True, session_id="s-1")
|
|
68
|
+
monkeypatch.setattr(local_exec, "_run_claude_cli", _limited)
|
|
69
|
+
|
|
70
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
71
|
+
assert result["ok"] is True and result["model"] == "aidraftsman:auto"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.parametrize("claude_result", [
|
|
75
|
+
# The task itself failed after claude started — would fail elsewhere too.
|
|
76
|
+
{"ok": False, "error": "tests failed", "session_id": "s-1"},
|
|
77
|
+
{"ok": False, "error": "prompt too long", "context_exceeded": True},
|
|
78
|
+
{"ok": True, "result": "done", "session_id": "s-1"},
|
|
79
|
+
])
|
|
80
|
+
def test_no_fallback_when_claude_itself_worked(tmp_path, monkeypatch, claude_result):
|
|
81
|
+
cli, record = _fake_cli(tmp_path, payload={"result": "ok"})
|
|
82
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
83
|
+
|
|
84
|
+
async def _run(*a, **k):
|
|
85
|
+
return local_exec._result(**claude_result)
|
|
86
|
+
monkeypatch.setattr(local_exec, "_run_claude_cli", _run)
|
|
87
|
+
|
|
88
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
89
|
+
assert result["ok"] is claude_result["ok"]
|
|
90
|
+
assert not record.exists()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_no_fallback_when_resuming_a_claude_session(tmp_path, monkeypatch, no_claude):
|
|
94
|
+
cli, record = _fake_cli(tmp_path, payload={"result": "ok"})
|
|
95
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
96
|
+
|
|
97
|
+
result = asyncio.run(local_exec.run_claude_local(
|
|
98
|
+
"continue", str(tmp_path / "p"), resume_session_id="s-9"))
|
|
99
|
+
assert result["ok"] is False
|
|
100
|
+
assert not record.exists()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_both_failing_keeps_claude_result_and_names_both(tmp_path, monkeypatch, no_claude):
|
|
104
|
+
cli, _ = _fake_cli(tmp_path, exit_code=2, stderr="You are not logged in.")
|
|
105
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
106
|
+
|
|
107
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
108
|
+
assert result["ok"] is False
|
|
109
|
+
assert "Claude Code CLI not found" in result["error"]
|
|
110
|
+
assert "aidraftsman fallback: You are not logged in." in result["error"]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_off_disables_the_fallback(monkeypatch):
|
|
114
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", "off")
|
|
115
|
+
assert local_exec.fallback_cli_path() is None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_files_changed_from_git(tmp_path, monkeypatch, no_claude):
|
|
119
|
+
import subprocess
|
|
120
|
+
project = tmp_path / "proj"
|
|
121
|
+
project.mkdir()
|
|
122
|
+
subprocess.run(["git", "init", "-q"], cwd=project, check=True)
|
|
123
|
+
cli, _ = _fake_cli(tmp_path, payload={"result": "ok"})
|
|
124
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
125
|
+
|
|
126
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(project)))
|
|
127
|
+
assert result["files_changed"] == [str(project / "made_by_fallback.txt")]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_tarball_is_bundled():
|
|
131
|
+
assert local_exec._bundled_tarball() is not None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_installs_bundled_cli_when_none_on_path(tmp_path, monkeypatch, no_claude):
|
|
135
|
+
"""No CLI on PATH → npm-install the bundled tarball into ~/.agentos, then run it."""
|
|
136
|
+
cli, record = _fake_cli(tmp_path, payload={"result": "from bundle"})
|
|
137
|
+
npm_log = tmp_path / "npm.json"
|
|
138
|
+
npm = tmp_path / "npm"
|
|
139
|
+
npm.write_text(f"""#!{sys.executable}
|
|
140
|
+
import json, os, sys
|
|
141
|
+
args = sys.argv[1:]
|
|
142
|
+
json.dump(args, open({str(npm_log)!r}, "w"))
|
|
143
|
+
bin_dir = os.path.join(args[args.index("--prefix") + 1], "node_modules", ".bin")
|
|
144
|
+
os.makedirs(bin_dir)
|
|
145
|
+
os.symlink({str(cli)!r}, os.path.join(bin_dir, "aidraftsman"))
|
|
146
|
+
""")
|
|
147
|
+
npm.chmod(0o755)
|
|
148
|
+
monkeypatch.delenv("AGENTOS_FALLBACK_CLI")
|
|
149
|
+
monkeypatch.setattr(local_exec, "_setting", lambda key: "")
|
|
150
|
+
monkeypatch.setattr(local_exec.config, "_CONFIG_DIR", tmp_path / ".agentos")
|
|
151
|
+
monkeypatch.setattr(local_exec.shutil, "which",
|
|
152
|
+
lambda name: str(npm) if name == "npm" else None)
|
|
153
|
+
|
|
154
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
155
|
+
|
|
156
|
+
assert result["ok"] is True and result["result"] == "from bundle"
|
|
157
|
+
args = json.loads(npm_log.read_text())
|
|
158
|
+
assert args[-1] == str(local_exec._bundled_tarball())
|
|
159
|
+
assert str(tmp_path / ".agentos" / "aidraftsman") in args[args.index("--prefix") + 1]
|
|
160
|
+
# Second run finds the installed copy without calling npm again.
|
|
161
|
+
npm_log.unlink()
|
|
162
|
+
assert local_exec.fallback_cli_path().startswith(str(tmp_path / ".agentos"))
|
|
163
|
+
asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
164
|
+
assert not npm_log.exists()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_no_npm_reports_why(tmp_path, monkeypatch, no_claude):
|
|
168
|
+
monkeypatch.delenv("AGENTOS_FALLBACK_CLI")
|
|
169
|
+
monkeypatch.setattr(local_exec, "_setting", lambda key: "")
|
|
170
|
+
monkeypatch.setattr(local_exec.config, "_CONFIG_DIR", tmp_path / ".agentos")
|
|
171
|
+
monkeypatch.setattr(local_exec.shutil, "which", lambda name: None)
|
|
172
|
+
|
|
173
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
174
|
+
assert result["ok"] is False
|
|
175
|
+
assert "Node.js 20+" in result["error"]
|
|
@@ -177,6 +177,18 @@ class TestResolveProjectPath:
|
|
|
177
177
|
# the fact the person fixing the ticket needs.
|
|
178
178
|
assert "/home/nobody/definitely-missing" in (got.unresolved or "")
|
|
179
179
|
|
|
180
|
+
def test_a_web_link_is_not_a_drive_path(self, tmp_path):
|
|
181
|
+
"""`https://host/...` once read as drive S: and the ticket was refused."""
|
|
182
|
+
got = handlers.resolve_project_path(
|
|
183
|
+
_task(humanInstructions=(
|
|
184
|
+
"Extract all item statements verbatim from the standard at "
|
|
185
|
+
"https://skillsengland.education.gov.uk/apprenticeships/[ST0890]. "
|
|
186
|
+
"Map each item's recommended AI capabilities.")),
|
|
187
|
+
{"workspace_root": str(tmp_path)},
|
|
188
|
+
)
|
|
189
|
+
assert got.source == "workspace-root"
|
|
190
|
+
assert got.unresolved is None
|
|
191
|
+
|
|
180
192
|
def test_an_explicit_override_wins(self, tmp_path):
|
|
181
193
|
got = handlers.resolve_project_path(
|
|
182
194
|
_task(humanInstructions="Work in /somewhere/else"),
|
|
File without changes
|
{devops_bot_sdk-1.6.54 → devops_bot_sdk-1.6.56}/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
|