devops-bot-sdk 1.6.84__tar.gz → 1.6.86__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.84 → devops_bot_sdk-1.6.86}/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/devops_bot_sdk.egg-info/SOURCES.txt +3 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/helpdesk.py +347 -15
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/helpdesk_kb.md +5 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/local_exec.py +5 -2
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/api.py +47 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/models.py +14 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/app.js +80 -0
- devops_bot_sdk-1.6.86/sdk/ui/static/help.html +48 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/styles.css +9 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/updater.py +29 -6
- devops_bot_sdk-1.6.86/tests/test_helpdesk.py +277 -0
- devops_bot_sdk-1.6.86/tests/test_panel_help.py +151 -0
- devops_bot_sdk-1.6.86/tests/test_updater_ignores_cwd.py +67 -0
- devops_bot_sdk-1.6.84/tests/test_helpdesk.py +0 -134
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/README.md +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/pyproject.toml +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/claude_usage.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/client.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/config.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/custom_agents.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/fallback_handoff.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/router.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/test.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/tickets.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/vps_identity.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/setup.cfg +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_activity_claude_runs.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_activity_live_tracking.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_crud_cli.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_listing.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_auto_ticket_queue.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_claude_usage_gate.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_create_safe_retry.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_fallback_cli.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_fallback_cli_upgrade.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_limit_fallback_handoff.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_panel_ticket_schedules.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_schedule_tickets.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_scheduler_transient_retry.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_ticket_cli.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_ticket_schedule_and_agent.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_vps_only_reporting.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_windows_panel_task.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_windows_stop_and_pidfile.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_windows_task_paths.py +0 -0
- {devops_bot_sdk-1.6.84 → devops_bot_sdk-1.6.86}/tests/test_write_hook.py +0 -0
|
@@ -138,6 +138,7 @@ sdk/ui/static/app.js
|
|
|
138
138
|
sdk/ui/static/chat.html
|
|
139
139
|
sdk/ui/static/custom-agents.html
|
|
140
140
|
sdk/ui/static/default-agents.html
|
|
141
|
+
sdk/ui/static/help.html
|
|
141
142
|
sdk/ui/static/index.html
|
|
142
143
|
sdk/ui/static/report.html
|
|
143
144
|
sdk/ui/static/runs.html
|
|
@@ -200,6 +201,7 @@ tests/test_limit_fallback_handoff.py
|
|
|
200
201
|
tests/test_local_exec_provider.py
|
|
201
202
|
tests/test_mcp_route_contract.py
|
|
202
203
|
tests/test_memory_guard.py
|
|
204
|
+
tests/test_panel_help.py
|
|
203
205
|
tests/test_panel_ticket_schedules.py
|
|
204
206
|
tests/test_phase_timer.py
|
|
205
207
|
tests/test_plan_new_command.py
|
|
@@ -236,6 +238,7 @@ tests/test_ui_gate.py
|
|
|
236
238
|
tests/test_ui_lifespan.py
|
|
237
239
|
tests/test_update_timer.py
|
|
238
240
|
tests/test_updater_extras.py
|
|
241
|
+
tests/test_updater_ignores_cwd.py
|
|
239
242
|
tests/test_updater_pep668.py
|
|
240
243
|
tests/test_updater_reports_the_truth.py
|
|
241
244
|
tests/test_url_access.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.86
|
|
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.86" # 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
|
|
@@ -16,6 +16,13 @@ log line for every claim and to say "not determinable" rather than guess. It
|
|
|
16
16
|
cannot run commands, edit files or reach the web, so asking for help can never
|
|
17
17
|
change the machine.
|
|
18
18
|
|
|
19
|
+
When claude is not installed, not logged in, or fails mid-answer, the AI Draftsman
|
|
20
|
+
fallback (``ad``) answers instead — the same fallback tickets use. ``ad`` has no
|
|
21
|
+
tool allowlist, ``--add-dir`` or system-prompt flag, so the helper compensates:
|
|
22
|
+
``--permission-mode plan`` (its no-changes mode), the SDK source and docs copied
|
|
23
|
+
INTO the case folder (its working directory), and the same rules sent at the top
|
|
24
|
+
of the prompt.
|
|
25
|
+
|
|
19
26
|
``agentos doctor`` builds the same case folder with no AI, for sending to an admin.
|
|
20
27
|
"""
|
|
21
28
|
from __future__ import annotations
|
|
@@ -43,6 +50,11 @@ _RUN_LOGS = 5
|
|
|
43
50
|
#: Case folders kept; older ones are pruned on each run.
|
|
44
51
|
_KEEP_CASES = 10
|
|
45
52
|
_ANSWER_TIMEOUT_S = 900
|
|
53
|
+
#: A rate-limited `ad` run (its gateway's 429 / RESOURCE_EXHAUSTED) is retried this
|
|
54
|
+
#: many times, this far apart — the quota window is per minute.
|
|
55
|
+
_AD_RATE_LIMIT_RETRIES = 2
|
|
56
|
+
_AD_RATE_LIMIT_WAIT_S = 45.0
|
|
57
|
+
_RATE_LIMIT_RE = re.compile(r"RATE_LIMIT|RESOURCE_EXHAUSTED|\b429\b")
|
|
46
58
|
|
|
47
59
|
# Secrets that may appear in config, env or logs. Order matters: the specific
|
|
48
60
|
# token shapes first, then the generic key=value catch-all.
|
|
@@ -78,7 +90,10 @@ Your evidence, all readable with Read/Grep/Glob:
|
|
|
78
90
|
- The knowledge base: {kb} — a map of programs, files, known issues and fixes.
|
|
79
91
|
{docs_line}
|
|
80
92
|
Accuracy rules — these override everything else:
|
|
81
|
-
|
|
93
|
+
0. WRITE NOTHING until you have finished reading. Your first actions must be file
|
|
94
|
+
reads; do not print a heading, a guess or a "Diagnosis" line before that.
|
|
95
|
+
1. Read before you answer. Start with diagnostics.md ("Key facts" at its top are
|
|
96
|
+
computed by the SDK and are always right) and errors.md; open the logs
|
|
82
97
|
and the source that bear on the problem. Never answer from memory alone.
|
|
83
98
|
2. Every claim about what happened must quote the log line (file + the line) that
|
|
84
99
|
shows it; every claim about how the SDK behaves must cite the source as
|
|
@@ -225,6 +240,75 @@ def _panel_log() -> str:
|
|
|
225
240
|
return ""
|
|
226
241
|
|
|
227
242
|
|
|
243
|
+
def key_facts() -> list[str]:
|
|
244
|
+
"""The yes/no state an answer most often gets wrong, computed — not inferred.
|
|
245
|
+
|
|
246
|
+
Put at the top of diagnostics.md and into the prompt, so a model never has to
|
|
247
|
+
work out from raw JSON whether the loop is running (the AI Draftsman fallback
|
|
248
|
+
once answered "the loop appears to be running" with running_pid null).
|
|
249
|
+
"""
|
|
250
|
+
def yn(v) -> str:
|
|
251
|
+
return "YES" if v else "NO"
|
|
252
|
+
|
|
253
|
+
facts: list[str] = []
|
|
254
|
+
try:
|
|
255
|
+
st = _loop_state()
|
|
256
|
+
facts.append(f"agentos-auto loop running: {yn(st['running_pid'])}"
|
|
257
|
+
+ (f" (pid {st['running_pid']})" if st["running_pid"] else ""))
|
|
258
|
+
facts.append(f"stop flag set (~/.agentos/auto.stop): {yn(st['stop_pending'])}")
|
|
259
|
+
facts.append(f"ticket in flight: {st['current_ticket'] or 'none'}")
|
|
260
|
+
facts.append(f"queued ticket batches: {len(st['queue'])}")
|
|
261
|
+
except Exception as exc: # noqa: BLE001
|
|
262
|
+
facts.append(f"agentos-auto loop state: unknown ({exc})")
|
|
263
|
+
try:
|
|
264
|
+
facts.append(f"agentd running: {yn(_agentd_state()['alive'])}")
|
|
265
|
+
except Exception as exc: # noqa: BLE001
|
|
266
|
+
facts.append(f"agentd state: unknown ({exc})")
|
|
267
|
+
try:
|
|
268
|
+
c = _claude_state()
|
|
269
|
+
facts.append(f"claude CLI: installed {yn(c.get('installed'))}, "
|
|
270
|
+
f"logged in {yn(c.get('logged_in'))}")
|
|
271
|
+
except Exception as exc: # noqa: BLE001
|
|
272
|
+
facts.append(f"claude CLI: unknown ({exc})")
|
|
273
|
+
try:
|
|
274
|
+
from sdk import local_exec
|
|
275
|
+
f = local_exec.fallback_status()
|
|
276
|
+
facts.append(f"AI Draftsman fallback (ad): installed {yn(f.get('installed'))}, "
|
|
277
|
+
f"logged in {yn(f.get('logged_in'))}")
|
|
278
|
+
except Exception as exc: # noqa: BLE001
|
|
279
|
+
facts.append(f"AI Draftsman fallback: unknown ({exc})")
|
|
280
|
+
return facts
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def valid_commands() -> dict[str, set[str]]:
|
|
284
|
+
"""Every real ``agentos <noun>`` and ``agentos-auto <cmd>``, read off the source."""
|
|
285
|
+
import inspect
|
|
286
|
+
|
|
287
|
+
from sdk import cli as cli_mod, run_auto
|
|
288
|
+
|
|
289
|
+
cli_src, auto_src = inspect.getsource(cli_mod), inspect.getsource(run_auto)
|
|
290
|
+
auto = set(re.findall(r'cmd == "([\w-]+)"', auto_src))
|
|
291
|
+
for group in re.findall(r"cmd in \(([^)]*)\)", auto_src):
|
|
292
|
+
auto |= set(re.findall(r'"([\w-]+)"', group))
|
|
293
|
+
return {"agentos": set(re.findall(r'noun == "([\w-]+)"', cli_src)),
|
|
294
|
+
"agentos-auto": auto}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def unknown_commands(answer: str) -> list[str]:
|
|
298
|
+
"""Commands written as code in ``answer`` that do not exist in the source."""
|
|
299
|
+
# One snippet per line, and only spaces/tabs between a program and its
|
|
300
|
+
# sub-command: "`agentos-auto` loop … `agentos-ui`" is two mentions, not a
|
|
301
|
+
# command `agentos-auto agentos-ui`.
|
|
302
|
+
code = "\n".join(re.findall(r"```.*?```", answer, re.S)
|
|
303
|
+
+ re.findall(r"`([^`\n]+)`", answer))
|
|
304
|
+
valid = valid_commands()
|
|
305
|
+
bad: list[str] = []
|
|
306
|
+
for prog, sub in re.findall(r"(?<![\w-])(agentos-auto|agentos(?!-))[ \t]+([a-z][\w-]*)", code):
|
|
307
|
+
if sub not in valid[prog] and f"{prog} {sub}" not in bad:
|
|
308
|
+
bad.append(f"{prog} {sub}")
|
|
309
|
+
return bad
|
|
310
|
+
|
|
311
|
+
|
|
228
312
|
def _log_sources() -> list[tuple[str, Path | str]]:
|
|
229
313
|
"""(name in the case folder, source path or literal text)."""
|
|
230
314
|
out: list[tuple[str, Path | str]] = []
|
|
@@ -261,6 +345,8 @@ def collect_case(question: str = "") -> Path:
|
|
|
261
345
|
"# Diagnostics", "",
|
|
262
346
|
f"collected: {datetime.now(timezone.utc).isoformat(timespec='seconds')}",
|
|
263
347
|
f"question: {redact(question) or '(none — doctor run)'}", "",
|
|
348
|
+
"## Key facts (computed by the SDK — trust these over any inference)",
|
|
349
|
+
*(f"- {f}" for f in key_facts()), "",
|
|
264
350
|
"## Environment",
|
|
265
351
|
f"- sdk version: {__version__}",
|
|
266
352
|
f"- sdk source: {_SDK_DIR}",
|
|
@@ -327,14 +413,118 @@ def _docs_dir() -> Path | None:
|
|
|
327
413
|
return None
|
|
328
414
|
|
|
329
415
|
|
|
330
|
-
def
|
|
331
|
-
"""The read-only `claude -p` invocation for one question."""
|
|
416
|
+
def _system_prompt(case: Path, sdk_dir: Path, kb: Path, docs: Path | None) -> str:
|
|
332
417
|
from sdk import __version__
|
|
333
418
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
sdk_dir=_SDK_DIR, version=__version__, kb=_KB_FILE, case_dir=case,
|
|
419
|
+
return SYSTEM_PROMPT.format(
|
|
420
|
+
sdk_dir=sdk_dir, version=__version__, kb=kb, case_dir=case,
|
|
337
421
|
docs_line=(f"- Project docs: {docs}\n" if docs else ""))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _copy_sources(case: Path) -> tuple[Path, Path, Path | None]:
|
|
425
|
+
"""Copy the SDK source (.py/.md/.html/.js) and docs into ``case`` for ``ad``,
|
|
426
|
+
which can only see its working directory. Returns (sdk dir, kb, docs dir)."""
|
|
427
|
+
def keep(_dir: str, names: list[str]) -> set[str]:
|
|
428
|
+
return {n for n in names
|
|
429
|
+
if n == "__pycache__"
|
|
430
|
+
or ("." in n and not n.endswith((".py", ".md", ".html", ".js")))}
|
|
431
|
+
|
|
432
|
+
sdk_copy = case / "sdk_source"
|
|
433
|
+
if not sdk_copy.exists():
|
|
434
|
+
shutil.copytree(_SDK_DIR, sdk_copy, ignore=keep)
|
|
435
|
+
docs = _docs_dir()
|
|
436
|
+
docs_copy = None
|
|
437
|
+
if docs:
|
|
438
|
+
docs_copy = case / "docs"
|
|
439
|
+
if not docs_copy.exists():
|
|
440
|
+
shutil.copytree(docs, docs_copy, ignore=keep)
|
|
441
|
+
return sdk_copy, sdk_copy / _KB_FILE.name, docs_copy
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def ad_argv(cli: str, *, follow_up: bool = False) -> list[str]:
|
|
445
|
+
"""The `ad -p` invocation; the prompt goes on stdin (no Windows argv limit)."""
|
|
446
|
+
from sdk import local_exec
|
|
447
|
+
|
|
448
|
+
args = ["-p", "--output-format", "text", "--permission-mode", "plan"]
|
|
449
|
+
if follow_up:
|
|
450
|
+
args.append("--continue")
|
|
451
|
+
return local_exec._fallback_argv(cli, *args)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def ad_prompt(question: str, case: Path, *, follow_up: bool = False) -> str:
|
|
455
|
+
"""``ad`` has no system-prompt flag: the rules travel at the top of the prompt,
|
|
456
|
+
with every path pointing INSIDE the case folder, where the sources were copied."""
|
|
457
|
+
if follow_up:
|
|
458
|
+
return question
|
|
459
|
+
sdk_copy, kb, docs = _copy_sources(case)
|
|
460
|
+
facts = ""
|
|
461
|
+
try:
|
|
462
|
+
diag = (case / "diagnostics.md").read_text(encoding="utf-8")
|
|
463
|
+
block = diag.split("## Key facts", 1)[1].split("\n## ", 1)[0]
|
|
464
|
+
facts = "\nKey facts (computed by the SDK — always right):" + block.split("\n", 1)[1]
|
|
465
|
+
except (OSError, IndexError):
|
|
466
|
+
pass
|
|
467
|
+
return (_system_prompt(case, sdk_copy, kb, docs) + facts
|
|
468
|
+
+ "\nAll of the above is in your working directory: ./diagnostics.md, "
|
|
469
|
+
"./errors.md, ./logs/, ./sdk_source/ (cite it as sdk/<file>.py:<line>)"
|
|
470
|
+
+ (", ./docs/" if docs else "") + ".\n\n"
|
|
471
|
+
+ f"The employee's problem:\n{question}\n")
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _engines() -> list[tuple[str, str]]:
|
|
475
|
+
"""Ready answer engines, best first: [("claude", path), ("ad", path)]."""
|
|
476
|
+
from sdk import local_exec
|
|
477
|
+
|
|
478
|
+
out: list[tuple[str, str]] = []
|
|
479
|
+
cli = local_exec.claude_cli_path()
|
|
480
|
+
if cli:
|
|
481
|
+
try:
|
|
482
|
+
ready = local_exec.claude_status(timeout=15.0).get("logged_in")
|
|
483
|
+
except Exception: # noqa: BLE001 — unknown: let the run itself decide
|
|
484
|
+
ready = True
|
|
485
|
+
if ready:
|
|
486
|
+
out.append(("claude", cli))
|
|
487
|
+
try:
|
|
488
|
+
fb = local_exec.fallback_status()
|
|
489
|
+
except Exception: # noqa: BLE001
|
|
490
|
+
fb = {}
|
|
491
|
+
if fb.get("installed") and fb.get("logged_in") and fb.get("path"):
|
|
492
|
+
out.append(("ad", str(fb["path"])))
|
|
493
|
+
return out
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _answer(engine: tuple[str, str], question: str, case: Path, *, follow_up: bool,
|
|
497
|
+
echo: bool = True) -> tuple[int, str]:
|
|
498
|
+
"""One answer: ``(exit code, captured text)``. ``echo`` streams it to the
|
|
499
|
+
terminal (the CLI); without it the text is only returned (the panel)."""
|
|
500
|
+
import time
|
|
501
|
+
|
|
502
|
+
name, cli = engine
|
|
503
|
+
if name == "claude":
|
|
504
|
+
argv = claude_argv(cli, question, case, follow_up=follow_up)
|
|
505
|
+
if echo:
|
|
506
|
+
return _run(argv, case), ""
|
|
507
|
+
return _run_capture(argv, case, stdin=None, echo=False)
|
|
508
|
+
prompt = ad_prompt(question, case, follow_up=follow_up)
|
|
509
|
+
for attempt in range(_AD_RATE_LIMIT_RETRIES + 1):
|
|
510
|
+
rc, out = _run_capture(ad_argv(cli, follow_up=follow_up), case, stdin=prompt, echo=echo)
|
|
511
|
+
if rc == 0:
|
|
512
|
+
if echo:
|
|
513
|
+
_check_ad_answer(out)
|
|
514
|
+
return rc, out
|
|
515
|
+
if not _RATE_LIMIT_RE.search(out[-4000:]) or attempt == _AD_RATE_LIMIT_RETRIES:
|
|
516
|
+
return rc, out
|
|
517
|
+
if echo:
|
|
518
|
+
print(f"\n(the AI Draftsman service is rate-limited — retrying in "
|
|
519
|
+
f"{int(_AD_RATE_LIMIT_WAIT_S)}s, {attempt + 1}/{_AD_RATE_LIMIT_RETRIES})\n")
|
|
520
|
+
time.sleep(_AD_RATE_LIMIT_WAIT_S)
|
|
521
|
+
return rc, out
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def claude_argv(cli: str, question: str, case: Path, *, follow_up: bool = False) -> list[str]:
|
|
525
|
+
"""The read-only `claude -p` invocation for one question."""
|
|
526
|
+
docs = _docs_dir()
|
|
527
|
+
system = _system_prompt(case, _SDK_DIR, _KB_FILE, docs)
|
|
338
528
|
argv = [cli, "-p", question,
|
|
339
529
|
"--output-format", "text",
|
|
340
530
|
"--allowedTools", "Read,Grep,Glob",
|
|
@@ -357,15 +547,26 @@ def ask(question: str, *, interactive: bool | None = None) -> int:
|
|
|
357
547
|
case = collect_case(question)
|
|
358
548
|
print(f" case folder: {case}")
|
|
359
549
|
|
|
360
|
-
|
|
361
|
-
if not
|
|
362
|
-
print("\nDevOps Bot Help needs the
|
|
550
|
+
engines = _engines()
|
|
551
|
+
if not engines:
|
|
552
|
+
print("\nDevOps Bot Help needs claude or the AI Draftsman fallback, and neither "
|
|
553
|
+
"is ready here.")
|
|
363
554
|
print(f" {local_exec.NO_LOCAL_CLI_MESSAGE}")
|
|
364
555
|
print(f"Meanwhile, send this folder to your admin: {case}")
|
|
365
556
|
return 1
|
|
366
557
|
|
|
558
|
+
engine = engines[0]
|
|
559
|
+
if engine[0] == "ad":
|
|
560
|
+
print(" (claude is not installed or not logged in — answering with the AI "
|
|
561
|
+
"Draftsman fallback)")
|
|
367
562
|
print("🤖 DevOps Bot Help is reading your logs and the SDK source …\n")
|
|
368
|
-
rc =
|
|
563
|
+
rc = _answer(engine, question, case, follow_up=False)[0]
|
|
564
|
+
if rc != 0 and len(engines) > 1:
|
|
565
|
+
# claude started but failed (expired login, usage limit, crash): one retry on ad.
|
|
566
|
+
engine = engines[1]
|
|
567
|
+
print(f"\n(claude could not answer, exit {rc} — retrying with the AI Draftsman "
|
|
568
|
+
"fallback)\n")
|
|
569
|
+
rc = _answer(engine, question, case, follow_up=False)[0]
|
|
369
570
|
if interactive is None:
|
|
370
571
|
interactive = sys.stdin.isatty()
|
|
371
572
|
while rc == 0 and interactive:
|
|
@@ -375,24 +576,155 @@ def ask(question: str, *, interactive: bool | None = None) -> int:
|
|
|
375
576
|
break
|
|
376
577
|
if not more:
|
|
377
578
|
break
|
|
378
|
-
rc =
|
|
579
|
+
rc = _answer(engine, more, case, follow_up=True)[0]
|
|
379
580
|
if rc != 0:
|
|
380
581
|
print(f"\nThe helper could not finish (exit {rc}). Send this folder to your admin: {case}")
|
|
381
582
|
return rc
|
|
382
583
|
|
|
383
584
|
|
|
384
|
-
def _run(argv: list[str], case: Path) -> int:
|
|
385
|
-
sys.stdout.flush() # our progress lines must land before
|
|
585
|
+
def _run(argv: list[str], case: Path, *, stdin: str | None = None) -> int:
|
|
586
|
+
sys.stdout.flush() # our progress lines must land before the answer
|
|
386
587
|
try:
|
|
387
|
-
return subprocess.run(argv, cwd=str(case), timeout=_ANSWER_TIMEOUT_S
|
|
588
|
+
return subprocess.run(argv, cwd=str(case), timeout=_ANSWER_TIMEOUT_S,
|
|
589
|
+
input=stdin, text=stdin is not None,
|
|
590
|
+
encoding="utf-8" if stdin is not None else None).returncode
|
|
388
591
|
except subprocess.TimeoutExpired:
|
|
389
592
|
print("\n(timed out)")
|
|
390
593
|
return 124
|
|
391
594
|
except OSError as exc:
|
|
392
|
-
print(f"\ncould not start
|
|
595
|
+
print(f"\ncould not start {Path(argv[0]).name}: {exc}")
|
|
393
596
|
return 1
|
|
394
597
|
|
|
395
598
|
|
|
599
|
+
def ad_notes(answer: str) -> list[str]:
|
|
600
|
+
"""The fallback model follows the rules less reliably than claude, so check the
|
|
601
|
+
one thing that can be checked mechanically — commands — and say so."""
|
|
602
|
+
notes = ["answered by the AI Draftsman fallback, which is less reliable than "
|
|
603
|
+
"claude: double-check before acting."]
|
|
604
|
+
bad = unknown_commands(answer)
|
|
605
|
+
if bad:
|
|
606
|
+
notes.append("⚠ these commands in the answer DO NOT EXIST — ignore them: "
|
|
607
|
+
+ ", ".join(f"`{b}`" for b in bad)
|
|
608
|
+
+ ". The real ones: `agentos help -h` and the top of `sdk/run_auto.py`.")
|
|
609
|
+
return notes
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def _check_ad_answer(answer: str) -> None:
|
|
613
|
+
print()
|
|
614
|
+
for note in ad_notes(answer):
|
|
615
|
+
print(f"— {note}")
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _run_capture(argv: list[str], case: Path, *, stdin: str | None,
|
|
619
|
+
echo: bool = True) -> tuple[int, str]:
|
|
620
|
+
"""Like _run, but KEEP the output (and echo it live unless ``echo`` is off), so
|
|
621
|
+
a rate-limit failure can be told apart from any other, the answer can be
|
|
622
|
+
checked afterwards, and the panel can show it."""
|
|
623
|
+
sys.stdout.flush()
|
|
624
|
+
tail: list[str] = []
|
|
625
|
+
try:
|
|
626
|
+
proc = subprocess.Popen(argv, cwd=str(case),
|
|
627
|
+
stdin=subprocess.PIPE if stdin is not None else subprocess.DEVNULL,
|
|
628
|
+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
629
|
+
text=True, encoding="utf-8", errors="replace")
|
|
630
|
+
except OSError as exc:
|
|
631
|
+
msg = f"could not start {Path(argv[0]).name}: {exc}"
|
|
632
|
+
if echo:
|
|
633
|
+
print("\n" + msg)
|
|
634
|
+
return 1, msg
|
|
635
|
+
if stdin is not None:
|
|
636
|
+
try:
|
|
637
|
+
proc.stdin.write(stdin)
|
|
638
|
+
proc.stdin.close()
|
|
639
|
+
except OSError:
|
|
640
|
+
pass
|
|
641
|
+
import time
|
|
642
|
+
deadline = time.monotonic() + _ANSWER_TIMEOUT_S
|
|
643
|
+
for line in proc.stdout:
|
|
644
|
+
if echo:
|
|
645
|
+
sys.stdout.write(line)
|
|
646
|
+
sys.stdout.flush()
|
|
647
|
+
tail.append(line)
|
|
648
|
+
if time.monotonic() > deadline:
|
|
649
|
+
proc.kill()
|
|
650
|
+
if echo:
|
|
651
|
+
print("\n(timed out)")
|
|
652
|
+
return 124, "".join(tail) + "\n(timed out)"
|
|
653
|
+
return proc.wait(), "".join(tail)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
# ── The control panel's Help page ──────────────────────────────────────────
|
|
657
|
+
|
|
658
|
+
_CASE_NAME_RE = re.compile(r"^case-\d{8}-\d{6}$")
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def case_path(name: str) -> Path | None:
|
|
662
|
+
"""A case folder by NAME, only if it is one of ours — the panel passes it back."""
|
|
663
|
+
if not _CASE_NAME_RE.match(name or ""):
|
|
664
|
+
return None
|
|
665
|
+
path = _CASES_DIR / name
|
|
666
|
+
return path if path.is_dir() else None
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def answer(question: str, case_name: str = "") -> dict:
|
|
670
|
+
"""Blocking, panel-facing ``agentos help``: the same case folder, engines and
|
|
671
|
+
rules, returned instead of printed.
|
|
672
|
+
|
|
673
|
+
A new question builds a new case; ``case_name`` continues that case's
|
|
674
|
+
conversation on the engine that answered it (`--continue` / `-c`).
|
|
675
|
+
"""
|
|
676
|
+
from sdk import local_exec
|
|
677
|
+
|
|
678
|
+
question = (question or "").strip()
|
|
679
|
+
if not question:
|
|
680
|
+
return {"ok": False, "error": "describe the problem first"}
|
|
681
|
+
follow_up = bool(case_name)
|
|
682
|
+
if follow_up:
|
|
683
|
+
case = case_path(case_name)
|
|
684
|
+
if case is None:
|
|
685
|
+
return {"ok": False, "error": f"unknown case {case_name!r}"}
|
|
686
|
+
else:
|
|
687
|
+
case = collect_case(question)
|
|
688
|
+
|
|
689
|
+
engines = _engines()
|
|
690
|
+
if follow_up:
|
|
691
|
+
used = (case / "engine.txt").read_text().strip() if (case / "engine.txt").exists() else ""
|
|
692
|
+
engines = [e for e in engines if e[0] == used] or engines[:1]
|
|
693
|
+
base = {"case": case.name, "case_path": str(case)}
|
|
694
|
+
if not engines:
|
|
695
|
+
return {**base, "ok": False,
|
|
696
|
+
"error": "neither claude nor the AI Draftsman fallback is ready on this "
|
|
697
|
+
f"machine. {local_exec.NO_LOCAL_CLI_MESSAGE}"}
|
|
698
|
+
|
|
699
|
+
engine = engines[0]
|
|
700
|
+
rc, out = _answer(engine, question, case, follow_up=follow_up, echo=False)
|
|
701
|
+
if rc != 0 and not follow_up and len(engines) > 1:
|
|
702
|
+
engine = engines[1]
|
|
703
|
+
rc, out = _answer(engine, question, case, follow_up=False, echo=False)
|
|
704
|
+
(case / "engine.txt").write_text(engine[0])
|
|
705
|
+
result = {**base, "ok": rc == 0, "engine": engine[0], "answer": out.strip(),
|
|
706
|
+
"notes": ad_notes(out) if engine[0] == "ad" and rc == 0 else []}
|
|
707
|
+
if rc != 0:
|
|
708
|
+
result["error"] = (f"the helper could not finish (exit {rc}) — send the case "
|
|
709
|
+
f"folder to your admin: {case}")
|
|
710
|
+
return result
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def bundle_zip(case: Path) -> Path:
|
|
714
|
+
"""A zip of a case's evidence (diagnostics, errors, logs) — NOT the SDK source
|
|
715
|
+
copied in for `ad`, which the admin already has."""
|
|
716
|
+
import zipfile
|
|
717
|
+
|
|
718
|
+
out = case / f"{case.name}.zip"
|
|
719
|
+
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
720
|
+
for path in sorted(case.rglob("*")):
|
|
721
|
+
rel = path.relative_to(case)
|
|
722
|
+
if (path.is_file() and path != out
|
|
723
|
+
and rel.parts[0] not in ("sdk_source", "docs")):
|
|
724
|
+
zf.write(path, f"{case.name}/{rel.as_posix()}")
|
|
725
|
+
return out
|
|
726
|
+
|
|
727
|
+
|
|
396
728
|
def doctor() -> int:
|
|
397
729
|
"""`agentos doctor` — the case folder only, no AI."""
|
|
398
730
|
case = collect_case()
|
|
@@ -119,3 +119,8 @@ agentos fallback install | login | status | logout
|
|
|
119
119
|
agentos doctor # collect a diagnostics bundle (no AI)
|
|
120
120
|
agentos help "<problem>" # this helper
|
|
121
121
|
```
|
|
122
|
+
|
|
123
|
+
`agentos help` answers with claude when it is installed and logged in, otherwise
|
|
124
|
+
(or if claude fails mid-answer) with the AI Draftsman fallback `ad`, which must be
|
|
125
|
+
logged in (`agentos fallback login`). With neither ready it only builds the case
|
|
126
|
+
folder — the same bundle `agentos doctor` makes.
|
|
@@ -1099,8 +1099,11 @@ def _setting(key: str) -> str:
|
|
|
1099
1099
|
#: The npm package that ships `ad`. It was published as @aidraftsman/cli before 1.1.1.
|
|
1100
1100
|
FALLBACK_PACKAGE = "@consultancy-outfit/ai-draftsman-cli"
|
|
1101
1101
|
_LEGACY_FALLBACK_PACKAGES = ("@aidraftsman/cli",)
|
|
1102
|
-
#: The oldest `ad`
|
|
1103
|
-
|
|
1102
|
+
#: The oldest `ad` to keep. 1.1.1 works against the gateway's Gemini models (see
|
|
1103
|
+
#: above); 1.1.2 also retries the gateway's in-stream 429 RESOURCE_EXHAUSTED frame
|
|
1104
|
+
#: (ai-draftsman-cli#3) instead of failing the turn — without it a fallback ticket
|
|
1105
|
+
#: or `agentos help` dies on the first per-minute quota hit.
|
|
1106
|
+
FALLBACK_MIN_VERSION = (1, 1, 2)
|
|
1104
1107
|
#: How to get the CLI when it is missing — shown in every "not found" message.
|
|
1105
1108
|
FALLBACK_INSTALL_HINT = f"npm install -g {FALLBACK_PACKAGE}"
|
|
1106
1109
|
#: `upgrade_fallback_cli` runs at most this often from the start-up path.
|
|
@@ -30,6 +30,7 @@ from sdk.ui.models import (
|
|
|
30
30
|
CustomAgentCreateRequest,
|
|
31
31
|
CustomAgentUpdateRequest,
|
|
32
32
|
GitActionRequest,
|
|
33
|
+
HelpAskRequest,
|
|
33
34
|
PromptRegenerateRequest,
|
|
34
35
|
PromptRunRequest,
|
|
35
36
|
PromptVerifyRequest,
|
|
@@ -936,6 +937,52 @@ async def agent_run_download(name: str = Query(...), file: str = Query(...)):
|
|
|
936
937
|
# to the backend `agents` table; the SDK lists/reads/runs them (consume-only).
|
|
937
938
|
|
|
938
939
|
|
|
940
|
+
# ── DevOps Bot Help (the panel's Help page; `agentos help` / `agentos doctor`) ──
|
|
941
|
+
|
|
942
|
+
@router.post("/help/ask", dependencies=[_auth])
|
|
943
|
+
async def help_ask(body: HelpAskRequest) -> dict:
|
|
944
|
+
"""Ask DevOps Bot Help. Runs as a job (an answer takes a minute or more);
|
|
945
|
+
poll GET /api/ui/jobs/{job_id} → ``{ok, answer, engine, notes, case, error?}``."""
|
|
946
|
+
import asyncio
|
|
947
|
+
|
|
948
|
+
from sdk import helpdesk
|
|
949
|
+
from sdk.ui import jobs
|
|
950
|
+
|
|
951
|
+
async def work() -> dict:
|
|
952
|
+
return await asyncio.to_thread(helpdesk.answer, body.question, body.case)
|
|
953
|
+
|
|
954
|
+
return {"job_id": jobs.start(work())}
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
@router.post("/help/doctor", dependencies=[_auth])
|
|
958
|
+
async def help_doctor() -> dict:
|
|
959
|
+
"""Collect a diagnostics bundle (no AI) — the panel's `agentos doctor`."""
|
|
960
|
+
import asyncio
|
|
961
|
+
|
|
962
|
+
from sdk import helpdesk
|
|
963
|
+
|
|
964
|
+
case = await asyncio.to_thread(helpdesk.collect_case)
|
|
965
|
+
return {"case": case.name, "case_path": str(case),
|
|
966
|
+
"diagnostics": (case / "diagnostics.md").read_text(encoding="utf-8"),
|
|
967
|
+
"errors": (case / "errors.md").read_text(encoding="utf-8")}
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
@router.get("/help/case/{name}/bundle", dependencies=[_auth])
|
|
971
|
+
async def help_bundle(name: str):
|
|
972
|
+
"""The case's evidence as a zip, to send to an admin (no SDK source inside)."""
|
|
973
|
+
import asyncio
|
|
974
|
+
|
|
975
|
+
from fastapi.responses import FileResponse
|
|
976
|
+
|
|
977
|
+
from sdk import helpdesk
|
|
978
|
+
|
|
979
|
+
case = helpdesk.case_path(name)
|
|
980
|
+
if case is None:
|
|
981
|
+
raise HTTPException(status_code=404, detail=f"unknown case: {name}")
|
|
982
|
+
zipped = await asyncio.to_thread(helpdesk.bundle_zip, case)
|
|
983
|
+
return FileResponse(zipped, media_type="application/zip", filename=zipped.name)
|
|
984
|
+
|
|
985
|
+
|
|
939
986
|
@router.get("/jobs/{job_id}", dependencies=[_auth])
|
|
940
987
|
async def job_status(job_id: str) -> dict:
|
|
941
988
|
"""Poll a detached job started by a long-running action (chat/analyze/run).
|
|
@@ -684,3 +684,17 @@ class SelfReportEvaluateRequest(BaseModel):
|
|
|
684
684
|
reason: str = Field(
|
|
685
685
|
"", max_length=500,
|
|
686
686
|
description="Optional note for the audit trail — why it was re-evaluated.")
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
class HelpAskRequest(BaseModel):
|
|
690
|
+
"""Body for ``POST /api/ui/help/ask`` — DevOps Bot Help, from the panel.
|
|
691
|
+
|
|
692
|
+
The same helper as ``agentos help``: diagnostics + logs from THIS machine, the
|
|
693
|
+
installed SDK source, read-only claude (or the AI Draftsman fallback).
|
|
694
|
+
"""
|
|
695
|
+
|
|
696
|
+
question: str = Field(..., min_length=1, max_length=4000,
|
|
697
|
+
description="The problem, in the employee's own words.")
|
|
698
|
+
case: str = Field("", max_length=40,
|
|
699
|
+
description="A case name from a previous answer, to ask a follow-up "
|
|
700
|
+
"in the same conversation. Empty = a new case.")
|