devops-bot-sdk 1.7.48__tar.gz → 1.7.50__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.7.48 → devops_bot_sdk-1.7.50}/PKG-INFO +1 -1
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/SOURCES.txt +2 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/areas.json +9 -9
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/releases.json +10 -0
- devops_bot_sdk-1.7.50/sdk/help_data/snapshot.json +17 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ipc/handlers.py +20 -4
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/local_exec.py +146 -10
- devops_bot_sdk-1.7.50/tests/test_fallback_folder_changes.py +39 -0
- devops_bot_sdk-1.7.50/tests/test_fallback_parity.py +127 -0
- devops_bot_sdk-1.7.48/sdk/help_data/snapshot.json +0 -17
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/README.md +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/pyproject.toml +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agent_sharing.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/claude_usage.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/client.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/config.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/custom_agents.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/deferred_tickets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/fallback_handoff.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/faqs.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_actions.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/commands.json +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/help.json +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/pages.json +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/helpdesk.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/helpdesk_kb.md +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/install_marker.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/instructions_template.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/log_colors.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/model_policy.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/router.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/schedule_tickets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/schedule_timeline.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/scope_plan.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/test.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/test_env.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_board.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_bulk.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_plan.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_pools.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/tickets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/faqs.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/help.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/vps_identity.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/setup.cfg +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_claude_runs.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_live_tracking.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_crud_cli.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_sharing.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_approval_park.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_auto_ticket_queue.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_backend_only_allowlist.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_claude_usage_gate.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_create_safe_retry.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_deferred_tickets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_fallback_cli.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_fallback_cli_upgrade.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_fallback_rate_limit.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_faqs.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graph_off_by_default.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_help_actions.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_help_attachments.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_help_questions.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_helpdesk.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_infographic_data.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_install_marker.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_instructions_template.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_limit_fallback_handoff.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_log_colors_backlog.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_model_policy.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_panel_help.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_panel_ticket_schedules.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_parallel_tickets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_plan_ahead.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_pool_pr_modes.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_quiet_run_log.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_tickets.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_timeline.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_scheduler_transient_retry.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_theme_switch.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_board.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_bulk.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_cli.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_detail_view.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_plan.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_pools.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_schedule_and_agent.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_bruteforce_guard.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_no_native_dialogs.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_ignores_cwd.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_version_flag.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_vps_only_reporting.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_panel_task.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_stop_and_pidfile.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_task_paths.py +0 -0
- {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_write_hook.py +0 -0
|
@@ -210,6 +210,8 @@ tests/test_epic_session_isolation.py
|
|
|
210
210
|
tests/test_estimator.py
|
|
211
211
|
tests/test_fallback_cli.py
|
|
212
212
|
tests/test_fallback_cli_upgrade.py
|
|
213
|
+
tests/test_fallback_folder_changes.py
|
|
214
|
+
tests/test_fallback_parity.py
|
|
213
215
|
tests/test_fallback_rate_limit.py
|
|
214
216
|
tests/test_faqs.py
|
|
215
217
|
tests/test_firewall_open_on_update.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.50
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -34,7 +34,7 @@ Rules:
|
|
|
34
34
|
- All data egress through submit_webhook only
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "1.7.
|
|
37
|
+
__version__ = "1.7.50" # 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
|
|
@@ -35,6 +35,15 @@
|
|
|
35
35
|
"latest_pr": 183,
|
|
36
36
|
"latest_date": "2026-10-28"
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
"area": "AI fallback",
|
|
40
|
+
"changes_30d": 4,
|
|
41
|
+
"features_30d": 2,
|
|
42
|
+
"fixes_30d": 2,
|
|
43
|
+
"latest": "AI Draftsman fallback resumes, fails loudly when blocked, sees non-git edits",
|
|
44
|
+
"latest_pr": 192,
|
|
45
|
+
"latest_date": "2026-11-04"
|
|
46
|
+
},
|
|
38
47
|
{
|
|
39
48
|
"area": "Windows",
|
|
40
49
|
"changes_30d": 4,
|
|
@@ -44,15 +53,6 @@
|
|
|
44
53
|
"latest_pr": 165,
|
|
45
54
|
"latest_date": "2026-10-24"
|
|
46
55
|
},
|
|
47
|
-
{
|
|
48
|
-
"area": "AI fallback",
|
|
49
|
-
"changes_30d": 3,
|
|
50
|
-
"features_30d": 2,
|
|
51
|
-
"fixes_30d": 1,
|
|
52
|
-
"latest": "continue a limit-hit ticket on the AI Draftsman CLI with its context",
|
|
53
|
-
"latest_pr": 164,
|
|
54
|
-
"latest_date": "2026-10-24"
|
|
55
|
-
},
|
|
56
56
|
{
|
|
57
57
|
"area": "Agents",
|
|
58
58
|
"changes_30d": 2,
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"pr": 192,
|
|
4
|
+
"date": "2026-11-04",
|
|
5
|
+
"merged_at": "2026-11-04T14:03:12+05:00",
|
|
6
|
+
"type": "Fix",
|
|
7
|
+
"area": "AI fallback",
|
|
8
|
+
"title": "AI Draftsman fallback resumes, fails loudly when blocked, sees non-git edits",
|
|
9
|
+
"version": "1.7.50",
|
|
10
|
+
"url": "https://github.com/consultancy-outfit/DevOpsBot-SDK/pull/192"
|
|
11
|
+
},
|
|
2
12
|
{
|
|
3
13
|
"pr": 191,
|
|
4
14
|
"date": "2026-11-03",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.7.50",
|
|
4
|
+
"commit": "4f370e5",
|
|
5
|
+
"pushed_at": "2026-11-04T14:03:12+05:00",
|
|
6
|
+
"generated_at": "2026-11-04T09:04:20+00:00",
|
|
7
|
+
"commands": 60,
|
|
8
|
+
"commands_invalid": 0,
|
|
9
|
+
"commands_undocumented": 0,
|
|
10
|
+
"panel_pages": 11,
|
|
11
|
+
"tests": 2624,
|
|
12
|
+
"prs_7d": 10,
|
|
13
|
+
"prs_30d": 44,
|
|
14
|
+
"features_30d": 13,
|
|
15
|
+
"fixes_30d": 19
|
|
16
|
+
}
|
|
17
|
+
]
|
|
@@ -3125,7 +3125,10 @@ async def _code_one_repo(
|
|
|
3125
3125
|
and not result.get("auth_expired"):
|
|
3126
3126
|
errored = not result.get("ok")
|
|
3127
3127
|
# "did real work happen?" — actual Write/Edit count, not files merely read.
|
|
3128
|
-
|
|
3128
|
+
# `ad` (the fallback CLI) streams no tool events, so _write_count stays
|
|
3129
|
+
# 0 on it — its folder diff (files_changed) is the evidence there.
|
|
3130
|
+
empty = (result.get("ok") and _write_count == 0
|
|
3131
|
+
and not result.get("files_changed"))
|
|
3129
3132
|
if not (errored or empty):
|
|
3130
3133
|
break
|
|
3131
3134
|
repair += 1
|
|
@@ -3300,8 +3303,13 @@ async def _code_one_repo(
|
|
|
3300
3303
|
# have errored out.
|
|
3301
3304
|
if not result.get("ok"):
|
|
3302
3305
|
issues.append(f"run errored: {(result.get('error') or 'failed')[:160]}")
|
|
3303
|
-
elif _write_count == 0:
|
|
3304
|
-
|
|
3306
|
+
elif _write_count == 0 and not result.get("files_changed"):
|
|
3307
|
+
# Say what the agent itself reported: "produced no files" alone gave
|
|
3308
|
+
# the operator nothing to act on (e.g. a fallback run that could not
|
|
3309
|
+
# run the script needed to edit a binary .xlsx).
|
|
3310
|
+
said = " ".join(str(result.get("result") or "").split())
|
|
3311
|
+
issues.append("task produced no files/deliverables"
|
|
3312
|
+
+ (f" — agent said: {said[-240:]}" if said else ""))
|
|
3305
3313
|
if issues:
|
|
3306
3314
|
await send(Envelope(
|
|
3307
3315
|
type="step_update", thread_id=thread_id,
|
|
@@ -3556,7 +3564,10 @@ async def handle_pipeline_approve(
|
|
|
3556
3564
|
type=Envelope.TYPE_ERROR, thread_id=thread_id,
|
|
3557
3565
|
data={"code": "LOCAL_EXEC_INCOMPLETE", "message": err[:300], "task_id": task_id},
|
|
3558
3566
|
).model_dump())
|
|
3559
|
-
|
|
3567
|
+
# Tell the ticket loop too: it used to see None here, count the ticket as
|
|
3568
|
+
# carried through, and print "✓ completed — 1/1" under a ticket it had
|
|
3569
|
+
# just moved to Backlog.
|
|
3570
|
+
return {"status": "incomplete", "task_id": task_id, "error": err[:300]}
|
|
3560
3571
|
|
|
3561
3572
|
# Surface deliberate no-ops so a skipped repo is transparent, not invisible.
|
|
3562
3573
|
if out_of_scope:
|
|
@@ -5817,6 +5828,11 @@ async def _run_one_task_steps(
|
|
|
5817
5828
|
send,
|
|
5818
5829
|
)
|
|
5819
5830
|
|
|
5831
|
+
# Ran, but non-compliant (no deliverable, no push/PR …): the handler
|
|
5832
|
+
# already moved it to Backlog and said why — it was NOT carried through.
|
|
5833
|
+
if isinstance(outcome, dict) and outcome.get("status") == "incomplete":
|
|
5834
|
+
return "skipped"
|
|
5835
|
+
|
|
5820
5836
|
# ── Claude CLI auth expired ────────────────────────────────────
|
|
5821
5837
|
# The runner's `claude` login lapsed. Waiting won't fix it, so we
|
|
5822
5838
|
# PAUSE (not drain-to-Backlog): keep the ticket In Progress, alert
|
|
@@ -1077,19 +1077,46 @@ async def _run_claude_cli(
|
|
|
1077
1077
|
# AD_API_KEY in the env. `agentos fallback status --verify` checks it against
|
|
1078
1078
|
# the server.
|
|
1079
1079
|
#
|
|
1080
|
-
#
|
|
1081
|
-
#
|
|
1082
|
-
#
|
|
1083
|
-
#
|
|
1084
|
-
#
|
|
1080
|
+
# Sessions: an `ad` run's session id comes back as ``ad:<id>``, so every caller
|
|
1081
|
+
# that resumes a run (the repair loop, a usage-limit resume, recon follow-ups)
|
|
1082
|
+
# resumes it on `ad` with ``ad -p -r <id>`` — never on claude, which has no such
|
|
1083
|
+
# session. A CLAUDE session is still never resumed on `ad` (the prompt would be
|
|
1084
|
+
# "continue" with no context behind it).
|
|
1085
|
+
#
|
|
1086
|
+
# What it does NOT do yet: honour the PreToolUse approval guard or stream
|
|
1087
|
+
# progress events (that needs `ad` itself to grow `stream-json`, `--allowedTools`
|
|
1088
|
+
# and a pre-tool hook). So the permission mode defaults to acceptEdits — file
|
|
1089
|
+
# edits apply, but a shell command that is not read-only, and every web fetch,
|
|
1090
|
+
# is denied rather than run unguarded. A run that was denied what it needed and
|
|
1091
|
+
# produced nothing fails LOUDLY with the denied calls named (`ad` reports them
|
|
1092
|
+
# as `permission_denials`), instead of "ran fine, made nothing". Set
|
|
1085
1093
|
# AGENTOS_FALLBACK_PERMISSION_MODE=bypass to let it run every command.
|
|
1086
1094
|
#
|
|
1087
1095
|
# Settings (process env or ~/.agentos/.env):
|
|
1096
|
+
# AGENTOS_ENGINE claude (default) | ad — `ad` runs every fresh
|
|
1097
|
+
# run on the AI Draftsman CLI first, for a box
|
|
1098
|
+
# with no Claude login at all
|
|
1088
1099
|
# AGENTOS_FALLBACK_CLI path to the CLI (default: `ad` on PATH), or "off"
|
|
1089
1100
|
# AGENTOS_FALLBACK_AGENT agent id/name for `--agent` (default: gateway's choice)
|
|
1101
|
+
# AGENTOS_FALLBACK_MODEL model slug for `--model` (overrides the agent)
|
|
1090
1102
|
# AGENTOS_FALLBACK_PERMISSION_MODE default | acceptEdits | plan | bypass
|
|
1091
1103
|
|
|
1092
1104
|
_FALLBACK_MODES = ("default", "acceptEdits", "plan", "bypass")
|
|
1105
|
+
#: Marks a session id as an `ad` one, so a resume goes back to `ad`.
|
|
1106
|
+
AD_SESSION_PREFIX = "ad:"
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
def ad_session_id(session_id: str | None) -> str | None:
|
|
1110
|
+
"""The raw `ad` session id inside ``ad:<id>``, or None for a claude session."""
|
|
1111
|
+
if session_id and session_id.startswith(AD_SESSION_PREFIX):
|
|
1112
|
+
return session_id[len(AD_SESSION_PREFIX):] or None
|
|
1113
|
+
return None
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
def engine() -> str:
|
|
1117
|
+
"""``"ad"`` when AGENTOS_ENGINE asks for the AI Draftsman CLI first, else ``"claude"``."""
|
|
1118
|
+
value = _setting("AGENTOS_ENGINE").lower()
|
|
1119
|
+
return "ad" if value in ("ad", "aidraftsman", "ai-draftsman", "fallback") else "claude"
|
|
1093
1120
|
|
|
1094
1121
|
|
|
1095
1122
|
def _setting(key: str) -> str:
|
|
@@ -1440,6 +1467,48 @@ def _git_changed_files(path: Path) -> set[str] | None:
|
|
|
1440
1467
|
return out
|
|
1441
1468
|
|
|
1442
1469
|
|
|
1470
|
+
# Folders a working-folder snapshot never looks in: VCS/tool state, caches and the
|
|
1471
|
+
# graphify index the SDK itself rebuilds — a change there is not a deliverable.
|
|
1472
|
+
_SNAPSHOT_SKIP_DIRS = {"node_modules", "graphify-out", "__pycache__", "venv", ".venv"}
|
|
1473
|
+
_SNAPSHOT_MAX_FILES = 20000
|
|
1474
|
+
|
|
1475
|
+
|
|
1476
|
+
def _folder_snapshot(path: Path) -> dict[str, tuple[int, int]] | None:
|
|
1477
|
+
"""``{file: (mtime_ns, size)}`` for a plain (non-git) working folder.
|
|
1478
|
+
|
|
1479
|
+
`_git_changed_files` answers None outside a repo, which left a fallback run
|
|
1480
|
+
in a documentation / analyst folder with NO record of what it wrote — so a
|
|
1481
|
+
task that updated a workbook in place was judged "produced no files". This
|
|
1482
|
+
is the non-git equivalent. None when the folder is too big to walk cheaply.
|
|
1483
|
+
"""
|
|
1484
|
+
out: dict[str, tuple[int, int]] = {}
|
|
1485
|
+
try:
|
|
1486
|
+
for root, dirs, files in os.walk(path):
|
|
1487
|
+
dirs[:] = [d for d in dirs if not d.startswith(".") and d not in _SNAPSHOT_SKIP_DIRS]
|
|
1488
|
+
for f in files:
|
|
1489
|
+
if f.startswith("~$"): # Office lock file, not a deliverable
|
|
1490
|
+
continue
|
|
1491
|
+
fp = os.path.join(root, f)
|
|
1492
|
+
try:
|
|
1493
|
+
st = os.stat(fp)
|
|
1494
|
+
except OSError:
|
|
1495
|
+
continue
|
|
1496
|
+
out[fp] = (st.st_mtime_ns, st.st_size)
|
|
1497
|
+
if len(out) > _SNAPSHOT_MAX_FILES:
|
|
1498
|
+
return None
|
|
1499
|
+
except OSError:
|
|
1500
|
+
return None
|
|
1501
|
+
return out
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
def _snapshot_changes(before: dict[str, tuple[int, int]] | None,
|
|
1505
|
+
after: dict[str, tuple[int, int]] | None) -> list[str]:
|
|
1506
|
+
"""Files created or modified between two `_folder_snapshot`s."""
|
|
1507
|
+
if before is None or after is None:
|
|
1508
|
+
return []
|
|
1509
|
+
return sorted(fp for fp, sig in after.items() if before.get(fp) != sig)
|
|
1510
|
+
|
|
1511
|
+
|
|
1443
1512
|
# A 429 / RESOURCE_EXHAUSTED from the AI Draftsman gateway is a "slow down", not
|
|
1444
1513
|
# a failure: `ad` (≥ 1.1.2) retries a few in-stream frames itself, but when it
|
|
1445
1514
|
# gives up the whole run used to fail — a ticket went to Backlog, `agentos help`
|
|
@@ -1479,12 +1548,17 @@ async def run_fallback_cli(
|
|
|
1479
1548
|
extra_env: dict[str, str] | None = None,
|
|
1480
1549
|
tier: str | None = None,
|
|
1481
1550
|
provider: str | None = None,
|
|
1551
|
+
resume_session_id: str | None = None,
|
|
1482
1552
|
_upgraded: bool = False,
|
|
1483
1553
|
_rl_attempt: int = 0,
|
|
1484
1554
|
_rl_waited: float = 0.0,
|
|
1485
1555
|
) -> dict[str, Any]:
|
|
1486
1556
|
"""Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run.
|
|
1487
1557
|
|
|
1558
|
+
``resume_session_id`` is an ``ad:<id>`` from an earlier `ad` run: the prompt
|
|
1559
|
+
continues that conversation (``-r <id>``). The result's ``session_id`` is
|
|
1560
|
+
``ad:<id>`` too, so the caller's repair / resume paths come back here.
|
|
1561
|
+
|
|
1488
1562
|
An outdated `ad` fails its first request (its tool schema is rejected) before
|
|
1489
1563
|
touching anything, so that one failure upgrades the CLI and runs again, once.
|
|
1490
1564
|
A rate limit (429 / RESOURCE_EXHAUSTED) waits and runs again — see above.
|
|
@@ -1502,9 +1576,15 @@ async def run_fallback_cli(
|
|
|
1502
1576
|
if mode not in _FALLBACK_MODES:
|
|
1503
1577
|
mode = "acceptEdits"
|
|
1504
1578
|
args = ["-p", "--output-format", "json", "--permission-mode", mode]
|
|
1579
|
+
fb_model = _setting("AGENTOS_FALLBACK_MODEL")
|
|
1505
1580
|
agent = _setting("AGENTOS_FALLBACK_AGENT")
|
|
1506
|
-
if
|
|
1581
|
+
if fb_model:
|
|
1582
|
+
args += ["--model", fb_model]
|
|
1583
|
+
elif agent:
|
|
1507
1584
|
args += ["--agent", agent]
|
|
1585
|
+
ad_resume = ad_session_id(resume_session_id)
|
|
1586
|
+
if ad_resume:
|
|
1587
|
+
args += ["-r", ad_resume]
|
|
1508
1588
|
cmd = _fallback_argv(cli, *args)
|
|
1509
1589
|
|
|
1510
1590
|
env = dict(os.environ)
|
|
@@ -1520,7 +1600,11 @@ async def run_fallback_cli(
|
|
|
1520
1600
|
env["GIT_CONFIG_VALUE_0"] = "https://github.com/"
|
|
1521
1601
|
|
|
1522
1602
|
before = _git_changed_files(path)
|
|
1523
|
-
|
|
1603
|
+
# Not a git repo: track the folder's files directly, so an in-place edit of
|
|
1604
|
+
# a workbook / document still counts as a deliverable.
|
|
1605
|
+
snap_before = _folder_snapshot(path) if before is None else None
|
|
1606
|
+
print(f"aidraftsman fallback spawn | mode={mode}"
|
|
1607
|
+
+ (f" | resume {ad_resume}" if ad_resume else "") + f" | {path}", flush=True)
|
|
1524
1608
|
proc = await asyncio.create_subprocess_exec(
|
|
1525
1609
|
*cmd, cwd=str(path), env=env,
|
|
1526
1610
|
stdin=asyncio.subprocess.PIPE,
|
|
@@ -1542,7 +1626,10 @@ async def run_fallback_cli(
|
|
|
1542
1626
|
raise
|
|
1543
1627
|
|
|
1544
1628
|
after = _git_changed_files(path)
|
|
1545
|
-
|
|
1629
|
+
if before is not None and after is not None:
|
|
1630
|
+
files_changed = sorted(after - before)
|
|
1631
|
+
else:
|
|
1632
|
+
files_changed = _snapshot_changes(snap_before, _folder_snapshot(path))
|
|
1546
1633
|
err_text = stderr.decode("utf-8", errors="replace").strip()
|
|
1547
1634
|
|
|
1548
1635
|
data: dict[str, Any] = {}
|
|
@@ -1563,7 +1650,8 @@ async def run_fallback_cli(
|
|
|
1563
1650
|
if done.startswith("AI Draftsman CLI updated"):
|
|
1564
1651
|
return await run_fallback_cli(
|
|
1565
1652
|
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1566
|
-
extra_env=extra_env, tier=tier, provider=provider,
|
|
1653
|
+
extra_env=extra_env, tier=tier, provider=provider,
|
|
1654
|
+
resume_session_id=resume_session_id, _upgraded=True)
|
|
1567
1655
|
err += f" — the AI Draftsman CLI is outdated; run `{FALLBACK_INSTALL_HINT}`"
|
|
1568
1656
|
auth_expired = proc.returncode == 2 or _is_auth_error(err)
|
|
1569
1657
|
if not auth_expired and _is_fallback_rate_limited(err):
|
|
@@ -1577,7 +1665,8 @@ async def run_fallback_cli(
|
|
|
1577
1665
|
await asyncio.sleep(wait)
|
|
1578
1666
|
return await run_fallback_cli(
|
|
1579
1667
|
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1580
|
-
extra_env=extra_env, tier=tier, provider=provider,
|
|
1668
|
+
extra_env=extra_env, tier=tier, provider=provider,
|
|
1669
|
+
resume_session_id=resume_session_id, _upgraded=_upgraded,
|
|
1581
1670
|
_rl_attempt=attempt, _rl_waited=_rl_waited + wait)
|
|
1582
1671
|
err += (f" — still rate-limited after {_rl_attempt} retr{'y' if _rl_attempt == 1 else 'ies'} "
|
|
1583
1672
|
f"and {_rl_waited / 60:.0f} min of waiting")
|
|
@@ -1591,8 +1680,33 @@ async def run_fallback_cli(
|
|
|
1591
1680
|
in_tok = int(usage.get("prompt_tokens", 0) or 0)
|
|
1592
1681
|
out_tok = int(usage.get("completion_tokens", 0) or 0)
|
|
1593
1682
|
text = str(data.get("result") or "")
|
|
1683
|
+
sid = str(data.get("session_id") or "")
|
|
1684
|
+
session_id = f"{AD_SESSION_PREFIX}{sid}" if sid else None
|
|
1685
|
+
denied = [str(d) for d in (data.get("permission_denials") or []) if d]
|
|
1686
|
+
if denied:
|
|
1687
|
+
print(f"aidraftsman was denied {len(denied)} call(s) in {mode} mode: "
|
|
1688
|
+
+ "; ".join(d[:120] for d in denied[:5]), flush=True)
|
|
1689
|
+
if denied and not files_changed:
|
|
1690
|
+
# `ad -p` exits 0 after a denial and answers around it, which read as
|
|
1691
|
+
# "ran fine, made nothing" (MWM-217: every web fetch and the Python
|
|
1692
|
+
# that edits the .xlsx were refused). Name what was blocked and how to
|
|
1693
|
+
# allow it — this is the failure, not the task.
|
|
1694
|
+
shown = "; ".join(d[:120] for d in denied[:5])
|
|
1695
|
+
more = f" (+{len(denied) - 5} more)" if len(denied) > 5 else ""
|
|
1696
|
+
return _result(
|
|
1697
|
+
ok=False,
|
|
1698
|
+
error=(f"AI Draftsman was blocked from {len(denied)} action(s) it needed "
|
|
1699
|
+
f"in {mode} mode and produced nothing: {shown}{more} — set "
|
|
1700
|
+
"AGENTOS_FALLBACK_PERMISSION_MODE=bypass to allow shell commands "
|
|
1701
|
+
"and web fetches, or log Claude in"),
|
|
1702
|
+
result=text, longest_text=text, session_id=session_id,
|
|
1703
|
+
input_tokens=in_tok, output_tokens=out_tok,
|
|
1704
|
+
total_tokens=int(usage.get("total_tokens", 0) or 0) or in_tok + out_tok,
|
|
1705
|
+
tier=tier, provider=provider, model="aidraftsman",
|
|
1706
|
+
)
|
|
1594
1707
|
return _result(
|
|
1595
1708
|
ok=True,
|
|
1709
|
+
session_id=session_id,
|
|
1596
1710
|
result=text,
|
|
1597
1711
|
longest_text=text,
|
|
1598
1712
|
input_tokens=in_tok,
|
|
@@ -1715,6 +1829,20 @@ async def _run_claude_local(
|
|
|
1715
1829
|
if force_fallback:
|
|
1716
1830
|
print("claude usage limit — continuing this ticket on the AI Draftsman CLI",
|
|
1717
1831
|
flush=True)
|
|
1832
|
+
return await run_fallback_cli(
|
|
1833
|
+
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1834
|
+
extra_env=extra_env, tier=tier, provider=provider,
|
|
1835
|
+
resume_session_id=resume_session_id if ad_session_id(resume_session_id) else None,
|
|
1836
|
+
)
|
|
1837
|
+
if ad_session_id(resume_session_id):
|
|
1838
|
+
# The session being continued is an `ad` one: it only exists there.
|
|
1839
|
+
return await run_fallback_cli(
|
|
1840
|
+
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1841
|
+
extra_env=extra_env, tier=tier, provider=provider,
|
|
1842
|
+
resume_session_id=resume_session_id,
|
|
1843
|
+
)
|
|
1844
|
+
if not resume_session_id and engine() == "ad":
|
|
1845
|
+
print("AGENTOS_ENGINE=ad — running on the AI Draftsman CLI", flush=True)
|
|
1718
1846
|
return await run_fallback_cli(
|
|
1719
1847
|
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1720
1848
|
extra_env=extra_env, tier=tier, provider=provider,
|
|
@@ -1747,6 +1875,14 @@ async def _run_claude_local(
|
|
|
1747
1875
|
)
|
|
1748
1876
|
if fallback["ok"]:
|
|
1749
1877
|
return fallback
|
|
1878
|
+
if fallback.get("session_id") and not fallback.get("auth_expired") \
|
|
1879
|
+
and not fallback.get("limit_exceeded"):
|
|
1880
|
+
# `ad` really ran and failed on its own terms (e.g. blocked actions):
|
|
1881
|
+
# that is the actionable reason, and its session can be repaired. Keep
|
|
1882
|
+
# claude's reason alongside it.
|
|
1883
|
+
fallback["error"] = (f"{fallback.get('error')} | claude: "
|
|
1884
|
+
f"{(result.get('error') or 'not working')[:160]}")
|
|
1885
|
+
return fallback
|
|
1750
1886
|
# Both failed: keep the claude result (its limit/auth flags drive the
|
|
1751
1887
|
# caller's parking and alerts) and say what the fallback hit too.
|
|
1752
1888
|
result["error"] = f"{result.get('error')} | aidraftsman fallback: {fallback.get('error')}"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""A fallback (`ad`) run in a plain, non-git working folder must still report
|
|
2
|
+
the files it changed — otherwise an in-place workbook edit is judged "task
|
|
3
|
+
produced no files/deliverables" and the ticket goes to Backlog (MWM-217)."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from sdk import local_exec
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_in_place_edit_and_new_file_are_seen(tmp_path):
|
|
12
|
+
wb = tmp_path / "UK_Shopping_Centres_list_Review_v3.xlsx"
|
|
13
|
+
wb.write_bytes(b"old")
|
|
14
|
+
untouched = tmp_path / "notes.txt"
|
|
15
|
+
untouched.write_text("same")
|
|
16
|
+
before = local_exec._folder_snapshot(tmp_path)
|
|
17
|
+
|
|
18
|
+
wb.write_bytes(b"new and longer")
|
|
19
|
+
st = os.stat(wb)
|
|
20
|
+
os.utime(wb, ns=(st.st_atime_ns, st.st_mtime_ns + 1_000_000_000))
|
|
21
|
+
(tmp_path / "summary.md").write_text("done")
|
|
22
|
+
|
|
23
|
+
changed = local_exec._snapshot_changes(before, local_exec._folder_snapshot(tmp_path))
|
|
24
|
+
assert changed == sorted([str(wb), str(tmp_path / "summary.md")])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_tool_state_and_office_lock_files_are_not_deliverables(tmp_path):
|
|
28
|
+
before = local_exec._folder_snapshot(tmp_path)
|
|
29
|
+
(tmp_path / "graphify-out").mkdir()
|
|
30
|
+
(tmp_path / "graphify-out" / "graph.json").write_text("{}")
|
|
31
|
+
(tmp_path / ".claude").mkdir()
|
|
32
|
+
(tmp_path / ".claude" / "x").write_text("x")
|
|
33
|
+
(tmp_path / "~$Book.xlsx").write_text("lock")
|
|
34
|
+
assert local_exec._snapshot_changes(before, local_exec._folder_snapshot(tmp_path)) == []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_no_snapshot_means_no_claim(tmp_path):
|
|
38
|
+
assert local_exec._snapshot_changes(None, {}) == []
|
|
39
|
+
assert local_exec._snapshot_changes({}, None) == []
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""The AI Draftsman CLI (`ad`) as a full fallback: its sessions resume on `ad`,
|
|
2
|
+
blocked actions fail loudly, AGENTOS_ENGINE=ad runs it first, and
|
|
3
|
+
AGENTOS_FALLBACK_MODEL picks its model."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from sdk import local_exec
|
|
13
|
+
|
|
14
|
+
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="POSIX script stand-in")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _fake_ad(tmp_path, payload, *, write=True):
|
|
18
|
+
"""An `ad` stand-in: records argv, optionally writes a file, prints ``payload``."""
|
|
19
|
+
record = tmp_path / "record.json"
|
|
20
|
+
script = tmp_path / "ad"
|
|
21
|
+
script.write_text(f"""#!{sys.executable}
|
|
22
|
+
import json, os, sys
|
|
23
|
+
sys.stdin.read()
|
|
24
|
+
json.dump({{"argv": sys.argv[1:]}}, open({str(record)!r}, "w"))
|
|
25
|
+
if {write!r}:
|
|
26
|
+
open("out.txt", "w").write("x")
|
|
27
|
+
print(json.dumps({payload!r}))
|
|
28
|
+
""")
|
|
29
|
+
script.chmod(0o755)
|
|
30
|
+
return script, record
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _claude_must_not_run(monkeypatch):
|
|
34
|
+
async def _boom(*a, **k):
|
|
35
|
+
raise AssertionError("claude must not be spawned")
|
|
36
|
+
monkeypatch.setattr(local_exec, "_run_claude_cli", _boom)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_an_ad_run_returns_a_resumable_ad_session(tmp_path, monkeypatch):
|
|
40
|
+
cli, _ = _fake_ad(tmp_path, {"result": "ok", "session_id": "abc123", "usage": {}})
|
|
41
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
42
|
+
result = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
|
|
43
|
+
assert result["ok"] is True
|
|
44
|
+
assert result["session_id"] == "ad:abc123"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_an_ad_session_resumes_on_ad_not_claude(tmp_path, monkeypatch):
|
|
48
|
+
cli, record = _fake_ad(tmp_path, {"result": "ok", "session_id": "abc123", "usage": {}})
|
|
49
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
50
|
+
_claude_must_not_run(monkeypatch)
|
|
51
|
+
result = asyncio.run(local_exec.run_claude_local(
|
|
52
|
+
"fix the failing step", str(tmp_path / "p"), resume_session_id="ad:abc123"))
|
|
53
|
+
assert result["ok"] is True
|
|
54
|
+
argv = json.loads(record.read_text())["argv"]
|
|
55
|
+
assert argv[-2:] == ["-r", "abc123"]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_engine_ad_runs_ad_first(tmp_path, monkeypatch):
|
|
59
|
+
cli, record = _fake_ad(tmp_path, {"result": "ok", "session_id": "s", "usage": {}})
|
|
60
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
61
|
+
monkeypatch.setenv("AGENTOS_ENGINE", "ad")
|
|
62
|
+
_claude_must_not_run(monkeypatch)
|
|
63
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
64
|
+
assert result["ok"] is True and record.exists()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_default_engine_is_claude(monkeypatch):
|
|
68
|
+
monkeypatch.delenv("AGENTOS_ENGINE", raising=False)
|
|
69
|
+
monkeypatch.setattr(local_exec, "_setting", lambda key: "")
|
|
70
|
+
assert local_exec.engine() == "claude"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_fallback_model_is_passed_instead_of_the_agent(tmp_path, monkeypatch):
|
|
74
|
+
cli, record = _fake_ad(tmp_path, {"result": "ok", "usage": {}})
|
|
75
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
76
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_MODEL", "gemini-2.5-pro")
|
|
77
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_AGENT", "dev")
|
|
78
|
+
asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
|
|
79
|
+
argv = json.loads(record.read_text())["argv"]
|
|
80
|
+
assert "--model" in argv and argv[argv.index("--model") + 1] == "gemini-2.5-pro"
|
|
81
|
+
assert "--agent" not in argv
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_blocked_and_nothing_produced_is_a_loud_failure(tmp_path, monkeypatch):
|
|
85
|
+
"""MWM-217: `ad` exits 0 after its web fetches and the .xlsx script were denied."""
|
|
86
|
+
cli, _ = _fake_ad(tmp_path, {
|
|
87
|
+
"result": "I could not fetch the sources.", "session_id": "s1", "usage": {},
|
|
88
|
+
"permission_denials": ["web_fetch(https://example.org)", "bash(python fill.py)"],
|
|
89
|
+
}, write=False)
|
|
90
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
91
|
+
result = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
|
|
92
|
+
assert result["ok"] is False
|
|
93
|
+
assert "blocked from 2 action(s)" in result["error"]
|
|
94
|
+
assert "bash(python fill.py)" in result["error"]
|
|
95
|
+
assert "AGENTOS_FALLBACK_PERMISSION_MODE=bypass" in result["error"]
|
|
96
|
+
assert result["session_id"] == "ad:s1" # still repairable on `ad`
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_blocked_but_produced_work_still_succeeds(tmp_path, monkeypatch):
|
|
100
|
+
cli, _ = _fake_ad(tmp_path, {
|
|
101
|
+
"result": "done", "session_id": "s1", "usage": {},
|
|
102
|
+
"permission_denials": ["bash(npm test)"],
|
|
103
|
+
})
|
|
104
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
105
|
+
result = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
|
|
106
|
+
assert result["ok"] is True
|
|
107
|
+
assert result["files_changed"]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_logged_out_claude_and_blocked_ad_reports_the_block(tmp_path, monkeypatch):
|
|
111
|
+
"""Both failed, but `ad` really ran: its reason is the one to act on, not a
|
|
112
|
+
pause waiting for a Claude login."""
|
|
113
|
+
cli, _ = _fake_ad(tmp_path, {
|
|
114
|
+
"result": "", "session_id": "s1", "usage": {},
|
|
115
|
+
"permission_denials": ["web_fetch(https://example.org)"],
|
|
116
|
+
}, write=False)
|
|
117
|
+
monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
|
|
118
|
+
|
|
119
|
+
async def _logged_out(*a, **k):
|
|
120
|
+
return local_exec._result(ok=False, error="Not logged in · Please run /login",
|
|
121
|
+
auth_expired=True)
|
|
122
|
+
monkeypatch.setattr(local_exec, "_run_claude_cli", _logged_out)
|
|
123
|
+
result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
|
|
124
|
+
assert result["ok"] is False
|
|
125
|
+
assert result["auth_expired"] is False
|
|
126
|
+
assert "blocked from 1 action(s)" in result["error"]
|
|
127
|
+
assert "claude: Not logged in" in result["error"]
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"version": "1.7.48",
|
|
4
|
-
"commit": "84377c8",
|
|
5
|
-
"pushed_at": "2026-11-03T14:46:05+05:00",
|
|
6
|
-
"generated_at": "2026-11-03T09:47:28+00:00",
|
|
7
|
-
"commands": 60,
|
|
8
|
-
"commands_invalid": 0,
|
|
9
|
-
"commands_undocumented": 0,
|
|
10
|
-
"panel_pages": 11,
|
|
11
|
-
"tests": 2613,
|
|
12
|
-
"prs_7d": 11,
|
|
13
|
-
"prs_30d": 43,
|
|
14
|
-
"features_30d": 13,
|
|
15
|
-
"fixes_30d": 18
|
|
16
|
-
}
|
|
17
|
-
]
|
|
File without changes
|
{devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/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
|