devops-bot-sdk 1.6.75__tar.gz → 1.6.79__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.75 → devops_bot_sdk-1.6.79}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/SOURCES.txt +2 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/cli.py +6 -3
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/local_exec.py +135 -7
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/run_auto.py +9 -9
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/service.py +121 -6
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/updater.py +26 -4
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/vps_identity.py +7 -1
- devops_bot_sdk-1.6.79/tests/test_fallback_cli_upgrade.py +147 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_updater_reports_the_truth.py +57 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_vps_only_reporting.py +27 -0
- devops_bot_sdk-1.6.79/tests/test_windows_panel_task.py +135 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_windows_task_paths.py +10 -1
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/README.md +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/custom_agents.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/tickets.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_claude_runs.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_live_tracking.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_crud_cli.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_fallback_cli.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_scheduler_transient_retry.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_cli.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.75 → devops_bot_sdk-1.6.79}/tests/test_write_hook.py +0 -0
|
@@ -175,6 +175,7 @@ tests/test_epic_inherited_agent_id.py
|
|
|
175
175
|
tests/test_epic_session_isolation.py
|
|
176
176
|
tests/test_estimator.py
|
|
177
177
|
tests/test_fallback_cli.py
|
|
178
|
+
tests/test_fallback_cli_upgrade.py
|
|
178
179
|
tests/test_firewall_open_on_update.py
|
|
179
180
|
tests/test_formats.py
|
|
180
181
|
tests/test_git_action.py
|
|
@@ -230,5 +231,6 @@ tests/test_url_access.py
|
|
|
230
231
|
tests/test_vps_health.py
|
|
231
232
|
tests/test_vps_only_reporting.py
|
|
232
233
|
tests/test_windows_loop_identity.py
|
|
234
|
+
tests/test_windows_panel_task.py
|
|
233
235
|
tests/test_windows_task_paths.py
|
|
234
236
|
tests/test_write_hook.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.79
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -34,7 +34,7 @@ Rules:
|
|
|
34
34
|
- All data egress through submit_webhook only
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "1.6.
|
|
37
|
+
__version__ = "1.6.79" # 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
|
|
@@ -1383,7 +1383,7 @@ def _browser_command(args: list[str]) -> None:
|
|
|
1383
1383
|
# ── fallback ─────────────────────────────────────────────────────────────
|
|
1384
1384
|
|
|
1385
1385
|
def _fallback_usage() -> None:
|
|
1386
|
-
print("usage: agentos fallback install #
|
|
1386
|
+
print("usage: agentos fallback install # install `ad`, or update an outdated one")
|
|
1387
1387
|
print(" agentos fallback login [--browser | --api-key [KEY]] [--base-url URL] [--no-browser]")
|
|
1388
1388
|
print(" agentos fallback status [--verify] # --verify asks the server too")
|
|
1389
1389
|
print(" agentos fallback logout")
|
|
@@ -1411,8 +1411,11 @@ def _fallback_command(args: list[str]) -> None:
|
|
|
1411
1411
|
if le._fallback_disabled():
|
|
1412
1412
|
print("error: the fallback is disabled (AGENTOS_FALLBACK_CLI=off)")
|
|
1413
1413
|
sys.exit(1)
|
|
1414
|
-
#
|
|
1415
|
-
#
|
|
1414
|
+
# Installs the CLI when it is missing, and replaces an outdated one (the old
|
|
1415
|
+
# package name, or a release older than FALLBACK_MIN_VERSION).
|
|
1416
|
+
done = le.upgrade_fallback_cli(install_missing=True)
|
|
1417
|
+
if done:
|
|
1418
|
+
print(done)
|
|
1416
1419
|
cli = le.fallback_cli_path()
|
|
1417
1420
|
if not cli:
|
|
1418
1421
|
print(f"error: the AI Draftsman CLI (`ad`) was not found. Install it with:\n"
|
|
@@ -24,6 +24,7 @@ import re
|
|
|
24
24
|
import shutil
|
|
25
25
|
import subprocess
|
|
26
26
|
import sys
|
|
27
|
+
import time
|
|
27
28
|
from datetime import datetime, timedelta
|
|
28
29
|
from pathlib import Path
|
|
29
30
|
from typing import Any, Awaitable, Callable
|
|
@@ -1047,7 +1048,7 @@ async def _run_claude_cli(
|
|
|
1047
1048
|
# ── Fallback: the AI Draftsman CLI ─────────────────────────────────────────
|
|
1048
1049
|
# When the `claude` CLI is not AVAILABLE — not installed, logged out, or dying
|
|
1049
1050
|
# before it even starts a session — the ticket runs on our own AI Draftsman CLI
|
|
1050
|
-
# (`ad`, npm package @
|
|
1051
|
+
# (`ad`, npm package @consultancy-outfit/ai-draftsman-cli) instead of failing or parking. It is the
|
|
1051
1052
|
# same job against the same project dir: the prompt goes in on stdin,
|
|
1052
1053
|
# `-p --output-format json` prints one JSON object.
|
|
1053
1054
|
#
|
|
@@ -1056,9 +1057,13 @@ async def _run_claude_cli(
|
|
|
1056
1057
|
# model mid-ticket.
|
|
1057
1058
|
#
|
|
1058
1059
|
# The CLI is the one already installed on the machine (`ad` on PATH, or an npm
|
|
1059
|
-
# global bin dir the service's PATH may not include). The SDK
|
|
1060
|
-
#
|
|
1061
|
-
#
|
|
1060
|
+
# global bin dir the service's PATH may not include). The SDK does not pin a copy
|
|
1061
|
+
# (one pinned inside the SDK went stale the day after it shipped), but it does keep
|
|
1062
|
+
# an installed one current: an `ad` from the old package name, or older than
|
|
1063
|
+
# FALLBACK_MIN_VERSION, is replaced with the latest release (`upgrade_fallback_cli`).
|
|
1064
|
+
# Before 1.1.1 every request carried a tool schema Gemini rejects (an integer
|
|
1065
|
+
# `enum`), so an old `ad` failed every ticket with "400 INVALID_ARGUMENT …
|
|
1066
|
+
# function_declarations". It needs its own login on the box, as the user the SDK
|
|
1062
1067
|
# runs as: `agentos fallback login` (browser sign-in code or an API key), or
|
|
1063
1068
|
# AD_API_KEY in the env. `agentos fallback status --verify` checks it against
|
|
1064
1069
|
# the server.
|
|
@@ -1089,8 +1094,15 @@ def _setting(key: str) -> str:
|
|
|
1089
1094
|
return ""
|
|
1090
1095
|
|
|
1091
1096
|
|
|
1097
|
+
#: The npm package that ships `ad`. It was published as @aidraftsman/cli before 1.1.1.
|
|
1098
|
+
FALLBACK_PACKAGE = "@consultancy-outfit/ai-draftsman-cli"
|
|
1099
|
+
_LEGACY_FALLBACK_PACKAGES = ("@aidraftsman/cli",)
|
|
1100
|
+
#: The oldest `ad` that works against the gateway's Gemini models (see above).
|
|
1101
|
+
FALLBACK_MIN_VERSION = (1, 1, 1)
|
|
1092
1102
|
#: How to get the CLI when it is missing — shown in every "not found" message.
|
|
1093
|
-
FALLBACK_INSTALL_HINT = "npm install -g
|
|
1103
|
+
FALLBACK_INSTALL_HINT = f"npm install -g {FALLBACK_PACKAGE}"
|
|
1104
|
+
#: `upgrade_fallback_cli` runs at most this often from the start-up path.
|
|
1105
|
+
_FALLBACK_UPGRADE_EVERY_S = 6 * 3600
|
|
1094
1106
|
|
|
1095
1107
|
|
|
1096
1108
|
def _fallback_disabled() -> bool:
|
|
@@ -1135,10 +1147,110 @@ def fallback_cli_path() -> str | None:
|
|
|
1135
1147
|
|
|
1136
1148
|
|
|
1137
1149
|
async def ensure_fallback_cli() -> str | None:
|
|
1138
|
-
""":func:`fallback_cli_path`. Kept for callers; it
|
|
1150
|
+
""":func:`fallback_cli_path`. Kept for callers; it installs nothing itself."""
|
|
1139
1151
|
return fallback_cli_path()
|
|
1140
1152
|
|
|
1141
1153
|
|
|
1154
|
+
def _version_tuple(version: str) -> tuple[int, ...]:
|
|
1155
|
+
parts = []
|
|
1156
|
+
for piece in str(version).split("-")[0].split("."):
|
|
1157
|
+
try:
|
|
1158
|
+
parts.append(int(piece))
|
|
1159
|
+
except ValueError:
|
|
1160
|
+
break
|
|
1161
|
+
return tuple(parts)
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
def _npm_cmd(*args: str) -> list[str] | None:
|
|
1165
|
+
"""An npm command line, or None without npm. npm is a .cmd shim on Windows."""
|
|
1166
|
+
npm = shutil.which("npm")
|
|
1167
|
+
if not npm:
|
|
1168
|
+
return None
|
|
1169
|
+
cmd = [npm, *args]
|
|
1170
|
+
if sys.platform == "win32" and npm.lower().endswith((".cmd", ".bat")):
|
|
1171
|
+
cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
|
|
1172
|
+
return cmd
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
def fallback_packages() -> dict[str, str]:
|
|
1176
|
+
"""The AI Draftsman CLI packages npm has installed globally, ``{name: version}``.
|
|
1177
|
+
|
|
1178
|
+
Read from npm rather than ``ad --version``: 1.1.1 still prints "1.1.0" there,
|
|
1179
|
+
so the version flag cannot tell the fixed release from the broken one.
|
|
1180
|
+
"""
|
|
1181
|
+
cmd = _npm_cmd("ls", "-g", "--depth=0", "--json")
|
|
1182
|
+
if not cmd:
|
|
1183
|
+
return {}
|
|
1184
|
+
try:
|
|
1185
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, errors="replace",
|
|
1186
|
+
timeout=60, check=False)
|
|
1187
|
+
deps = (json.loads(proc.stdout or "{}") or {}).get("dependencies") or {}
|
|
1188
|
+
except Exception: # noqa: BLE001 — npm missing/slow/odd output: nothing known
|
|
1189
|
+
return {}
|
|
1190
|
+
wanted = (FALLBACK_PACKAGE, *_LEGACY_FALLBACK_PACKAGES)
|
|
1191
|
+
return {name: str((deps.get(name) or {}).get("version") or "")
|
|
1192
|
+
for name in wanted if name in deps}
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
def upgrade_fallback_cli(*, install_missing: bool = False) -> str:
|
|
1196
|
+
"""Bring the AI Draftsman CLI up to date. Returns what was done, ``""`` if nothing.
|
|
1197
|
+
|
|
1198
|
+
Replaces an `ad` from the old package name and upgrades one older than
|
|
1199
|
+
FALLBACK_MIN_VERSION to the latest release. A machine without the CLI is left
|
|
1200
|
+
alone unless ``install_missing`` (``agentos fallback install``). Skipped when the
|
|
1201
|
+
fallback is off or AGENTOS_FALLBACK_CLI points at a CLI the owner manages. Never raises.
|
|
1202
|
+
"""
|
|
1203
|
+
if _fallback_disabled() or _setting("AGENTOS_FALLBACK_CLI"):
|
|
1204
|
+
return ""
|
|
1205
|
+
if not _npm_cmd("--version"):
|
|
1206
|
+
return ""
|
|
1207
|
+
installed = fallback_packages()
|
|
1208
|
+
legacy = [p for p in _LEGACY_FALLBACK_PACKAGES if p in installed]
|
|
1209
|
+
current = installed.get(FALLBACK_PACKAGE)
|
|
1210
|
+
if not legacy and current and _version_tuple(current) >= FALLBACK_MIN_VERSION:
|
|
1211
|
+
return ""
|
|
1212
|
+
if not legacy and not current and not install_missing:
|
|
1213
|
+
return ""
|
|
1214
|
+
try:
|
|
1215
|
+
for pkg in legacy: # same `ad` bin: the old package must go first
|
|
1216
|
+
subprocess.run(_npm_cmd("uninstall", "-g", pkg), capture_output=True,
|
|
1217
|
+
text=True, errors="replace", timeout=300, check=False)
|
|
1218
|
+
proc = subprocess.run(_npm_cmd("install", "-g", f"{FALLBACK_PACKAGE}@latest"),
|
|
1219
|
+
capture_output=True, text=True, errors="replace",
|
|
1220
|
+
timeout=600, check=False)
|
|
1221
|
+
except Exception as exc: # noqa: BLE001
|
|
1222
|
+
return f"could not update the AI Draftsman CLI: {exc} — run `{FALLBACK_INSTALL_HINT}`"
|
|
1223
|
+
if proc.returncode != 0:
|
|
1224
|
+
tail = (proc.stderr or proc.stdout or "").strip()[-300:]
|
|
1225
|
+
return f"could not update the AI Draftsman CLI ({tail}) — run `{FALLBACK_INSTALL_HINT}`"
|
|
1226
|
+
now = fallback_packages().get(FALLBACK_PACKAGE, "?")
|
|
1227
|
+
before = current or (installed.get(legacy[0]) if legacy else "") or "not installed"
|
|
1228
|
+
return f"AI Draftsman CLI updated {before} → {now}"
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
def maybe_upgrade_fallback_cli() -> str:
|
|
1232
|
+
""":func:`upgrade_fallback_cli`, at most every few hours (start-up path)."""
|
|
1233
|
+
stamp = Path.home() / ".agentos" / "fallback-cli-checked"
|
|
1234
|
+
try:
|
|
1235
|
+
if time.time() - stamp.stat().st_mtime < _FALLBACK_UPGRADE_EVERY_S:
|
|
1236
|
+
return ""
|
|
1237
|
+
except OSError:
|
|
1238
|
+
pass
|
|
1239
|
+
message = upgrade_fallback_cli()
|
|
1240
|
+
try:
|
|
1241
|
+
stamp.parent.mkdir(parents=True, exist_ok=True)
|
|
1242
|
+
stamp.touch()
|
|
1243
|
+
except OSError:
|
|
1244
|
+
pass
|
|
1245
|
+
return message
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
def _is_stale_fallback_error(text: str | None) -> bool:
|
|
1249
|
+
"""The gateway rejected `ad`'s own tool schema — an outdated CLI, not the task."""
|
|
1250
|
+
t = text or ""
|
|
1251
|
+
return "INVALID_ARGUMENT" in t and "function_declarations" in t
|
|
1252
|
+
|
|
1253
|
+
|
|
1142
1254
|
def _fallback_argv(cli: str, *args: str) -> list[str]:
|
|
1143
1255
|
# npm installs a .cmd shim on Windows — same reason as the claude spawn.
|
|
1144
1256
|
cmd = [cli, *args]
|
|
@@ -1325,8 +1437,13 @@ async def run_fallback_cli(
|
|
|
1325
1437
|
extra_env: dict[str, str] | None = None,
|
|
1326
1438
|
tier: str | None = None,
|
|
1327
1439
|
provider: str | None = None,
|
|
1440
|
+
_upgraded: bool = False,
|
|
1328
1441
|
) -> dict[str, Any]:
|
|
1329
|
-
"""Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run.
|
|
1442
|
+
"""Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run.
|
|
1443
|
+
|
|
1444
|
+
An outdated `ad` fails its first request (its tool schema is rejected) before
|
|
1445
|
+
touching anything, so that one failure upgrades the CLI and runs again, once.
|
|
1446
|
+
"""
|
|
1330
1447
|
cli = await ensure_fallback_cli()
|
|
1331
1448
|
if not cli:
|
|
1332
1449
|
return _result(ok=False, error="AI Draftsman CLI (`ad`) not found on this "
|
|
@@ -1392,6 +1509,17 @@ async def run_fallback_cli(
|
|
|
1392
1509
|
continue
|
|
1393
1510
|
if proc.returncode != 0 or not isinstance(data, dict) or "result" not in data:
|
|
1394
1511
|
err = err_text[-500:] or f"aidraftsman exited with code {proc.returncode}"
|
|
1512
|
+
if _is_stale_fallback_error(err) and not _upgraded:
|
|
1513
|
+
print("aidraftsman rejected its own tool list — the CLI is outdated; updating it",
|
|
1514
|
+
flush=True)
|
|
1515
|
+
done = await asyncio.to_thread(upgrade_fallback_cli)
|
|
1516
|
+
if done:
|
|
1517
|
+
print(done, flush=True)
|
|
1518
|
+
if done.startswith("AI Draftsman CLI updated"):
|
|
1519
|
+
return await run_fallback_cli(
|
|
1520
|
+
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1521
|
+
extra_env=extra_env, tier=tier, provider=provider, _upgraded=True)
|
|
1522
|
+
err += f" — the AI Draftsman CLI is outdated; run `{FALLBACK_INSTALL_HINT}`"
|
|
1395
1523
|
auth_expired = proc.returncode == 2 or _is_auth_error(err)
|
|
1396
1524
|
if auth_expired:
|
|
1397
1525
|
err += " — log the fallback in with `agentos fallback login`"
|
|
@@ -1310,13 +1310,15 @@ def main() -> None:
|
|
|
1310
1310
|
# report before → after. Does NOT start, stop, or touch the running loop —
|
|
1311
1311
|
# a running daemon keeps its current code until it is restarted. Honours
|
|
1312
1312
|
# AGENTOS_NO_UPDATE=1 (auto_update no-ops).
|
|
1313
|
-
from sdk.updater import auto_update
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1313
|
+
from sdk.updater import auto_update
|
|
1314
|
+
# NOT quiet: auto_update's own report is the only honest answer. Quiet, a
|
|
1315
|
+
# failed pip (a locked .exe on Windows, no network, …) left the version
|
|
1316
|
+
# unchanged and this command printed "already up to date" — so boxes sat
|
|
1317
|
+
# on 1.6.51 / 1.6.73 for days while PyPI had newer, and nobody could tell.
|
|
1318
|
+
# The report says "still on vX — vY is published", quotes pip's error and
|
|
1319
|
+
# prints the manual fix command.
|
|
1320
|
+
changed, before, after = auto_update(quiet=False)
|
|
1321
|
+
if changed:
|
|
1320
1322
|
# Reload the control-panel server so newly-added /api/ui/* routes take
|
|
1321
1323
|
# effect — a running server keeps the code it imported at boot. Only
|
|
1322
1324
|
# restarts a managed (systemd/launchd/schtask) service; a manually-run
|
|
@@ -1338,8 +1340,6 @@ def main() -> None:
|
|
|
1338
1340
|
if pid:
|
|
1339
1341
|
print("note: a background loop is running the OLD version — "
|
|
1340
1342
|
"`agentos-auto stop` then `agentos-auto` to run the new one.")
|
|
1341
|
-
else:
|
|
1342
|
-
print(f"already up to date (v{after})")
|
|
1343
1343
|
|
|
1344
1344
|
# One-time GitHub history backfill — the last 50 days of THIS user's PRs.
|
|
1345
1345
|
# Fired from `update` because that is the moment every machine passes
|
|
@@ -410,20 +410,118 @@ def _uninstall_launchd() -> tuple[bool, str]:
|
|
|
410
410
|
|
|
411
411
|
# ── Windows (Scheduled Task) ────────────────────────────────────────────────
|
|
412
412
|
|
|
413
|
+
#: Written when Windows refused the logged-out (S4U) panel task, so ``ensure`` does
|
|
414
|
+
#: not rebuild the task on every update only to be refused again.
|
|
415
|
+
_S4U_REFUSED_MARKER = Path.home() / ".agentos" / "panel_task_s4u_refused"
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _windows_account() -> str:
|
|
419
|
+
"""``DOMAIN\\user`` for the task principal — the user whose ~/.agentos holds the token."""
|
|
420
|
+
import getpass
|
|
421
|
+
|
|
422
|
+
user = os.environ.get("USERNAME") or getpass.getuser()
|
|
423
|
+
domain = os.environ.get("USERDOMAIN") or os.environ.get("COMPUTERNAME") or ""
|
|
424
|
+
return f"{domain}\\{user}" if domain else user
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _panel_task_xml(exe: str, args: str, home: str, account: str) -> str:
|
|
428
|
+
"""Task Scheduler XML for the panel: runs whether or not anyone is signed in.
|
|
429
|
+
|
|
430
|
+
The old task was ``/SC ONLOGON`` with an interactive token, so the panel — and
|
|
431
|
+
with it the health ping, the activity collector and the shipper — stopped the
|
|
432
|
+
moment the user signed out of RDP. A VPS used all day then reported nothing
|
|
433
|
+
from sign-out until the next sign-in ("VPS not reporting since 8h ago").
|
|
434
|
+
|
|
435
|
+
* ``S4U`` logon: runs without a stored password, signed in or not. It has no
|
|
436
|
+
Windows network credentials, which the panel does not use (HTTPS only).
|
|
437
|
+
* Boot AND logon triggers: up after a reboot with nobody signed in, and started
|
|
438
|
+
again at sign-in if it was down.
|
|
439
|
+
* Restart on failure, no execution time limit (the default is 72 h).
|
|
440
|
+
* ``HighestAvailable``: the collector reads the Security log, which needs an
|
|
441
|
+
elevated token — same as the old ``/RL HIGHEST``.
|
|
442
|
+
"""
|
|
443
|
+
from xml.sax.saxutils import escape
|
|
444
|
+
|
|
445
|
+
who = escape(account)
|
|
446
|
+
return (
|
|
447
|
+
'<?xml version="1.0" encoding="UTF-16"?>\n'
|
|
448
|
+
'<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">\n'
|
|
449
|
+
" <RegistrationInfo><Description>AgentOS control panel (health, activity, "
|
|
450
|
+
"ticket runs)</Description></RegistrationInfo>\n"
|
|
451
|
+
" <Triggers>\n"
|
|
452
|
+
" <BootTrigger><Enabled>true</Enabled><Delay>PT1M</Delay></BootTrigger>\n"
|
|
453
|
+
f" <LogonTrigger><Enabled>true</Enabled><UserId>{who}</UserId></LogonTrigger>\n"
|
|
454
|
+
" </Triggers>\n"
|
|
455
|
+
f' <Principals><Principal id="Author"><UserId>{who}</UserId>'
|
|
456
|
+
"<LogonType>S4U</LogonType><RunLevel>HighestAvailable</RunLevel>"
|
|
457
|
+
"</Principal></Principals>\n"
|
|
458
|
+
" <Settings>\n"
|
|
459
|
+
" <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
|
|
460
|
+
" <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n"
|
|
461
|
+
" <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n"
|
|
462
|
+
" <StartWhenAvailable>true</StartWhenAvailable>\n"
|
|
463
|
+
" <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>\n"
|
|
464
|
+
" <RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure>\n"
|
|
465
|
+
" </Settings>\n"
|
|
466
|
+
' <Actions Context="Author"><Exec>'
|
|
467
|
+
f"<Command>{escape(exe)}</Command><Arguments>{escape(args)}</Arguments>"
|
|
468
|
+
f"<WorkingDirectory>{escape(home)}</WorkingDirectory>"
|
|
469
|
+
"</Exec></Actions>\n"
|
|
470
|
+
"</Task>\n"
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _create_task_from_xml(task: str, xml: str) -> tuple[int, str]:
|
|
475
|
+
import tempfile
|
|
476
|
+
|
|
477
|
+
fd, path = tempfile.mkstemp(suffix=".xml")
|
|
478
|
+
try:
|
|
479
|
+
with os.fdopen(fd, "w", encoding="utf-16") as fh: # schtasks /XML wants UTF-16
|
|
480
|
+
fh.write(xml)
|
|
481
|
+
return _run(["schtasks", "/Create", "/TN", task, "/XML", path, "/F"])
|
|
482
|
+
except OSError as exc:
|
|
483
|
+
return 1, str(exc)
|
|
484
|
+
finally:
|
|
485
|
+
try:
|
|
486
|
+
os.unlink(path)
|
|
487
|
+
except OSError:
|
|
488
|
+
pass
|
|
489
|
+
|
|
490
|
+
|
|
413
491
|
def _install_schtask(host: str, port: int) -> tuple[bool, str]:
|
|
414
492
|
exe = _exe()
|
|
415
493
|
refusal = _require_absolute(exe, WIN_TASK)
|
|
416
494
|
if refusal:
|
|
417
495
|
return False, refusal
|
|
418
|
-
|
|
419
|
-
rc, out =
|
|
420
|
-
|
|
496
|
+
args = f"--host {host} --port {port} --no-browser"
|
|
497
|
+
rc, out = _create_task_from_xml(
|
|
498
|
+
WIN_TASK, _panel_task_xml(exe, args, str(Path.home()), _windows_account()))
|
|
499
|
+
if rc == 0:
|
|
500
|
+
try:
|
|
501
|
+
_S4U_REFUSED_MARKER.unlink()
|
|
502
|
+
except OSError:
|
|
503
|
+
pass
|
|
504
|
+
_run(["schtasks", "/Run", "/TN", WIN_TASK])
|
|
505
|
+
return True, (f"installed scheduled task '{WIN_TASK}' — runs whether or not anyone "
|
|
506
|
+
f"is signed in, restarts on failure, and is running now.")
|
|
507
|
+
|
|
508
|
+
# Windows refused it (e.g. a policy denying "Log on as a batch job"). Fall back to
|
|
509
|
+
# the at-logon task: worse — it stops at sign-out — but never worse than before.
|
|
510
|
+
try:
|
|
511
|
+
_S4U_REFUSED_MARKER.parent.mkdir(parents=True, exist_ok=True)
|
|
512
|
+
_S4U_REFUSED_MARKER.write_text(out[-2000:], encoding="utf-8")
|
|
513
|
+
except OSError:
|
|
514
|
+
pass
|
|
515
|
+
tr = f'"{exe}" {args}'
|
|
516
|
+
rc, out2 = _run(["schtasks", "/Create", "/TN", WIN_TASK, "/TR", tr,
|
|
517
|
+
"/SC", "ONLOGON", "/RL", "HIGHEST", "/F"])
|
|
421
518
|
if rc != 0:
|
|
422
|
-
return False, f"schtasks create failed:\n{out}"
|
|
519
|
+
return False, f"schtasks create failed:\n{out}\n{out2}"
|
|
423
520
|
_run(["schtasks", "/Run", "/TN", WIN_TASK])
|
|
424
521
|
return True, (f"installed scheduled task '{WIN_TASK}' — starts at logon and is "
|
|
425
|
-
f"running now
|
|
426
|
-
f"
|
|
522
|
+
f"running now. NOTE: Windows refused the run-while-signed-out task, so "
|
|
523
|
+
f"the panel STOPS when you sign out — disconnect from RDP instead of "
|
|
524
|
+
f"signing out. Reason: {out.strip()[-300:]}")
|
|
427
525
|
|
|
428
526
|
|
|
429
527
|
def _update_task_xml(exe: str, home: str) -> str:
|
|
@@ -527,6 +625,19 @@ def _schtask_points_nowhere(task: str) -> bool:
|
|
|
527
625
|
return cmd is not None and not (Path(cmd).is_absolute() and Path(cmd).is_file())
|
|
528
626
|
|
|
529
627
|
|
|
628
|
+
def _schtask_runs_signed_out(task: str) -> bool | None:
|
|
629
|
+
"""True when ``task`` keeps running after sign-out; ``None`` if it cannot be read."""
|
|
630
|
+
rc, out = _run(["schtasks", "/Query", "/TN", task, "/XML"])
|
|
631
|
+
if rc != 0:
|
|
632
|
+
return None
|
|
633
|
+
return bool(re.search(r"<LogonType>\s*(S4U|Password)\s*</LogonType>", out))
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _panel_task_needs_upgrade() -> bool:
|
|
637
|
+
"""An at-logon panel task (pre-1.6.76) that Windows has not already refused to replace."""
|
|
638
|
+
return _schtask_runs_signed_out(WIN_TASK) is False and not _S4U_REFUSED_MARKER.exists()
|
|
639
|
+
|
|
640
|
+
|
|
530
641
|
def _uninstall_schtask() -> tuple[bool, str]:
|
|
531
642
|
_run(["schtasks", "/Delete", "/TN", WIN_UPDATE_TASK, "/F"])
|
|
532
643
|
_run(["schtasks", "/End", "/TN", WIN_TASK])
|
|
@@ -855,6 +966,10 @@ def ensure(restart_running: bool = False) -> tuple[bool, str]:
|
|
|
855
966
|
rok, rmsg = refresh() # rebuilds the task with an absolute path
|
|
856
967
|
return rok, (_with_token_warning(f"panel task pointed at a missing program — "
|
|
857
968
|
f"rebuilt; {rmsg}") if rok else rmsg)
|
|
969
|
+
if IS_WINDOWS and _panel_task_needs_upgrade():
|
|
970
|
+
rok, rmsg = refresh() # at-logon task → runs while signed out
|
|
971
|
+
return rok, (_with_token_warning(f"panel task now runs while signed out; {rmsg}")
|
|
972
|
+
if rok else rmsg)
|
|
858
973
|
if _is_active():
|
|
859
974
|
stale = _restart_if_stale()
|
|
860
975
|
if stale is not None:
|
|
@@ -564,6 +564,9 @@ def auto_update(quiet: bool = False) -> tuple[bool, str, str | None]:
|
|
|
564
564
|
"""
|
|
565
565
|
cur = current_version()
|
|
566
566
|
if os.getenv("AGENTOS_NO_UPDATE"):
|
|
567
|
+
if not quiet:
|
|
568
|
+
_say(f"⚠ updates are switched off (AGENTOS_NO_UPDATE is set) — staying on v{cur}. "
|
|
569
|
+
"Remove it from the environment / ~/.agentos/.env to update.")
|
|
567
570
|
return (False, cur, None)
|
|
568
571
|
|
|
569
572
|
latest = latest_pypi_version()
|
|
@@ -573,6 +576,13 @@ def auto_update(quiet: bool = False) -> tuple[bool, str, str | None]:
|
|
|
573
576
|
# Nothing to do on a PyPI install that is already at (or past) the published
|
|
574
577
|
# version — a git install still checks its branch, which PyPI cannot answer for.
|
|
575
578
|
if not is_github and not behind_pypi:
|
|
579
|
+
if not quiet:
|
|
580
|
+
if latest:
|
|
581
|
+
_say(f"✓ already up to date (v{cur}).")
|
|
582
|
+
else:
|
|
583
|
+
# Not "up to date": we never found out. Saying so is the difference
|
|
584
|
+
# between a box that is current and one that cannot reach PyPI.
|
|
585
|
+
_say(f"⚠ could not reach PyPI to check for updates — staying on v{cur}.")
|
|
576
586
|
audit_changed = _ensure_activity_audit(quiet)
|
|
577
587
|
# Already current — just self-heal the service, restarting only if the
|
|
578
588
|
# audit setup changed something the running panel must pick up.
|
|
@@ -637,6 +647,13 @@ def auto_update_and_reexec(module: str, argv: list[str]) -> None:
|
|
|
637
647
|
if os.getenv("AGENTOS_NO_UPDATE") or os.getenv("_AGENTOS_REEXEC"):
|
|
638
648
|
return
|
|
639
649
|
updated, _cur, _latest = auto_update(quiet=False)
|
|
650
|
+
try:
|
|
651
|
+
from sdk.local_exec import maybe_upgrade_fallback_cli
|
|
652
|
+
fallback_note = maybe_upgrade_fallback_cli()
|
|
653
|
+
if fallback_note:
|
|
654
|
+
_say(("✓ " if fallback_note.startswith("AI Draftsman CLI updated") else "⚠ ") + fallback_note)
|
|
655
|
+
except Exception: # noqa: BLE001 — the fallback CLI must never block a run
|
|
656
|
+
pass
|
|
640
657
|
|
|
641
658
|
# A self-upgrade of a RUNNING package can land broken on Windows (the active
|
|
642
659
|
# agentos-auto.exe / loaded files can't be replaced mid-pip — pip uninstalls
|
|
@@ -659,8 +676,13 @@ def auto_update_and_reexec(module: str, argv: list[str]) -> None:
|
|
|
659
676
|
if not updated:
|
|
660
677
|
return
|
|
661
678
|
_say("restarting on the updated version …\n")
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
679
|
+
try:
|
|
680
|
+
result = subprocess.run(
|
|
681
|
+
[sys.executable, "-m", module, *argv],
|
|
682
|
+
env={**os.environ, "_AGENTOS_REEXEC": "1"},
|
|
683
|
+
)
|
|
684
|
+
except KeyboardInterrupt:
|
|
685
|
+
# Ctrl+C reaches the child too, which stops cleanly and says how to resume;
|
|
686
|
+
# the parent only waits for it, so it leaves without a traceback.
|
|
687
|
+
sys.exit(130)
|
|
666
688
|
sys.exit(result.returncode)
|
|
@@ -40,6 +40,11 @@ logger = logging.getLogger("sdk.vps_identity")
|
|
|
40
40
|
#: A machine does not change into (or out of) a VPS often; a definite answer is
|
|
41
41
|
#: reused this long so a 5-minute shipper does not fetch the profile every tick.
|
|
42
42
|
CACHE_TTL_S = 6 * 3600.0
|
|
43
|
+
#: A "no" is re-asked much sooner. The usual "no" is "no VPS is assigned to this
|
|
44
|
+
#: user" on a box set up before its allocation existed — and holding that for 6 h
|
|
45
|
+
#: kept a freshly assigned VPS silent (no health, activity discarded) for the rest
|
|
46
|
+
#: of the working day. Re-asking costs one profile GET every few minutes.
|
|
47
|
+
NEGATIVE_CACHE_TTL_S = 10 * 60.0
|
|
43
48
|
|
|
44
49
|
_TRUTHY = {"1", "true", "yes", "on"}
|
|
45
50
|
_FALSEY = {"0", "false", "no", "off"}
|
|
@@ -146,7 +151,8 @@ async def is_assigned_vps(client: "BackendClient | None" = None) -> bool | None:
|
|
|
146
151
|
if forced is not None:
|
|
147
152
|
return forced
|
|
148
153
|
now = time.monotonic()
|
|
149
|
-
|
|
154
|
+
ttl = CACHE_TTL_S if _cache["value"] else NEGATIVE_CACHE_TTL_S
|
|
155
|
+
if _cache["value"] is not None and now - _cache["at"] < ttl:
|
|
150
156
|
return _cache["value"]
|
|
151
157
|
try:
|
|
152
158
|
if client is None:
|