devops-bot-sdk 1.6.73__tar.gz → 1.6.76__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.73 → devops_bot_sdk-1.6.76}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/SOURCES.txt +2 -1
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/pyproject.toml +1 -1
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/recorder.py +15 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/cli.py +12 -9
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/local_exec.py +140 -85
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/service.py +121 -6
- devops_bot_sdk-1.6.76/tests/test_activity_claude_runs.py +134 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_fallback_cli.py +67 -43
- devops_bot_sdk-1.6.76/tests/test_windows_panel_task.py +135 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_windows_task_paths.py +10 -1
- devops_bot_sdk-1.6.73/sdk/vendor/aidraftsman-cli-0.1.0.tgz +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/README.md +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/custom_agents.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/tickets.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/api.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/vps_identity.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_live_tracking.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_crud_cli.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_scheduler_transient_retry.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_cli.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_vps_only_reporting.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.73 → devops_bot_sdk-1.6.76}/tests/test_write_hook.py +0 -0
|
@@ -141,7 +141,7 @@ sdk/ui/static/schedules.html
|
|
|
141
141
|
sdk/ui/static/setup.html
|
|
142
142
|
sdk/ui/static/styles.css
|
|
143
143
|
sdk/ui/static/tickets.html
|
|
144
|
-
|
|
144
|
+
tests/test_activity_claude_runs.py
|
|
145
145
|
tests/test_activity_collectors.py
|
|
146
146
|
tests/test_activity_core.py
|
|
147
147
|
tests/test_activity_live_tracking.py
|
|
@@ -230,5 +230,6 @@ tests/test_url_access.py
|
|
|
230
230
|
tests/test_vps_health.py
|
|
231
231
|
tests/test_vps_only_reporting.py
|
|
232
232
|
tests/test_windows_loop_identity.py
|
|
233
|
+
tests/test_windows_panel_task.py
|
|
233
234
|
tests/test_windows_task_paths.py
|
|
234
235
|
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.76
|
|
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.76" # 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
|
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
import logging
|
|
20
20
|
import time
|
|
21
21
|
import uuid
|
|
22
|
+
from contextvars import ContextVar
|
|
22
23
|
from typing import Any
|
|
23
24
|
|
|
24
25
|
from sdk.activity import settings
|
|
@@ -41,6 +42,17 @@ _FILE_TOOLS = {"Write", "Edit", "MultiEdit", "NotebookEdit", "Read"}
|
|
|
41
42
|
#: SDK-origin records are exact, not inferred — hence full confidence.
|
|
42
43
|
_SDK_CONFIDENCE = 100
|
|
43
44
|
|
|
45
|
+
#: The agent run open in THIS task's context. ``local_exec.run_claude_local``
|
|
46
|
+
#: records every claude run it starts, unless a caller (agentd) already opened one
|
|
47
|
+
#: around it — this is how the two avoid recording the same run twice. A
|
|
48
|
+
#: ContextVar, not a global: concurrent runs live in separate asyncio tasks.
|
|
49
|
+
_open_run: ContextVar[str | None] = ContextVar("agentos_open_agent_run", default=None)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def active_run_id() -> str | None:
|
|
53
|
+
"""The agent run already open in the current context, if any."""
|
|
54
|
+
return _open_run.get()
|
|
55
|
+
|
|
44
56
|
|
|
45
57
|
def record_session(session: ActivitySession) -> bool:
|
|
46
58
|
"""Queue a session (or its update). ``False`` when disabled or on any failure."""
|
|
@@ -94,6 +106,7 @@ def agent_run_started(
|
|
|
94
106
|
) -> str:
|
|
95
107
|
"""Record that agentos started an agent run. Returns the session key."""
|
|
96
108
|
key = agent_session_key(run_id)
|
|
109
|
+
_open_run.set(run_id)
|
|
97
110
|
record_session(
|
|
98
111
|
ActivitySession(
|
|
99
112
|
externalKey=key,
|
|
@@ -134,6 +147,8 @@ def agent_run_finished(
|
|
|
134
147
|
) -> None:
|
|
135
148
|
"""Record the end of an agent run and close its session."""
|
|
136
149
|
key = agent_session_key(run_id)
|
|
150
|
+
if _open_run.get() == run_id:
|
|
151
|
+
_open_run.set(None)
|
|
137
152
|
now = time.time()
|
|
138
153
|
record_session(
|
|
139
154
|
ActivitySession(
|
|
@@ -1383,13 +1383,15 @@ 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 # where `ad` is, or how to install it")
|
|
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")
|
|
1390
1390
|
print()
|
|
1391
|
-
print(" When the `claude` CLI is not
|
|
1392
|
-
print("
|
|
1391
|
+
print(" When the `claude` CLI is not available (not installed or logged out), tickets")
|
|
1392
|
+
print(" run on the AI Draftsman CLI (`ad`) installed on this machine. A claude usage")
|
|
1393
|
+
print(" limit is NOT a fallback: that run pauses and resumes on claude.")
|
|
1394
|
+
print(" `ad` needs its own login, made as the user the SDK runs as.")
|
|
1393
1395
|
print()
|
|
1394
1396
|
print(" --browser prints a sign-in code and a link; approve it on the AI Draftsman")
|
|
1395
1397
|
print(" frontend from any machine — works on a headless VPS")
|
|
@@ -1409,14 +1411,15 @@ def _fallback_command(args: list[str]) -> None:
|
|
|
1409
1411
|
if le._fallback_disabled():
|
|
1410
1412
|
print("error: the fallback is disabled (AGENTOS_FALLBACK_CLI=off)")
|
|
1411
1413
|
sys.exit(1)
|
|
1412
|
-
|
|
1414
|
+
# The SDK no longer installs the CLI: it is updated continuously, so the
|
|
1415
|
+
# copy on the machine is the one to use. This only reports where it is.
|
|
1416
|
+
cli = le.fallback_cli_path()
|
|
1413
1417
|
if not cli:
|
|
1414
|
-
print("error:
|
|
1418
|
+
print(f"error: the AI Draftsman CLI (`ad`) was not found. Install it with:\n"
|
|
1419
|
+
f" {le.FALLBACK_INSTALL_HINT}\n"
|
|
1420
|
+
"or point AGENTOS_FALLBACK_CLI at it in ~/.agentos/.env")
|
|
1415
1421
|
sys.exit(1)
|
|
1416
|
-
print(f"
|
|
1417
|
-
ad = Path(cli).with_name("ad.cmd" if cli.lower().endswith(".cmd") else "ad")
|
|
1418
|
-
if ad.exists():
|
|
1419
|
-
print(f"also as : {ad} (the same CLI under its short name)")
|
|
1422
|
+
print(f"found : {cli}")
|
|
1420
1423
|
print("next : agentos fallback login")
|
|
1421
1424
|
return
|
|
1422
1425
|
|
|
@@ -1045,16 +1045,23 @@ async def _run_claude_cli(
|
|
|
1045
1045
|
|
|
1046
1046
|
|
|
1047
1047
|
# ── Fallback: the AI Draftsman CLI ─────────────────────────────────────────
|
|
1048
|
-
# When the `claude` CLI is not
|
|
1049
|
-
#
|
|
1050
|
-
#
|
|
1051
|
-
#
|
|
1052
|
-
#
|
|
1053
|
-
#
|
|
1054
|
-
#
|
|
1055
|
-
#
|
|
1056
|
-
#
|
|
1057
|
-
#
|
|
1048
|
+
# When the `claude` CLI is not AVAILABLE — not installed, logged out, or dying
|
|
1049
|
+
# before it even starts a session — the ticket runs on our own AI Draftsman CLI
|
|
1050
|
+
# (`ad`, npm package @aidraftsman/cli) instead of failing or parking. It is the
|
|
1051
|
+
# same job against the same project dir: the prompt goes in on stdin,
|
|
1052
|
+
# `-p --output-format json` prints one JSON object.
|
|
1053
|
+
#
|
|
1054
|
+
# A claude USAGE LIMIT is not "unavailable": that run parks and resumes on the
|
|
1055
|
+
# same claude session when the window resets, and is never handed to another
|
|
1056
|
+
# model mid-ticket.
|
|
1057
|
+
#
|
|
1058
|
+
# 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 never installs it:
|
|
1060
|
+
# it is updated continuously, and a copy pinned inside the SDK went stale the
|
|
1061
|
+
# day after it shipped. It needs its own login on the box, as the user the SDK
|
|
1062
|
+
# runs as: `agentos fallback login` (browser sign-in code or an API key), or
|
|
1063
|
+
# AD_API_KEY in the env. `agentos fallback status --verify` checks it against
|
|
1064
|
+
# the server.
|
|
1058
1065
|
#
|
|
1059
1066
|
# What it does NOT do: honour the PreToolUse approval guard, resume a Claude
|
|
1060
1067
|
# session, or stream progress events. So a resumed run never falls back (the
|
|
@@ -1064,7 +1071,7 @@ async def _run_claude_cli(
|
|
|
1064
1071
|
# AGENTOS_FALLBACK_PERMISSION_MODE=bypass to let it run every command.
|
|
1065
1072
|
#
|
|
1066
1073
|
# Settings (process env or ~/.agentos/.env):
|
|
1067
|
-
# AGENTOS_FALLBACK_CLI path to the CLI (default:
|
|
1074
|
+
# AGENTOS_FALLBACK_CLI path to the CLI (default: `ad` on PATH), or "off"
|
|
1068
1075
|
# AGENTOS_FALLBACK_AGENT agent id/name for `--agent` (default: gateway's choice)
|
|
1069
1076
|
# AGENTOS_FALLBACK_PERMISSION_MODE default | acceptEdits | plan | bypass
|
|
1070
1077
|
|
|
@@ -1082,91 +1089,54 @@ def _setting(key: str) -> str:
|
|
|
1082
1089
|
return ""
|
|
1083
1090
|
|
|
1084
1091
|
|
|
1085
|
-
#:
|
|
1086
|
-
|
|
1087
|
-
_VENDOR_DIR = Path(__file__).parent / "vendor"
|
|
1092
|
+
#: How to get the CLI when it is missing — shown in every "not found" message.
|
|
1093
|
+
FALLBACK_INSTALL_HINT = "npm install -g @aidraftsman/cli"
|
|
1088
1094
|
|
|
1089
1095
|
|
|
1090
1096
|
def _fallback_disabled() -> bool:
|
|
1091
1097
|
return _setting("AGENTOS_FALLBACK_CLI").lower() in ("off", "0", "false", "no", "none")
|
|
1092
1098
|
|
|
1093
1099
|
|
|
1094
|
-
def
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
""
|
|
1101
|
-
|
|
1102
|
-
One prefix per tarball name, so an SDK update that ships a new CLI version
|
|
1103
|
-
installs it fresh rather than reusing the old one.
|
|
1104
|
-
"""
|
|
1105
|
-
prefix = config._CONFIG_DIR / "aidraftsman" / tarball.name.removesuffix(".tgz")
|
|
1106
|
-
name = "aidraftsman.cmd" if sys.platform == "win32" else "aidraftsman"
|
|
1107
|
-
return prefix / "node_modules" / ".bin" / name
|
|
1100
|
+
def _npm_global_bins() -> list[Path]:
|
|
1101
|
+
"""npm's global bin dirs, for a service whose PATH does not include them."""
|
|
1102
|
+
if sys.platform == "win32":
|
|
1103
|
+
appdata = os.environ.get("APPDATA")
|
|
1104
|
+
return [Path(appdata) / "npm"] if appdata else []
|
|
1105
|
+
home = Path.home()
|
|
1106
|
+
return [home / ".npm-global" / "bin", home / ".local" / "bin",
|
|
1107
|
+
Path("/usr/local/bin"), Path("/usr/bin")]
|
|
1108
1108
|
|
|
1109
1109
|
|
|
1110
1110
|
def fallback_cli_path() -> str | None:
|
|
1111
|
-
"""Absolute path to
|
|
1111
|
+
"""Absolute path to the installed AI Draftsman CLI, or None if absent or disabled.
|
|
1112
1112
|
|
|
1113
|
-
Resolution order: ``AGENTOS_FALLBACK_CLI``, then ``
|
|
1114
|
-
on ``PATH``, then
|
|
1115
|
-
|
|
1113
|
+
Resolution order: ``AGENTOS_FALLBACK_CLI``, then ``ad`` (and its long name
|
|
1114
|
+
``aidraftsman``) on ``PATH``, then npm's global bin dirs. Never installs
|
|
1115
|
+
anything — the CLI on the machine is the one kept up to date.
|
|
1116
1116
|
"""
|
|
1117
1117
|
if _fallback_disabled():
|
|
1118
1118
|
return None
|
|
1119
1119
|
override = _setting("AGENTOS_FALLBACK_CLI")
|
|
1120
1120
|
if override and Path(override).exists():
|
|
1121
1121
|
return override
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
for name in ("aidraftsman", "ad"):
|
|
1122
|
+
names = ("ad", "aidraftsman")
|
|
1123
|
+
for name in names:
|
|
1125
1124
|
found = shutil.which(name)
|
|
1126
1125
|
if found:
|
|
1127
1126
|
return found
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1127
|
+
suffixes = (".cmd", ".exe", "") if sys.platform == "win32" else ("",)
|
|
1128
|
+
for d in _npm_global_bins():
|
|
1129
|
+
for name in names:
|
|
1130
|
+
for suffix in suffixes:
|
|
1131
|
+
candidate = d / f"{name}{suffix}"
|
|
1132
|
+
if candidate.is_file():
|
|
1133
|
+
return str(candidate)
|
|
1131
1134
|
return None
|
|
1132
1135
|
|
|
1133
1136
|
|
|
1134
|
-
def _install_bundled(tarball: Path) -> str | None:
|
|
1135
|
-
"""``npm install`` the bundled tarball into a private prefix (no sudo, no -g)."""
|
|
1136
|
-
npm = shutil.which("npm")
|
|
1137
|
-
if not npm:
|
|
1138
|
-
print("aidraftsman fallback unavailable: npm not found (Node.js 20+ required)", flush=True)
|
|
1139
|
-
return None
|
|
1140
|
-
bin_path = _bundled_bin(tarball)
|
|
1141
|
-
prefix = bin_path.parents[2]
|
|
1142
|
-
prefix.mkdir(parents=True, exist_ok=True)
|
|
1143
|
-
cmd = [npm, "install", "--prefix", str(prefix), "--no-audit", "--no-fund",
|
|
1144
|
-
"--omit=dev", str(tarball)]
|
|
1145
|
-
if sys.platform == "win32" and npm.lower().endswith((".cmd", ".bat")):
|
|
1146
|
-
cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
|
|
1147
|
-
print(f"installing bundled {tarball.name} into {prefix}", flush=True)
|
|
1148
|
-
try:
|
|
1149
|
-
proc = subprocess.run(cmd, capture_output=True, text=True, errors="replace",
|
|
1150
|
-
timeout=600)
|
|
1151
|
-
except Exception as exc: # noqa: BLE001 — never raise into a ticket run
|
|
1152
|
-
print(f"aidraftsman install failed: {exc}", flush=True)
|
|
1153
|
-
return None
|
|
1154
|
-
if proc.returncode != 0 or not bin_path.exists():
|
|
1155
|
-
print(f"aidraftsman install failed: {(proc.stderr or proc.stdout).strip()[-300:]}",
|
|
1156
|
-
flush=True)
|
|
1157
|
-
return None
|
|
1158
|
-
return str(bin_path)
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
1137
|
async def ensure_fallback_cli() -> str | None:
|
|
1162
|
-
""":func:`fallback_cli_path
|
|
1163
|
-
|
|
1164
|
-
if found or _fallback_disabled():
|
|
1165
|
-
return found
|
|
1166
|
-
tarball = _bundled_tarball()
|
|
1167
|
-
if not tarball:
|
|
1168
|
-
return None
|
|
1169
|
-
return await asyncio.to_thread(_install_bundled, tarball)
|
|
1138
|
+
""":func:`fallback_cli_path`. Kept for callers; it no longer installs anything."""
|
|
1139
|
+
return fallback_cli_path()
|
|
1170
1140
|
|
|
1171
1141
|
|
|
1172
1142
|
def _fallback_argv(cli: str, *args: str) -> list[str]:
|
|
@@ -1200,7 +1170,7 @@ def fallback_status(*, verify: bool = False, timeout: float = 20.0) -> dict:
|
|
|
1200
1170
|
return out
|
|
1201
1171
|
cli = fallback_cli_path()
|
|
1202
1172
|
if not cli:
|
|
1203
|
-
out["error"] = "not
|
|
1173
|
+
out["error"] = f"`ad` not found — install it with `{FALLBACK_INSTALL_HINT}`"
|
|
1204
1174
|
return out
|
|
1205
1175
|
out.update(installed=True, path=cli)
|
|
1206
1176
|
try:
|
|
@@ -1238,8 +1208,8 @@ def fallback_status(*, verify: bool = False, timeout: float = 20.0) -> dict:
|
|
|
1238
1208
|
#: Shown when neither CLI can take a ticket; the entry points' "CLI missing" error.
|
|
1239
1209
|
NO_LOCAL_CLI_MESSAGE = (
|
|
1240
1210
|
"Neither the Claude Code CLI nor the AI Draftsman fallback is ready on this "
|
|
1241
|
-
"machine. Install claude and run `claude setup-token`, or
|
|
1242
|
-
"
|
|
1211
|
+
"machine. Install claude and run `claude setup-token`, or install the AI "
|
|
1212
|
+
f"Draftsman CLI (`{FALLBACK_INSTALL_HINT}`) and run `agentos fallback login`."
|
|
1243
1213
|
)
|
|
1244
1214
|
|
|
1245
1215
|
|
|
@@ -1274,13 +1244,12 @@ def fallback_login(*, method: str | None = None, api_key: str | None = None,
|
|
|
1274
1244
|
a link to approve on the AI Draftsman frontend (works on a headless VPS: open
|
|
1275
1245
|
the link on any machine), ``method="api-key"`` validates and stores a key
|
|
1276
1246
|
from Settings → API keys (prompted with hidden input when ``api_key`` is not
|
|
1277
|
-
given). ``None`` shows the CLI's own menu.
|
|
1247
|
+
given). ``None`` shows the CLI's own menu.
|
|
1278
1248
|
"""
|
|
1279
|
-
cli =
|
|
1249
|
+
cli = fallback_cli_path()
|
|
1280
1250
|
if not cli:
|
|
1281
|
-
print("error: the AI Draftsman CLI
|
|
1282
|
-
"(
|
|
1283
|
-
"or it is disabled with AGENTOS_FALLBACK_CLI=off)")
|
|
1251
|
+
print("error: the AI Draftsman CLI (`ad`) was not found — install it with "
|
|
1252
|
+
f"`{FALLBACK_INSTALL_HINT}` (or it is disabled with AGENTOS_FALLBACK_CLI=off)")
|
|
1284
1253
|
return 1
|
|
1285
1254
|
args = ["auth", "login"]
|
|
1286
1255
|
if api_key:
|
|
@@ -1317,7 +1286,11 @@ def _claude_not_working(result: dict[str, Any]) -> bool:
|
|
|
1317
1286
|
"""
|
|
1318
1287
|
if result.get("ok"):
|
|
1319
1288
|
return False
|
|
1320
|
-
if result.get("
|
|
1289
|
+
if result.get("limit_exceeded"):
|
|
1290
|
+
# Out of usage is not "unavailable": the run parks and resumes on the same
|
|
1291
|
+
# claude session when the window resets (local_exec's limit handling).
|
|
1292
|
+
return False
|
|
1293
|
+
if result.get("auth_expired"):
|
|
1321
1294
|
return True
|
|
1322
1295
|
if result.get("context_exceeded") or result.get("output_cap_exceeded"):
|
|
1323
1296
|
return False
|
|
@@ -1356,9 +1329,8 @@ async def run_fallback_cli(
|
|
|
1356
1329
|
"""Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run."""
|
|
1357
1330
|
cli = await ensure_fallback_cli()
|
|
1358
1331
|
if not cli:
|
|
1359
|
-
return _result(ok=False, error="AI Draftsman CLI not
|
|
1360
|
-
"
|
|
1361
|
-
"`agentos fallback install`).",
|
|
1332
|
+
return _result(ok=False, error="AI Draftsman CLI (`ad`) not found on this "
|
|
1333
|
+
f"machine — install it with `{FALLBACK_INSTALL_HINT}`.",
|
|
1362
1334
|
tier=tier, provider=provider)
|
|
1363
1335
|
|
|
1364
1336
|
path = Path(project_path).expanduser()
|
|
@@ -1462,11 +1434,94 @@ async def run_claude_local(
|
|
|
1462
1434
|
settings_env: dict[str, str] | None = None,
|
|
1463
1435
|
tier: str | None = None,
|
|
1464
1436
|
provider: str | None = None,
|
|
1437
|
+
activity_label: str | None = None,
|
|
1465
1438
|
) -> dict[str, Any]:
|
|
1466
1439
|
"""Run the local `claude` CLI; if the CLI itself is not working, re-run on
|
|
1467
1440
|
the AI Draftsman CLI. See ``_run_claude_cli`` for the arguments and the
|
|
1468
1441
|
result shape, and the fallback notes above ``run_fallback_cli``.
|
|
1442
|
+
|
|
1443
|
+
Every run is recorded in the VPS activity trail as an AGENT_RUN session with
|
|
1444
|
+
its tool calls — ticket runs, scheduled runs and panel runs alike, since they
|
|
1445
|
+
all come through here. Before this only agentd recorded its runs, so the
|
|
1446
|
+
trail showed no agent work at all for most VPSes. A caller that already
|
|
1447
|
+
opened a run (agentd) is left to record its own; ``activity_label`` names the
|
|
1448
|
+
run, defaulting to the repository it ran in.
|
|
1469
1449
|
"""
|
|
1450
|
+
from sdk.activity import recorder as _audit
|
|
1451
|
+
|
|
1452
|
+
if _audit.active_run_id() is not None:
|
|
1453
|
+
return await _run_claude_local(
|
|
1454
|
+
prompt, project_path, allowed_tools=allowed_tools, on_event=on_event,
|
|
1455
|
+
timeout=timeout, github_token=github_token,
|
|
1456
|
+
resume_session_id=resume_session_id, approval_dir=approval_dir,
|
|
1457
|
+
idle_timeout=idle_timeout, max_output_tokens=max_output_tokens,
|
|
1458
|
+
extra_env=extra_env, unset_env=unset_env, settings_env=settings_env,
|
|
1459
|
+
tier=tier, provider=provider,
|
|
1460
|
+
)
|
|
1461
|
+
|
|
1462
|
+
import uuid as _uuid
|
|
1463
|
+
|
|
1464
|
+
run_id = str(_uuid.uuid4())
|
|
1465
|
+
label = activity_label or f"claude: {Path(project_path).name or project_path}"
|
|
1466
|
+
_audit.agent_run_started(run_id, agent_name=label[:255])
|
|
1467
|
+
|
|
1468
|
+
async def _audited(event: dict) -> None:
|
|
1469
|
+
if isinstance(event, dict) and event.get("type") == "assistant":
|
|
1470
|
+
content = (event.get("message") or {}).get("content") or []
|
|
1471
|
+
for block in content:
|
|
1472
|
+
if isinstance(block, dict) and block.get("type") == "tool_use":
|
|
1473
|
+
_audit.agent_tool_call(run_id, str(block.get("name") or "tool"),
|
|
1474
|
+
block.get("input"))
|
|
1475
|
+
if on_event is not None:
|
|
1476
|
+
await on_event(event)
|
|
1477
|
+
|
|
1478
|
+
try:
|
|
1479
|
+
result = await _run_claude_local(
|
|
1480
|
+
prompt, project_path, allowed_tools=allowed_tools, on_event=_audited,
|
|
1481
|
+
timeout=timeout, github_token=github_token,
|
|
1482
|
+
resume_session_id=resume_session_id, approval_dir=approval_dir,
|
|
1483
|
+
idle_timeout=idle_timeout, max_output_tokens=max_output_tokens,
|
|
1484
|
+
extra_env={**(extra_env or {}), "AGENTOS_ACTOR": "agent",
|
|
1485
|
+
"AGENTOS_RUN_ID": run_id},
|
|
1486
|
+
unset_env=unset_env, settings_env=settings_env, tier=tier, provider=provider,
|
|
1487
|
+
)
|
|
1488
|
+
except BaseException as exc: # incl. CancelledError: the session must still close
|
|
1489
|
+
_audit.agent_run_finished(run_id, ok=False, agent_name=label[:255],
|
|
1490
|
+
detail=(str(exc) or type(exc).__name__)[:500])
|
|
1491
|
+
raise
|
|
1492
|
+
ok = bool(result.get("ok"))
|
|
1493
|
+
_audit.agent_run_finished(run_id, ok=ok, agent_name=label[:255],
|
|
1494
|
+
detail=None if ok else str(result.get("error") or "")[:500])
|
|
1495
|
+
return result
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
async def _run_claude_local(
|
|
1499
|
+
prompt: str,
|
|
1500
|
+
project_path: str,
|
|
1501
|
+
*,
|
|
1502
|
+
allowed_tools: list[str] | None,
|
|
1503
|
+
on_event: EventHandler | None,
|
|
1504
|
+
timeout: float,
|
|
1505
|
+
github_token: str | None,
|
|
1506
|
+
resume_session_id: str | None,
|
|
1507
|
+
approval_dir: str | None,
|
|
1508
|
+
idle_timeout: float,
|
|
1509
|
+
max_output_tokens: int | None,
|
|
1510
|
+
extra_env: dict[str, str] | None,
|
|
1511
|
+
unset_env: list[str] | None,
|
|
1512
|
+
settings_env: dict[str, str] | None,
|
|
1513
|
+
tier: str | None,
|
|
1514
|
+
provider: str | None,
|
|
1515
|
+
) -> dict[str, Any]:
|
|
1516
|
+
"""The un-audited body of :func:`run_claude_local`."""
|
|
1517
|
+
if not resume_session_id and not is_available() and fallback_cli_path():
|
|
1518
|
+
# No claude here at all: go straight to `ad` rather than spawn a
|
|
1519
|
+
# process that cannot exist just to learn that it failed.
|
|
1520
|
+
print("claude CLI not installed — running on the AI Draftsman CLI", flush=True)
|
|
1521
|
+
return await run_fallback_cli(
|
|
1522
|
+
prompt, project_path, timeout=timeout, github_token=github_token,
|
|
1523
|
+
extra_env=extra_env, tier=tier, provider=provider,
|
|
1524
|
+
)
|
|
1470
1525
|
result = await _run_claude_cli(
|
|
1471
1526
|
prompt, project_path,
|
|
1472
1527
|
allowed_tools=allowed_tools, on_event=on_event, timeout=timeout,
|
|
@@ -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:
|