devops-bot-sdk 1.6.79__tar.gz → 1.6.85__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.79 → devops_bot_sdk-1.6.85}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/SOURCES.txt +14 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/pyproject.toml +1 -1
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agentd.py +5 -7
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/schedules.py +12 -4
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/browser_profile.py +3 -9
- devops_bot_sdk-1.6.85/sdk/claude_usage.py +136 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/cli.py +16 -1
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/client.py +94 -4
- devops_bot_sdk-1.6.85/sdk/fallback_handoff.py +202 -0
- devops_bot_sdk-1.6.85/sdk/helpdesk.py +751 -0
- devops_bot_sdk-1.6.85/sdk/helpdesk_kb.md +126 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ipc/handlers.py +110 -3
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/local_exec.py +26 -6
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/run_auto.py +420 -35
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/scheduler.py +54 -10
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/api.py +69 -4
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/models.py +24 -4
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/app.js +135 -11
- devops_bot_sdk-1.6.85/sdk/ui/static/help.html +48 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/schedules.html +24 -1
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/styles.css +16 -0
- devops_bot_sdk-1.6.85/tests/test_auto_ticket_queue.py +150 -0
- devops_bot_sdk-1.6.85/tests/test_claude_usage_gate.py +63 -0
- devops_bot_sdk-1.6.85/tests/test_create_safe_retry.py +108 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_fallback_cli.py +3 -2
- devops_bot_sdk-1.6.85/tests/test_helpdesk.py +277 -0
- devops_bot_sdk-1.6.85/tests/test_limit_fallback_handoff.py +164 -0
- devops_bot_sdk-1.6.85/tests/test_panel_help.py +151 -0
- devops_bot_sdk-1.6.85/tests/test_panel_ticket_schedules.py +86 -0
- devops_bot_sdk-1.6.85/tests/test_ticket_schedule_and_agent.py +183 -0
- devops_bot_sdk-1.6.85/tests/test_windows_stop_and_pidfile.py +106 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/README.md +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/custom_agents.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/tickets.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/vps_identity.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_claude_runs.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_live_tracking.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_crud_cli.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_fallback_cli_upgrade.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_scheduler_transient_retry.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_cli.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_vps_only_reporting.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_windows_panel_task.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_windows_task_paths.py +0 -0
- {devops_bot_sdk-1.6.79 → devops_bot_sdk-1.6.85}/tests/test_write_hook.py +0 -0
|
@@ -9,6 +9,7 @@ devops_bot_sdk.egg-info/top_level.txt
|
|
|
9
9
|
sdk/__init__.py
|
|
10
10
|
sdk/agentd.py
|
|
11
11
|
sdk/browser_profile.py
|
|
12
|
+
sdk/claude_usage.py
|
|
12
13
|
sdk/cli.py
|
|
13
14
|
sdk/client.py
|
|
14
15
|
sdk/config.py
|
|
@@ -17,6 +18,7 @@ sdk/custom_agents.py
|
|
|
17
18
|
sdk/departments.py
|
|
18
19
|
sdk/estimator.py
|
|
19
20
|
sdk/exceptions.py
|
|
21
|
+
sdk/fallback_handoff.py
|
|
20
22
|
sdk/firewall.py
|
|
21
23
|
sdk/formats.py
|
|
22
24
|
sdk/git_ops.py
|
|
@@ -24,6 +26,8 @@ sdk/github_fetch.py
|
|
|
24
26
|
sdk/github_ingest.py
|
|
25
27
|
sdk/graph_recon.py
|
|
26
28
|
sdk/graphify.py
|
|
29
|
+
sdk/helpdesk.py
|
|
30
|
+
sdk/helpdesk_kb.md
|
|
27
31
|
sdk/intake.py
|
|
28
32
|
sdk/journal.py
|
|
29
33
|
sdk/local_exec.py
|
|
@@ -134,6 +138,7 @@ sdk/ui/static/app.js
|
|
|
134
138
|
sdk/ui/static/chat.html
|
|
135
139
|
sdk/ui/static/custom-agents.html
|
|
136
140
|
sdk/ui/static/default-agents.html
|
|
141
|
+
sdk/ui/static/help.html
|
|
137
142
|
sdk/ui/static/index.html
|
|
138
143
|
sdk/ui/static/report.html
|
|
139
144
|
sdk/ui/static/runs.html
|
|
@@ -161,13 +166,16 @@ tests/test_agent_workspace_settings.py
|
|
|
161
166
|
tests/test_agents_connectors_builder.py
|
|
162
167
|
tests/test_allowed_tools_config.py
|
|
163
168
|
tests/test_auto_agent_dispatch.py
|
|
169
|
+
tests/test_auto_ticket_queue.py
|
|
164
170
|
tests/test_browser_profile.py
|
|
165
171
|
tests/test_cheap_tier.py
|
|
172
|
+
tests/test_claude_usage_gate.py
|
|
166
173
|
tests/test_client_jira_projects.py
|
|
167
174
|
tests/test_config_token_diagnostics.py
|
|
168
175
|
tests/test_configure_token_owner.py
|
|
169
176
|
tests/test_console_encoding.py
|
|
170
177
|
tests/test_crash_recovery.py
|
|
178
|
+
tests/test_create_safe_retry.py
|
|
171
179
|
tests/test_departments.py
|
|
172
180
|
tests/test_done_transition.py
|
|
173
181
|
tests/test_epic_decompose_run.py
|
|
@@ -185,12 +193,16 @@ tests/test_graph_recon.py
|
|
|
185
193
|
tests/test_graphify_build.py
|
|
186
194
|
tests/test_graphify_hook_install.py
|
|
187
195
|
tests/test_health.py
|
|
196
|
+
tests/test_helpdesk.py
|
|
188
197
|
tests/test_intake_job_outcome.py
|
|
189
198
|
tests/test_intake_plan_review.py
|
|
190
199
|
tests/test_journal.py
|
|
200
|
+
tests/test_limit_fallback_handoff.py
|
|
191
201
|
tests/test_local_exec_provider.py
|
|
192
202
|
tests/test_mcp_route_contract.py
|
|
193
203
|
tests/test_memory_guard.py
|
|
204
|
+
tests/test_panel_help.py
|
|
205
|
+
tests/test_panel_ticket_schedules.py
|
|
194
206
|
tests/test_phase_timer.py
|
|
195
207
|
tests/test_plan_new_command.py
|
|
196
208
|
tests/test_plan_run_activity.py
|
|
@@ -219,6 +231,7 @@ tests/test_task_file_format_doc.py
|
|
|
219
231
|
tests/test_ticket_cli.py
|
|
220
232
|
tests/test_ticket_fields.py
|
|
221
233
|
tests/test_ticket_run_resolve_by_id.py
|
|
234
|
+
tests/test_ticket_schedule_and_agent.py
|
|
222
235
|
tests/test_ticket_speed_fixes.py
|
|
223
236
|
tests/test_tier_flag_parity.py
|
|
224
237
|
tests/test_ui_gate.py
|
|
@@ -232,5 +245,6 @@ tests/test_vps_health.py
|
|
|
232
245
|
tests/test_vps_only_reporting.py
|
|
233
246
|
tests/test_windows_loop_identity.py
|
|
234
247
|
tests/test_windows_panel_task.py
|
|
248
|
+
tests/test_windows_stop_and_pidfile.py
|
|
235
249
|
tests/test_windows_task_paths.py
|
|
236
250
|
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.85
|
|
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.85" # 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
|
|
@@ -176,13 +176,11 @@ def _read_pid() -> int | None:
|
|
|
176
176
|
|
|
177
177
|
|
|
178
178
|
def _pid_alive(pid: int) -> bool:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return True # exists but owned by another user
|
|
185
|
-
return True
|
|
179
|
+
"""Delegates to run_auto's probe — NOT ``os.kill(pid, 0)``, which on Windows is
|
|
180
|
+
``TerminateProcess`` (it kills a live agentd) and raises a bare OSError
|
|
181
|
+
(WinError 87) for a dead pid, crashing `status`/`start` on a stale pidfile."""
|
|
182
|
+
from sdk import run_auto
|
|
183
|
+
return run_auto._pid_alive(pid)
|
|
186
184
|
|
|
187
185
|
|
|
188
186
|
def _daemon_running() -> int | None:
|
|
@@ -49,9 +49,10 @@ MAX_CONSECUTIVE_FAILURES = 3
|
|
|
49
49
|
|
|
50
50
|
_SLUG_RE = re.compile(r"[^A-Za-z0-9_.-]+")
|
|
51
51
|
|
|
52
|
-
# What a schedule fires: a web-app agent,
|
|
53
|
-
# ``sdk.plan_run`` — the same pipeline a ticket gets, with no ticket)
|
|
54
|
-
|
|
52
|
+
# What a schedule fires: a web-app agent, a local plan (a task file run through
|
|
53
|
+
# ``sdk.plan_run`` — the same pipeline a ticket gets, with no ticket), or named
|
|
54
|
+
# Jira tickets (``agentos-auto ticket <keys> [--agent ID]``, queued behind a busy loop).
|
|
55
|
+
KINDS = ("agent", "plan", "tickets")
|
|
55
56
|
|
|
56
57
|
# Where a run's files are copied, in addition to the agent's own output folder:
|
|
57
58
|
# ``flat`` drops them straight into ``output_dir``; ``timestamp`` makes a fresh
|
|
@@ -272,12 +273,15 @@ def create(agent: str, cron: str = "", *, timezone_name: str = "Asia/Karachi",
|
|
|
272
273
|
variables: dict | None = None, agent_id: str = "",
|
|
273
274
|
scope: str = "custom", enabled: bool = True,
|
|
274
275
|
run_at: str = "", kind: str = "agent", task_file: str = "",
|
|
275
|
-
output_dir: str = "", output_layout: str = "flat"
|
|
276
|
+
output_dir: str = "", output_layout: str = "flat",
|
|
277
|
+
ticket_keys: list[str] | None = None) -> dict:
|
|
276
278
|
"""Persist a new schedule. Raises ``ValueError`` on anything unusable.
|
|
277
279
|
|
|
278
280
|
Exactly one of ``cron`` (recurring) and ``run_at`` (once, at that local date
|
|
279
281
|
and time) must be given. ``kind="plan"`` fires ``task_file`` through the local
|
|
280
282
|
coding pipeline instead of an agent; ``agent`` is then just its display name.
|
|
283
|
+
``kind="tickets"`` runs ``ticket_keys`` through ``agentos-auto ticket``, with
|
|
284
|
+
``agent_id`` (when set) overriding each ticket's own agent.
|
|
281
285
|
"""
|
|
282
286
|
if not _valid_agent_name(agent):
|
|
283
287
|
raise ValueError(f"invalid agent name: {agent!r}")
|
|
@@ -286,6 +290,9 @@ def create(agent: str, cron: str = "", *, timezone_name: str = "Asia/Karachi",
|
|
|
286
290
|
raise ValueError(f"kind must be one of {', '.join(KINDS)}")
|
|
287
291
|
if kind == "plan" and not str(task_file or "").strip():
|
|
288
292
|
raise ValueError("a plan schedule needs a task file")
|
|
293
|
+
keys = [str(k).strip().upper() for k in (ticket_keys or []) if str(k).strip()]
|
|
294
|
+
if kind == "tickets" and not keys:
|
|
295
|
+
raise ValueError("a tickets schedule needs at least one ticket key")
|
|
289
296
|
|
|
290
297
|
cron = (cron or "").strip()
|
|
291
298
|
run_at_utc = ""
|
|
@@ -310,6 +317,7 @@ def create(agent: str, cron: str = "", *, timezone_name: str = "Asia/Karachi",
|
|
|
310
317
|
# is by name, so a rename would otherwise orphan the schedule silently.
|
|
311
318
|
"agent_id": str(agent_id or ""),
|
|
312
319
|
"task_file": str(task_file or "").strip(),
|
|
320
|
+
"ticket_keys": keys,
|
|
313
321
|
"scope": scope,
|
|
314
322
|
"cron": cron,
|
|
315
323
|
"run_at": run_at_utc,
|
|
@@ -202,15 +202,9 @@ def busy_pid() -> int | None:
|
|
|
202
202
|
pid = int(raw)
|
|
203
203
|
except ValueError:
|
|
204
204
|
return None
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return None
|
|
209
|
-
except PermissionError:
|
|
210
|
-
return pid # alive, owned by someone else
|
|
211
|
-
except OSError:
|
|
212
|
-
return None
|
|
213
|
-
return pid
|
|
205
|
+
# run_auto's probe: ``os.kill(pid, 0)`` on Windows is TerminateProcess.
|
|
206
|
+
from sdk import run_auto
|
|
207
|
+
return pid if run_auto._pid_alive(pid) else None
|
|
214
208
|
|
|
215
209
|
|
|
216
210
|
async def fetch_dom(url: str, *, timeout: float = _FETCH_TIMEOUT_S) -> Fetched:
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Claude plan usage gate — keeps scheduled runs inside the subscription limits.
|
|
2
|
+
|
|
3
|
+
The scheduler fires agents around the clock to keep the VPS busy. Each run is a
|
|
4
|
+
whole ``claude`` process billed against the same 5-hour and 7-day windows the
|
|
5
|
+
person's own ticket work uses, so filler work must yield BEFORE the windows
|
|
6
|
+
fill, not after a run dies with "usage limit reached" (``sdk.local_exec`` only
|
|
7
|
+
detects the limit once it has already been hit).
|
|
8
|
+
|
|
9
|
+
The numbers come from the same endpoint Claude Code's own ``/usage`` reads,
|
|
10
|
+
authenticated with the CLI's OAuth token in ``~/.claude/.credentials.json``.
|
|
11
|
+
It is not a documented API, so this module FAILS OPEN: no credentials, a
|
|
12
|
+
network error or an unexpected shape all report "allowed". The limit-hit
|
|
13
|
+
detection in ``local_exec`` is still the backstop in that case.
|
|
14
|
+
|
|
15
|
+
Config — process env OR ``~/.agentos/.env``, all optional:
|
|
16
|
+
``AGENTOS_USAGE_GATE`` ``0``/``false``/``no``/``off`` disables the gate.
|
|
17
|
+
``AGENTOS_USAGE_MAX_5H_PCT`` skip scheduled runs at/above this 5-hour % (default 60).
|
|
18
|
+
``AGENTOS_USAGE_MAX_7D_PCT`` skip scheduled runs at/above this 7-day % (default 75).
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
import time
|
|
26
|
+
import urllib.request
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger("sdk.claude_usage")
|
|
30
|
+
|
|
31
|
+
USAGE_URL = "https://api.anthropic.com/api/oauth/usage"
|
|
32
|
+
CREDENTIALS = Path.home() / ".claude" / ".credentials.json"
|
|
33
|
+
|
|
34
|
+
DEFAULT_MAX_5H_PCT = 60.0
|
|
35
|
+
DEFAULT_MAX_7D_PCT = 75.0
|
|
36
|
+
|
|
37
|
+
# One fetch serves every schedule due in the same few minutes.
|
|
38
|
+
_CACHE_TTL_S = 300.0
|
|
39
|
+
_TIMEOUT_S = 10.0
|
|
40
|
+
|
|
41
|
+
_cache: dict = {"at": 0.0, "data": None}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _setting(name: str) -> str:
|
|
45
|
+
raw = os.environ.get(name)
|
|
46
|
+
if raw is not None and str(raw).strip() != "":
|
|
47
|
+
return str(raw).strip()
|
|
48
|
+
try:
|
|
49
|
+
from sdk.config import _dotenv_value
|
|
50
|
+
return (_dotenv_value(name) or "").strip()
|
|
51
|
+
except Exception: # noqa: BLE001
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def enabled() -> bool:
|
|
56
|
+
return (_setting("AGENTOS_USAGE_GATE") or "1").lower() not in ("0", "false", "no", "off")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _pct(name: str, default: float) -> float:
|
|
60
|
+
raw = _setting(name)
|
|
61
|
+
if not raw:
|
|
62
|
+
return default
|
|
63
|
+
try:
|
|
64
|
+
return max(0.0, float(raw))
|
|
65
|
+
except ValueError:
|
|
66
|
+
logger.warning("claude_usage.bad_threshold %s=%r — using %s", name, raw, default)
|
|
67
|
+
return default
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _token() -> str:
|
|
71
|
+
try:
|
|
72
|
+
data = json.loads(CREDENTIALS.read_text(encoding="utf-8"))
|
|
73
|
+
return str((data.get("claudeAiOauth") or {}).get("accessToken") or "")
|
|
74
|
+
except (OSError, ValueError, AttributeError):
|
|
75
|
+
return ""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def fetch(*, force: bool = False) -> dict | None:
|
|
79
|
+
"""The raw usage document, cached for a few minutes. None when unavailable."""
|
|
80
|
+
now = time.monotonic()
|
|
81
|
+
if not force and _cache["data"] is not None and now - _cache["at"] < _CACHE_TTL_S:
|
|
82
|
+
return _cache["data"]
|
|
83
|
+
token = _token()
|
|
84
|
+
if not token:
|
|
85
|
+
return None
|
|
86
|
+
req = urllib.request.Request(USAGE_URL, headers={
|
|
87
|
+
"Authorization": f"Bearer {token}",
|
|
88
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
89
|
+
"User-Agent": "agentos-sdk",
|
|
90
|
+
})
|
|
91
|
+
try:
|
|
92
|
+
with urllib.request.urlopen(req, timeout=_TIMEOUT_S) as resp:
|
|
93
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
94
|
+
except Exception as exc: # noqa: BLE001 — fail open, never break the scheduler
|
|
95
|
+
logger.info("claude_usage.fetch_failed err=%s", exc)
|
|
96
|
+
return None
|
|
97
|
+
if not isinstance(data, dict):
|
|
98
|
+
return None
|
|
99
|
+
_cache.update(at=now, data=data)
|
|
100
|
+
return data
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _window(data: dict, key: str) -> tuple[float | None, str]:
|
|
104
|
+
win = data.get(key)
|
|
105
|
+
if not isinstance(win, dict):
|
|
106
|
+
return None, ""
|
|
107
|
+
try:
|
|
108
|
+
return float(win.get("utilization")), str(win.get("resets_at") or "")
|
|
109
|
+
except (TypeError, ValueError):
|
|
110
|
+
return None, ""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def check(data: dict | None = None) -> dict:
|
|
114
|
+
"""``{"allowed", "reason", "five_hour", "seven_day"}`` for a scheduled run now.
|
|
115
|
+
|
|
116
|
+
``allowed`` is False only when a window is known to be at/above its
|
|
117
|
+
threshold; anything unknown is allowed.
|
|
118
|
+
"""
|
|
119
|
+
out = {"allowed": True, "reason": "", "five_hour": None, "seven_day": None}
|
|
120
|
+
if not enabled():
|
|
121
|
+
return out
|
|
122
|
+
data = data if data is not None else fetch()
|
|
123
|
+
if not data:
|
|
124
|
+
return out
|
|
125
|
+
for key, label, env, default in (
|
|
126
|
+
("five_hour", "5-hour", "AGENTOS_USAGE_MAX_5H_PCT", DEFAULT_MAX_5H_PCT),
|
|
127
|
+
("seven_day", "7-day", "AGENTOS_USAGE_MAX_7D_PCT", DEFAULT_MAX_7D_PCT),
|
|
128
|
+
):
|
|
129
|
+
used, resets = _window(data, key)
|
|
130
|
+
out[key] = used
|
|
131
|
+
limit = _pct(env, default)
|
|
132
|
+
if used is not None and used >= limit and out["allowed"]:
|
|
133
|
+
out["allowed"] = False
|
|
134
|
+
out["reason"] = (f"Claude {label} usage {used:.0f}% ≥ {limit:.0f}% cap"
|
|
135
|
+
+ (f" (resets {resets})" if resets else ""))
|
|
136
|
+
return out
|
|
@@ -231,13 +231,28 @@ def main() -> None:
|
|
|
231
231
|
_fallback_command(rest)
|
|
232
232
|
return
|
|
233
233
|
|
|
234
|
+
if noun == "help":
|
|
235
|
+
# `agentos help -h` is the command list; `agentos help <problem>` (or bare
|
|
236
|
+
# `agentos help` on a terminal, which asks for one) is DevOps Bot Help.
|
|
237
|
+
if rest[:1] in (["-h"], ["--help"]):
|
|
238
|
+
_print_usage()
|
|
239
|
+
return
|
|
240
|
+
from sdk import helpdesk
|
|
241
|
+
sys.exit(helpdesk.cli(rest))
|
|
242
|
+
|
|
243
|
+
if noun == "doctor":
|
|
244
|
+
from sdk import helpdesk
|
|
245
|
+
sys.exit(helpdesk.doctor())
|
|
246
|
+
|
|
234
247
|
_print_usage()
|
|
235
248
|
sys.exit(1)
|
|
236
249
|
|
|
237
250
|
|
|
238
251
|
def _print_usage() -> None:
|
|
239
|
-
print("usage: agentos <configure|chat|agent|ticket|integration|default-agents|agentd|github|report|browser|fallback|firewall|activity-setup|swagger> ...")
|
|
252
|
+
print("usage: agentos <configure|chat|agent|ticket|integration|default-agents|agentd|github|report|browser|fallback|help|doctor|firewall|activity-setup|swagger> ...")
|
|
240
253
|
print()
|
|
254
|
+
print(' agentos help "<your problem>" # DevOps Bot Help: reads your logs + the SDK, tells you the fix')
|
|
255
|
+
print(" agentos doctor # collect a diagnostics bundle to send to your admin (no AI)")
|
|
241
256
|
print(" agentos configure [--rotate]")
|
|
242
257
|
print(" agentos chat [--product-id ID] [--pv-id ID] [--no-autorun] # requirements → ticket")
|
|
243
258
|
print(" agentos agent <list|run NAME|chat NAME MSG|logs NAME|enable NAME|disable NAME|rm NAME>")
|
|
@@ -19,6 +19,7 @@ import json
|
|
|
19
19
|
import logging
|
|
20
20
|
import os
|
|
21
21
|
import random
|
|
22
|
+
import re
|
|
22
23
|
from typing import Any, AsyncGenerator
|
|
23
24
|
|
|
24
25
|
import httpx
|
|
@@ -97,6 +98,69 @@ def is_retryable_backend_error(status_code: int, body: str) -> bool:
|
|
|
97
98
|
return status_code in (408, 429) or status_code >= 500
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
#: How many times set_task_status waits out a 429 before giving up on a ticket.
|
|
102
|
+
_RATE_LIMIT_RETRIES = 3
|
|
103
|
+
_RETRY_AFTER_DEFAULT_S = 60.0
|
|
104
|
+
_RETRY_AFTER_MAX_S = 120.0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _retry_after_seconds(resp: httpx.Response) -> float:
|
|
108
|
+
"""Seconds to wait after a 429: the Retry-After header, else the "retry after N
|
|
109
|
+
seconds" the backend puts in its message, else 60. Capped so a bad value can't
|
|
110
|
+
hang the CLI."""
|
|
111
|
+
raw = resp.headers.get("retry-after", "")
|
|
112
|
+
match = re.search(r"retry after (\d+)", resp.text or "", re.IGNORECASE)
|
|
113
|
+
for candidate in (raw, match.group(1) if match else ""):
|
|
114
|
+
try:
|
|
115
|
+
secs = float(candidate)
|
|
116
|
+
except (TypeError, ValueError):
|
|
117
|
+
continue
|
|
118
|
+
if secs > 0:
|
|
119
|
+
return min(secs, _RETRY_AFTER_MAX_S)
|
|
120
|
+
return _RETRY_AFTER_DEFAULT_S
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
#: Statuses where the backend refused the request WITHOUT acting on it, so a retry
|
|
124
|
+
#: cannot duplicate a create. 401 is here because the backend intermittently fails
|
|
125
|
+
#: to verify a perfectly good token (its profile lookup blips) — the same request
|
|
126
|
+
#: succeeds a moment later. A plain 500 / read timeout is deliberately NOT: a POST
|
|
127
|
+
#: may already have created the ticket or agent.
|
|
128
|
+
_SAFE_RETRY_STATUSES = (401, 408, 429, 502, 503, 504)
|
|
129
|
+
_SAFE_RETRY_ATTEMPTS = 3
|
|
130
|
+
_SAFE_RETRY_MAX_WAIT_S = 30.0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def _request_with_safe_retry(send, *, label: str) -> httpx.Response:
|
|
134
|
+
"""Call ``send()`` (one HTTP request) and retry only failures where nothing was
|
|
135
|
+
processed.
|
|
136
|
+
|
|
137
|
+
Retries the statuses in ``_SAFE_RETRY_STATUSES``, a saturated DB pool, and a
|
|
138
|
+
connection that never opened (``httpx.ConnectError`` — the request was never
|
|
139
|
+
sent). Backoff 1s/2s/4s, or the server's Retry-After for a 429. Any other
|
|
140
|
+
exception propagates exactly as before.
|
|
141
|
+
"""
|
|
142
|
+
for attempt in range(_SAFE_RETRY_ATTEMPTS + 1):
|
|
143
|
+
last = attempt == _SAFE_RETRY_ATTEMPTS
|
|
144
|
+
try:
|
|
145
|
+
resp = await send()
|
|
146
|
+
except httpx.ConnectError as exc:
|
|
147
|
+
if last:
|
|
148
|
+
raise
|
|
149
|
+
reason = f"connect error ({exc})"
|
|
150
|
+
wait = float(2 ** attempt)
|
|
151
|
+
else:
|
|
152
|
+
pool = any(m in (resp.text or "").lower() for m in _POOL_MARKERS)
|
|
153
|
+
if last or not (resp.status_code in _SAFE_RETRY_STATUSES or pool):
|
|
154
|
+
return resp
|
|
155
|
+
reason = f"HTTP {resp.status_code}"
|
|
156
|
+
wait = (min(_retry_after_seconds(resp), _SAFE_RETRY_MAX_WAIT_S)
|
|
157
|
+
if resp.status_code == 429 else float(2 ** attempt))
|
|
158
|
+
logger.warning("client.%s %s — retrying in %ss (%d/%d)", label, reason, wait,
|
|
159
|
+
attempt + 1, _SAFE_RETRY_ATTEMPTS)
|
|
160
|
+
await asyncio.sleep(wait)
|
|
161
|
+
raise AssertionError("unreachable") # pragma: no cover
|
|
162
|
+
|
|
163
|
+
|
|
100
164
|
def _notify_status(task_id: str, status: str) -> None:
|
|
101
165
|
for cb in list(_status_observers):
|
|
102
166
|
try:
|
|
@@ -1369,9 +1433,31 @@ class BackendClient:
|
|
|
1369
1433
|
k: v for k, v in self._headers.items() if k.lower() != "content-type"
|
|
1370
1434
|
}
|
|
1371
1435
|
headers.update({"accept": "*/*", "developer-name": "null", "product": ""})
|
|
1436
|
+
async def _put(client: httpx.AsyncClient, value: str) -> httpx.Response:
|
|
1437
|
+
# A bulk `set-status` (dozens of tickets) trips the backend's rate limit
|
|
1438
|
+
# ("Too many requests. Please retry after 60 seconds."). Wait it out and
|
|
1439
|
+
# retry, so the batch slows down instead of failing every remaining ticket.
|
|
1440
|
+
for attempt in range(_RATE_LIMIT_RETRIES + 1):
|
|
1441
|
+
resp = await client.put(url, files={"status": (None, value)}, headers=headers)
|
|
1442
|
+
if resp.status_code != 429 or attempt == _RATE_LIMIT_RETRIES:
|
|
1443
|
+
return resp
|
|
1444
|
+
wait = _retry_after_seconds(resp)
|
|
1445
|
+
logger.warning("client.set_task_status rate-limited task=%s — waiting %ss "
|
|
1446
|
+
"(retry %d/%d)", task_id, wait, attempt + 1, _RATE_LIMIT_RETRIES)
|
|
1447
|
+
await asyncio.sleep(wait)
|
|
1448
|
+
return resp
|
|
1449
|
+
|
|
1372
1450
|
try:
|
|
1373
1451
|
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
1374
|
-
resp = await client
|
|
1452
|
+
resp = await _put(client, status)
|
|
1453
|
+
# A ticket left In Progress by a loop that died mid-run has no running
|
|
1454
|
+
# timer, and the backend refuses to move it anywhere without one. Start
|
|
1455
|
+
# a timer (re-mark In Progress) and retry once — the same blip the
|
|
1456
|
+
# reject path does before move_to_backlog.
|
|
1457
|
+
if (resp.status_code >= 400 and "no running timer" in resp.text.lower()
|
|
1458
|
+
and status.strip().lower() != "in progress"):
|
|
1459
|
+
await _put(client, "In Progress")
|
|
1460
|
+
resp = await _put(client, status)
|
|
1375
1461
|
if resp.status_code >= 400:
|
|
1376
1462
|
logger.warning("client.set_task_status failed task=%s status=%s body=%s",
|
|
1377
1463
|
task_id, resp.status_code, resp.text[:200])
|
|
@@ -1552,7 +1638,9 @@ class BackendClient:
|
|
|
1552
1638
|
files = to_multipart(fields, model_used)
|
|
1553
1639
|
try:
|
|
1554
1640
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
1555
|
-
resp = await
|
|
1641
|
+
resp = await _request_with_safe_retry(
|
|
1642
|
+
lambda: client.post(url, files=files, headers=headers),
|
|
1643
|
+
label="create_task_ml")
|
|
1556
1644
|
if resp.status_code >= 400:
|
|
1557
1645
|
try:
|
|
1558
1646
|
self.last_task_ml_error = resp.json() or {}
|
|
@@ -2546,8 +2634,10 @@ class BackendClient:
|
|
|
2546
2634
|
setattr(self, err_attr, {})
|
|
2547
2635
|
try:
|
|
2548
2636
|
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
2549
|
-
resp = await
|
|
2550
|
-
|
|
2637
|
+
resp = await _request_with_safe_retry(
|
|
2638
|
+
lambda: client.request(method, self._url(path), json=json,
|
|
2639
|
+
headers=self._headers),
|
|
2640
|
+
label=f"{method} {path}")
|
|
2551
2641
|
if resp.status_code >= 400:
|
|
2552
2642
|
setattr(self, err_attr, self._error_record(resp))
|
|
2553
2643
|
logger.warning("client.%s %s failed status=%s body=%s", method, path,
|