devops-bot-sdk 1.7.5__tar.gz → 1.7.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/PKG-INFO +1 -1
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/client.py +1 -1
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/helpdesk_kb.md +9 -2
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/log_colors.py +47 -6
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/platform_compat.py +5 -4
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/run_auto.py +2 -2
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/api.py +62 -8
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/app.js +67 -10
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/styles.css +15 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_listing.py +88 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_log_colors_backlog.py +55 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/README.md +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/pyproject.toml +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/classifier.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/collectors/common.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/collectors/linux_auditd.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/collectors/macos_eslogger.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/collectors/windows_eventlog.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/models.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/os_setup.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/recorder.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/redact.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/settings.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/shipper.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/activity/spool.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agent_sharing.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/activity.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/schedules.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/ticket_run.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/browser_profile.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/claude_usage.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/config.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/custom_agents.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/deferred_tickets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/departments.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/estimator.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/fallback_handoff.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/firewall.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/formats.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/github_fetch.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/github_ingest.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/graph_recon.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/help_actions.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/helpdesk.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/intake.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/journal.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/memory_guard.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/model_policy.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/phase_timer.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/plan_run.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/prd_progress.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/proof_readers.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/providers.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/resume.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/router.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/schedule_tickets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/scheduler.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/self_report.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/self_report_local.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/swagger.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/task_file.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/task_types.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/test.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ticket_board.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ticket_fields.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/tickets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/tiers/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/tiers/mini_agent.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/session.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/chat.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/help.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/report.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/schedules.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/ui/swagger.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/url_access.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/vps_health.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/vps_identity.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/sdk/write_hook.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/setup.cfg +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_activity_claude_runs.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_activity_collectors.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_activity_core.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_activity_live_tracking.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_activity_macos.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_activity_os_setup.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_activity_pipeline.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_activity.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_activity_tracking.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_crud_cli.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_prompt_async.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_run_ticket.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_schedules.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_sharing.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agent_workspace_settings.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_allowed_tools_config.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_approval_park.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_auto_agent_dispatch.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_auto_ticket_queue.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_browser_profile.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_cheap_tier.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_claude_usage_gate.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_client_jira_projects.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_config_token_diagnostics.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_configure_token_owner.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_console_encoding.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_crash_recovery.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_create_safe_retry.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_deferred_tickets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_departments.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_done_transition.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_epic_decompose_run.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_epic_inherited_agent_id.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_epic_session_isolation.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_estimator.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_fallback_cli.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_fallback_cli_upgrade.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_firewall_open_on_update.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_formats.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_github_fetch.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_github_ingest.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_graph_recon.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_graphify_build.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_graphify_hook_install.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_help_actions.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_help_attachments.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_help_questions.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_helpdesk.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_infographic_data.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_intake_job_outcome.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_intake_plan_review.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_journal.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_limit_fallback_handoff.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_local_exec_provider.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_mcp_route_contract.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_memory_guard.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_model_policy.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_panel_help.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_panel_ticket_schedules.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_parallel_tickets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_phase_timer.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_plan_new_command.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_plan_run_activity.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_pr_identity.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_prd_artifact.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_project_path_resolution.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_proof_readers.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_providers.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_recon_session_isolation.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_resume.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_router.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_run_auto_agent_only.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_run_outputs.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_run_verdict_is_honest.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_schedule_api.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_schedule_outputs_and_plans.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_schedule_tickets.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_scheduler.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_scheduler_transient_retry.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_self_report.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_service_token_warning.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_stale_panel_restart.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_swagger_command.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_task_file_and_plan_run.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_task_file_format_doc.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ticket_board.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ticket_cli.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ticket_fields.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ticket_run_resolve_by_id.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ticket_schedule_and_agent.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ticket_speed_fixes.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_tier_flag_parity.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ui_gate.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_ui_lifespan.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_update_timer.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_updater_extras.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_updater_ignores_cwd.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_updater_pep668.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_updater_reports_the_truth.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_url_access.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_vps_health.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_vps_only_reporting.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_windows_loop_identity.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_windows_panel_task.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_windows_stop_and_pidfile.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_windows_task_paths.py +0 -0
- {devops_bot_sdk-1.7.5 → devops_bot_sdk-1.7.8}/tests/test_write_hook.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.8
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -34,7 +34,7 @@ Rules:
|
|
|
34
34
|
- All data egress through submit_webhook only
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "1.7.
|
|
37
|
+
__version__ = "1.7.8" # 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
|
|
@@ -2758,7 +2758,7 @@ class BackendClient:
|
|
|
2758
2758
|
url = self._url("/api/v1/agents/shared")
|
|
2759
2759
|
async with httpx.AsyncClient(timeout=15.0) as client:
|
|
2760
2760
|
resp = await client.get(url, headers=self._headers)
|
|
2761
|
-
if resp.status_code
|
|
2761
|
+
if resp.status_code in (404, 405): # 405: an older backend's /agents/{name} catch-all
|
|
2762
2762
|
raise BackendFeatureMissing("Agent sharing", url)
|
|
2763
2763
|
_check_status(resp.status_code, self._base_url)
|
|
2764
2764
|
return resp.json().get("agents", [])
|
|
@@ -104,6 +104,11 @@ stuck In Progress after a crash → `restore-stranded-tickets` (⚠ moves ticket
|
|
|
104
104
|
pending invite per recipient on the AI backend (devops-bot-ai, Mongo `agent_shares`); it reaches them only
|
|
105
105
|
after they `agentos agent accept ID` (or Accept on the Custom Agents page). Accepted
|
|
106
106
|
shares are runnable (`sdk/agents/chat.resolve_agent` reads `GET /api/v1/agents/shared`).
|
|
107
|
+
Accepted shares appear on Custom Agents next to your own (`GET /api/ui/agents` and
|
|
108
|
+
`/api/ui/agents/custom` add them with `source="shared"`, `shared_by`, `via`,
|
|
109
|
+
`share_id`): Run dropdown, installed cards and a "Shared with you" platform group,
|
|
110
|
+
with Run / Remove only. Their inputs come from the backend row (`/api/ui/agents/vars`
|
|
111
|
+
falls back to `resolve_agent`).
|
|
107
112
|
The panel's Share dialog (Custom Agents → Share) has Person / Team / Department tabs;
|
|
108
113
|
each lists from `GET /api/v1/agents/share-targets?kind=…` (users, teams, departments
|
|
109
114
|
in the platform's SQL — names, never ids) and sends the picks as `user:<id>` /
|
|
@@ -200,8 +205,10 @@ the fix is not in their code, `agentos-auto update` installs the latest.
|
|
|
200
205
|
`agentos-auto logs [-f]` and a foreground run colour lines in a terminal
|
|
201
206
|
(`sdk/log_colors.py`): every status change prints `● KEY → Done` (green) /
|
|
202
207
|
`→ To Do` (cyan) / `→ In Progress` (yellow) / `→ Backlog` (magenta); failures red,
|
|
203
|
-
warnings and
|
|
204
|
-
|
|
208
|
+
warnings, parked and partial yellow, `— ok` agent runs green. Same colours in Windows
|
|
209
|
+
PowerShell/cmd (ANSI mode is enabled on the console) and on the panel's Runs live log
|
|
210
|
+
and Schedules run history. The log file stays plain. No colour → the output isn't a
|
|
211
|
+
terminal, an old Windows console refused ANSI mode, or `NO_COLOR` / `AGENTOS_COLOR=never` is set; `AGENTOS_COLOR=always`
|
|
205
212
|
forces it. `agentos tickets` (= `agentos ticket list`, `sdk/ticket_board.py`) prints
|
|
206
213
|
the counts per status and the tickets in each; `agentos-auto status` ends with a
|
|
207
214
|
one-line `board:` count. The control panel shows **Backlog** next to To Do / In Progress / Done: a
|
|
@@ -9,8 +9,11 @@ the panel, Help and grep, so colour codes never go into it. Instead
|
|
|
9
9
|
● KEY → In Progress yellow ▶ run headers bold
|
|
10
10
|
● KEY → Backlog magenta ticket keys (ABC-123) bold, [timestamps] dim
|
|
11
11
|
|
|
12
|
+
✓ / completed / "— ok" green ↳ transcript pointers, "… idle" dim
|
|
13
|
+
|
|
12
14
|
Colour is on only for a terminal, and honours ``NO_COLOR`` (any value → off) and
|
|
13
|
-
``AGENTOS_COLOR=always|never``.
|
|
15
|
+
``AGENTOS_COLOR=always|never``. On Windows (PowerShell, cmd, Windows Terminal) the
|
|
16
|
+
console's ANSI mode is switched on first; a console that refuses gets plain text.
|
|
14
17
|
"""
|
|
15
18
|
from __future__ import annotations
|
|
16
19
|
|
|
@@ -31,29 +34,64 @@ _ARROW = re.compile(r"(→\s*)(Done|To Do|In Progress|Backlog|backlog)\b")
|
|
|
31
34
|
_MOVED = re.compile(r"\b((?:moved (?:back )?to|returned to|status →)\s+)(Done|To Do|In Progress|Backlog|backlog)\b",
|
|
32
35
|
re.IGNORECASE)
|
|
33
36
|
_BAD = re.compile(r"(✗|\bTraceback\b|\berror\b|\bfailed\b|\bnon-compliant\b|\bFAILED\b)", re.IGNORECASE)
|
|
34
|
-
_WARN = re.compile(r"(⚠|\bwarning\b|\bparked\b
|
|
35
|
-
_GOOD = re.compile(r"(✓|\bcompleted\b)")
|
|
37
|
+
_WARN = re.compile(r"(⚠|\bwarning\b|\bparked\b|⏸|◐|\bpartial\b)", re.IGNORECASE)
|
|
38
|
+
_GOOD = re.compile(r"(✓|\bcompleted\b|— ok\b)")
|
|
39
|
+
_POINTER = re.compile(r"^(\s*)(↳ \w+:)")
|
|
40
|
+
_IDLE = re.compile(r"^\s*…\s*(?:idle|waiting)\b")
|
|
36
41
|
_HEADER = re.compile(r"^\s*(?:\[[^\]]*\]\s*)?▶")
|
|
37
42
|
|
|
38
43
|
|
|
44
|
+
_win_vt: bool | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _windows_vt() -> bool:
|
|
48
|
+
"""Turn on ANSI escape handling for this Windows console (once). conhost —
|
|
49
|
+
what PowerShell and cmd run in — prints raw ``←[32m`` without it."""
|
|
50
|
+
global _win_vt
|
|
51
|
+
if _win_vt is None:
|
|
52
|
+
_win_vt = False
|
|
53
|
+
try:
|
|
54
|
+
import ctypes
|
|
55
|
+
from ctypes import wintypes
|
|
56
|
+
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
57
|
+
k32.GetStdHandle.restype = wintypes.HANDLE
|
|
58
|
+
ok = False
|
|
59
|
+
for std in (-11, -12): # STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
|
|
60
|
+
h, mode = k32.GetStdHandle(std), wintypes.DWORD()
|
|
61
|
+
if h and k32.GetConsoleMode(h, ctypes.byref(mode)):
|
|
62
|
+
# ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
63
|
+
ok = bool(k32.SetConsoleMode(h, mode.value | 0x0001 | 0x0004)) or ok
|
|
64
|
+
_win_vt = ok
|
|
65
|
+
except Exception: # noqa: BLE001
|
|
66
|
+
_win_vt = False
|
|
67
|
+
return _win_vt
|
|
68
|
+
|
|
69
|
+
|
|
39
70
|
def enabled(stream=None) -> bool:
|
|
40
|
-
"""Colour this stream? Env first, then whether it is a terminal
|
|
71
|
+
"""Colour this stream? Env first, then whether it is a terminal (and, on
|
|
72
|
+
Windows, whether its console accepts ANSI colour)."""
|
|
41
73
|
if os.environ.get("NO_COLOR"):
|
|
42
74
|
return False
|
|
43
75
|
pref = (os.environ.get("AGENTOS_COLOR") or "").strip().lower()
|
|
44
76
|
if pref in ("always", "1", "on", "yes"):
|
|
77
|
+
if sys.platform == "win32":
|
|
78
|
+
_windows_vt()
|
|
45
79
|
return True
|
|
46
80
|
if pref in ("never", "0", "off", "no"):
|
|
47
81
|
return False
|
|
48
82
|
stream = stream or sys.stdout
|
|
49
83
|
try:
|
|
50
|
-
|
|
84
|
+
tty = bool(stream.isatty())
|
|
51
85
|
except Exception: # noqa: BLE001
|
|
52
86
|
return False
|
|
87
|
+
if tty and sys.platform == "win32":
|
|
88
|
+
return _windows_vt()
|
|
89
|
+
return tty
|
|
53
90
|
|
|
54
91
|
|
|
55
92
|
def _paint(color: str, text: str) -> str:
|
|
56
|
-
|
|
93
|
+
# An inner span's reset would end the outer colour too — re-open it after each.
|
|
94
|
+
return f"{color}{text.replace(_RESET, _RESET + color)}{_RESET}"
|
|
57
95
|
|
|
58
96
|
|
|
59
97
|
def colorize(line: str) -> str:
|
|
@@ -75,6 +113,7 @@ def colorize(line: str) -> str:
|
|
|
75
113
|
body = _MOVED.sub(_status, body)
|
|
76
114
|
body = _KEY.sub(lambda m: _paint(BOLD, m.group(0)), body)
|
|
77
115
|
body = _STAMP.sub(lambda m: m.group(1) + _paint(DIM, m.group(2)), body)
|
|
116
|
+
body = _POINTER.sub(lambda m: m.group(1) + _paint(DIM, m.group(2)), body)
|
|
78
117
|
|
|
79
118
|
if _HEADER.match(line):
|
|
80
119
|
body = _paint(BOLD + BLUE, body)
|
|
@@ -82,6 +121,8 @@ def colorize(line: str) -> str:
|
|
|
82
121
|
body = _paint(YELLOW, body)
|
|
83
122
|
elif _GOOD.search(line):
|
|
84
123
|
body = _paint(GREEN, body)
|
|
124
|
+
elif _IDLE.match(line):
|
|
125
|
+
body = _paint(DIM, body)
|
|
85
126
|
return body + nl
|
|
86
127
|
|
|
87
128
|
|
|
@@ -16,7 +16,7 @@ _ASCII_FALLBACK = str.maketrans({
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def safe_print(message: str = "") -> None:
|
|
19
|
+
def safe_print(message: str = "", **kw) -> None:
|
|
20
20
|
"""``print`` that cannot take a command down on a legacy Windows codepage.
|
|
21
21
|
|
|
22
22
|
:func:`ensure_utf8_stdio` is the real fix and every entry point installs it.
|
|
@@ -31,14 +31,15 @@ def safe_print(message: str = "") -> None:
|
|
|
31
31
|
``run_auto._send``, the glyph raises inside the ERROR handler, so the real
|
|
32
32
|
error is replaced by a UnicodeEncodeError traceback and is never seen at all.
|
|
33
33
|
"""
|
|
34
|
+
# ``end``/``flush``/``file`` pass through to print (run_auto's status lines flush).
|
|
34
35
|
try:
|
|
35
|
-
print(message)
|
|
36
|
+
print(message, **kw)
|
|
36
37
|
except UnicodeEncodeError:
|
|
37
38
|
try:
|
|
38
|
-
print(message.translate(_ASCII_FALLBACK))
|
|
39
|
+
print(message.translate(_ASCII_FALLBACK), **kw)
|
|
39
40
|
except UnicodeEncodeError:
|
|
40
41
|
# A glyph outside the table (engine output echoed into the line).
|
|
41
|
-
print(message.encode("ascii", "replace").decode("ascii"))
|
|
42
|
+
print(message.encode("ascii", "replace").decode("ascii"), **kw)
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
def ensure_utf8_stdio() -> None:
|
|
@@ -1817,13 +1817,13 @@ def _run_loop(workspace_root: str | None, only_keys: list[str] | None = None,
|
|
|
1817
1817
|
+ ", ".join(_describe_parked(p) for p in parked)) if parked else ""
|
|
1818
1818
|
if continuous:
|
|
1819
1819
|
_idle_s = _idle_refetch_s
|
|
1820
|
-
|
|
1820
|
+
_print(f"… idle — watching for new To Do tickets "
|
|
1821
1821
|
f"(re-checking every {int(_idle_s)}s{waiting}; "
|
|
1822
1822
|
f"`agentos-auto stop` to end)")
|
|
1823
1823
|
else:
|
|
1824
1824
|
_idle_s = _PARK_RECHECK_S
|
|
1825
1825
|
if waiting != last_wait: # say it once per change, not every minute
|
|
1826
|
-
|
|
1826
|
+
_print(f"… waiting{waiting} (re-checking every {int(_idle_s)}s; "
|
|
1827
1827
|
f"`agentos-auto skip KEY` drops one, `agentos-auto stop` ends)")
|
|
1828
1828
|
last_wait = waiting
|
|
1829
1829
|
_waited = 0.0
|
|
@@ -466,6 +466,37 @@ async def product_agents(product_id: str = Query(...)) -> dict:
|
|
|
466
466
|
return {"response": [], "product_id": product_id, "error": str(exc)}
|
|
467
467
|
|
|
468
468
|
|
|
469
|
+
async def _shared_agent_rows(client) -> tuple[list[dict], str]:
|
|
470
|
+
"""Agents others shared with you that you ACCEPTED, as ``(rows, error)``.
|
|
471
|
+
|
|
472
|
+
Each row is the backend agent row plus ``source="shared"``, ``shared_by`` (a
|
|
473
|
+
NAME), ``share_id`` (for Remove) and ``via`` ("team X" / "department X").
|
|
474
|
+
A backend without sharing has none — that is not an error to show."""
|
|
475
|
+
from sdk.exceptions import BackendFeatureMissing
|
|
476
|
+
|
|
477
|
+
fetch = getattr(client, "list_shared_agents", None)
|
|
478
|
+
if fetch is None:
|
|
479
|
+
return [], ""
|
|
480
|
+
try:
|
|
481
|
+
rows = await fetch()
|
|
482
|
+
except BackendFeatureMissing:
|
|
483
|
+
return [], ""
|
|
484
|
+
except Exception as exc: # noqa: BLE001
|
|
485
|
+
return [], f"Couldn't load agents shared with you: {exc}"
|
|
486
|
+
out = []
|
|
487
|
+
for r in rows or []:
|
|
488
|
+
if not isinstance(r, dict) or not r.get("name"):
|
|
489
|
+
continue
|
|
490
|
+
by = r.get("shared_by") or {}
|
|
491
|
+
via = r.get("via_team") or {}
|
|
492
|
+
out.append({**r, "source": "shared",
|
|
493
|
+
"shared_by": (by.get("name") or by.get("email") or "") if isinstance(by, dict) else str(by),
|
|
494
|
+
"share_id": r.get("share_id") or "",
|
|
495
|
+
"via": (f"{'department' if via.get('kind') == 'department' else 'team'} {via['name']}"
|
|
496
|
+
if isinstance(via, dict) and via.get("name") else "")})
|
|
497
|
+
return out, ""
|
|
498
|
+
|
|
499
|
+
|
|
469
500
|
@router.get("/agents", dependencies=[_auth])
|
|
470
501
|
async def agents() -> dict:
|
|
471
502
|
"""The caller's own custom agents (``relation_type='User'``) from the backend.
|
|
@@ -480,14 +511,21 @@ async def agents() -> dict:
|
|
|
480
511
|
from sdk.client import BackendClient
|
|
481
512
|
from sdk.exceptions import BackendFeatureMissing
|
|
482
513
|
|
|
514
|
+
client = BackendClient.from_config()
|
|
515
|
+
# Accepted shares sit next to your own (source="shared"), so the Custom Agents
|
|
516
|
+
# page lists and runs them too; your own agent wins a name clash.
|
|
517
|
+
shared, shared_error = await _shared_agent_rows(client)
|
|
483
518
|
try:
|
|
484
|
-
rows = await
|
|
519
|
+
rows = await client.list_custom_agents()
|
|
485
520
|
except BackendFeatureMissing:
|
|
486
|
-
return {"agents":
|
|
487
|
-
|
|
521
|
+
return {"agents": shared, "shared_error": shared_error,
|
|
522
|
+
"error": ("Custom agents aren't available on this "
|
|
523
|
+
"backend — it may be running an older version.")}
|
|
488
524
|
except Exception as exc: # noqa: BLE001 — degrade to empty, surface the reason
|
|
489
|
-
return {"agents":
|
|
490
|
-
|
|
525
|
+
return {"agents": shared, "error": str(exc), "shared_error": shared_error}
|
|
526
|
+
own = {r.get("name") for r in rows}
|
|
527
|
+
return {"agents": rows + [s for s in shared if s["name"] not in own], "error": "",
|
|
528
|
+
"shared_error": shared_error}
|
|
491
529
|
|
|
492
530
|
|
|
493
531
|
def _flat_agent(row: dict, source: str, *, chattable: bool, department_id: str = "") -> dict:
|
|
@@ -640,6 +678,14 @@ async def agent_vars(name: str = Query(...)) -> dict:
|
|
|
640
678
|
from sdk.agents import varstore as agent_varstore
|
|
641
679
|
|
|
642
680
|
manifest = next((m for m in load_agents() if m.name == name), None)
|
|
681
|
+
if manifest is None:
|
|
682
|
+
# Platform agents (yours, department, shared with you) live on the backend,
|
|
683
|
+
# not in ~/.agentos/agents — read their prompt there so the inputs render.
|
|
684
|
+
try:
|
|
685
|
+
from sdk.agents.chat import resolve_agent
|
|
686
|
+
manifest = await resolve_agent(name)
|
|
687
|
+
except Exception: # noqa: BLE001 — unknown / unreachable: no inputs to show
|
|
688
|
+
manifest = None
|
|
643
689
|
names = extract_variables(manifest.prompt) if manifest else []
|
|
644
690
|
values = agent_varstore.load_vars(name)
|
|
645
691
|
return {"name": name, "variables": names,
|
|
@@ -1816,11 +1862,19 @@ async def custom_agents_list() -> dict:
|
|
|
1816
1862
|
backend, which decides what is yours."""
|
|
1817
1863
|
from sdk import custom_agents
|
|
1818
1864
|
|
|
1819
|
-
|
|
1865
|
+
client = _ticket_client()
|
|
1866
|
+
scopes, err = await custom_agents.list_agents(client)
|
|
1820
1867
|
if scopes is None:
|
|
1821
1868
|
raise HTTPException(status_code=502, detail=err)
|
|
1822
|
-
|
|
1823
|
-
|
|
1869
|
+
out = {scope: [custom_agents.editable_view(r) for r in rows]
|
|
1870
|
+
for scope, rows in scopes.items()}
|
|
1871
|
+
# Accepted shares: read-only here (Run / Remove), never Edit or Delete.
|
|
1872
|
+
shared, shared_error = await _shared_agent_rows(client)
|
|
1873
|
+
out["shared"] = [{"id": s.get("id"), "name": s["name"], "status": s.get("status"),
|
|
1874
|
+
"shared_by": s["shared_by"], "via": s["via"], "share_id": s["share_id"]}
|
|
1875
|
+
for s in shared]
|
|
1876
|
+
out["shared_error"] = shared_error
|
|
1877
|
+
return out
|
|
1824
1878
|
|
|
1825
1879
|
|
|
1826
1880
|
@router.post("/agents/custom", dependencies=[_auth])
|
|
@@ -131,6 +131,30 @@
|
|
|
131
131
|
const STATUS_COLOR = { "To Do": "var(--blue)", "In Progress": "var(--amber)", "Done": "var(--green)", "Backlog": "var(--violet)" };
|
|
132
132
|
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); }
|
|
133
133
|
|
|
134
|
+
// One log line as coloured HTML — the panel's twin of sdk/log_colors.colorize:
|
|
135
|
+
// statuses in their own colour, errors red, warnings amber, ▶ headers bold,
|
|
136
|
+
// ticket keys bold, [timestamps] dim. Never changes the text itself.
|
|
137
|
+
const LOG_STATUS = { "done": "lg-done", "to do": "lg-todo", "in progress": "lg-prog", "backlog": "lg-back" };
|
|
138
|
+
function colorLogLine(line) {
|
|
139
|
+
let h = esc(line);
|
|
140
|
+
if (!line.trim()) return h;
|
|
141
|
+
const arrow = /(→\s*)(Done|To Do|In Progress|Backlog|backlog)\b/;
|
|
142
|
+
if (/(✗|\bTraceback\b|\berror\b|\bfailed\b|\bnon-compliant\b)/i.test(line) && !arrow.test(line))
|
|
143
|
+
return `<span class="lg-bad">${h}</span>`;
|
|
144
|
+
const status = (_, pre, word) => `${pre}<b class="${LOG_STATUS[word.toLowerCase()] || ""}">${word}</b>`;
|
|
145
|
+
h = h.replace(/(→\s*)(Done|To Do|In Progress|Backlog|backlog)\b/g, status)
|
|
146
|
+
.replace(/\b((?:moved (?:back )?to|returned to|status →)\s+)(Done|To Do|In Progress|Backlog|backlog)\b/gi, status)
|
|
147
|
+
.replace(/\b[A-Z][A-Z0-9]{1,9}-\d+\b/g, m => `<b>${m}</b>`)
|
|
148
|
+
.replace(/^(\s*(?:•\s*)?)(\[\d{4}-\d{2}-\d{2}[ T][\d:]+Z?\])/, (_, pre, ts) => `${pre}<span class="lg-dim">${ts}</span>`)
|
|
149
|
+
.replace(/(↳ transcript:)/, `<span class="lg-dim">$1</span>`);
|
|
150
|
+
if (/^\s*(?:\[[^\]]*\]\s*)?▶/.test(line)) return `<span class="lg-head">${h}</span>`;
|
|
151
|
+
if (/(⚠|\bwarning\b|\bparked\b|⏸|\bpartial\b)/i.test(line) && !line.includes("→")) return `<span class="lg-warn">${h}</span>`;
|
|
152
|
+
if (/(✓|\bcompleted\b|— ok\b)/.test(line)) return `<span class="lg-good">${h}</span>`;
|
|
153
|
+
if (/^\s*…\s*idle\b/.test(line)) return `<span class="lg-dim">${h}</span>`;
|
|
154
|
+
return h;
|
|
155
|
+
}
|
|
156
|
+
function colorLog(text) { return String(text || "").split("\n").map(colorLogLine).join("\n"); }
|
|
157
|
+
|
|
134
158
|
// Parse a "KEY=VALUE per line" textarea into an object. Used for ephemeral
|
|
135
159
|
// run-time secrets (never persisted). Blank lines / lines without "=" ignored.
|
|
136
160
|
function parseSecrets(id) {
|
|
@@ -457,8 +481,10 @@
|
|
|
457
481
|
async function act(path, method, okMsg) {
|
|
458
482
|
try { await api(path, { method }); toast(okMsg, "ok"); }
|
|
459
483
|
catch (e) { toast(e.message, "err"); }
|
|
460
|
-
|
|
484
|
+
// Accept / Remove changes which agents you have: the lists above reload too.
|
|
485
|
+
document.dispatchEvent(new CustomEvent("agentos:shares-changed"));
|
|
461
486
|
}
|
|
487
|
+
document.addEventListener("agentos:shares-changed", () => loadShared());
|
|
462
488
|
async function loadShared() {
|
|
463
489
|
const box = $("sh-list");
|
|
464
490
|
document.querySelectorAll("[data-shtab]").forEach(b => b.classList.toggle("primary", b.dataset.shtab === tab));
|
|
@@ -488,7 +514,7 @@
|
|
|
488
514
|
return `<tr><td><div class="nm">${esc(x.agent_name)}</div>${x.message ? `<div class="sub">“${esc(x.message)}”</div>` : ""}</td>
|
|
489
515
|
<td>${esc(who || "?")}${via}</td><td>${pill(x.status)}</td><td class="sub mono">${esc(when)}</td><td class="acts">${acts}</td></tr>`;
|
|
490
516
|
}).join("") + `</tbody></table>`;
|
|
491
|
-
box.querySelectorAll("[data-acc]").forEach(b => b.onclick = () => act(`/api/ui/agents/shares/${b.dataset.acc}/accept`, "POST", "Accepted — it
|
|
517
|
+
box.querySelectorAll("[data-acc]").forEach(b => b.onclick = () => act(`/api/ui/agents/shares/${b.dataset.acc}/accept`, "POST", "Accepted — it's now in your custom agents"));
|
|
492
518
|
box.querySelectorAll("[data-dec]").forEach(b => b.onclick = () => act(`/api/ui/agents/shares/${b.dataset.dec}/decline`, "POST", "Declined"));
|
|
493
519
|
box.querySelectorAll("[data-rm]").forEach(b => b.onclick = () => {
|
|
494
520
|
if (!confirm(tab === "incoming" ? "Remove this shared agent from your list?" : "Revoke this share? They lose access.")) return;
|
|
@@ -1094,17 +1120,27 @@
|
|
|
1094
1120
|
return;
|
|
1095
1121
|
}
|
|
1096
1122
|
caMeta = {}; rows.forEach(a => { caMeta[a.name] = a; });
|
|
1123
|
+
const own = rows.filter(a => a.source !== "shared"), shared = rows.filter(a => a.source === "shared");
|
|
1097
1124
|
const kaSel = $("key-agent");
|
|
1098
1125
|
if (kaSel) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
:
|
|
1126
|
+
const opt = (a) => `<option value="${esc(a.name)}">${esc(a.name)}${a.shared_by ? " — from " + esc(a.shared_by) : ""}</option>`;
|
|
1127
|
+
kaSel.innerHTML = !rows.length ? `<option value="">(no agents yet)</option>`
|
|
1128
|
+
: !shared.length ? own.map(opt).join("")
|
|
1129
|
+
: (own.length ? `<optgroup label="Your agents">${own.map(opt).join("")}</optgroup>` : "")
|
|
1130
|
+
+ `<optgroup label="Shared with you">${shared.map(opt).join("")}</optgroup>`;
|
|
1102
1131
|
onAgentSelect();
|
|
1103
1132
|
}
|
|
1104
1133
|
// Custom agents: full control for everyone (view / Edit-content / enable /
|
|
1105
|
-
// disable / delete) — RBAC applies only to department agents.
|
|
1134
|
+
// disable / delete) — RBAC applies only to department agents. Agents shared
|
|
1135
|
+
// with you (accepted) are someone else's: Run and Remove only.
|
|
1106
1136
|
$("local-agents").innerHTML = rows.length ? rows.map(a => {
|
|
1107
1137
|
const enc = encodeURIComponent(a.name);
|
|
1138
|
+
if (a.source === "shared") {
|
|
1139
|
+
const from = `<span class="pill shared">shared</span> <span class="muted" style="font-size:12.5px">from <b>${esc(a.shared_by || "?")}</b>${a.via ? " · via " + esc(a.via) : ""}</span>`;
|
|
1140
|
+
return agentCardHTML(a, `${from}<span class="spacer" style="flex:1"></span>
|
|
1141
|
+
<a class="btn primary" href="/agents.html?agent=${enc}">Run</a>
|
|
1142
|
+
${a.share_id ? `<button class="btn ghost danger" data-unshare="${esc(a.share_id)}" data-name="${esc(a.name)}">Remove</button>` : ""}`);
|
|
1143
|
+
}
|
|
1108
1144
|
const actions = [
|
|
1109
1145
|
`<a class="btn primary" href="/agents.html?agent=${enc}">Run</a>`,
|
|
1110
1146
|
`<button class="btn" data-edit="${esc(a.name)}">Edit</button>`,
|
|
@@ -1117,6 +1153,7 @@
|
|
|
1117
1153
|
: "<div class='empty'>No custom agents assigned to you yet. Create one under <b>Your platform agents</b> below, or in the web app.</div>";
|
|
1118
1154
|
|
|
1119
1155
|
wireContentToggles($("local-agents"));
|
|
1156
|
+
$("local-agents").querySelectorAll("[data-unshare]").forEach(b => b.onclick = () => removeShared(b.dataset.unshare, b.dataset.name));
|
|
1120
1157
|
$("local-agents").querySelectorAll("[data-enable]").forEach(b => b.onclick = () => setStatus(b.dataset.enable, "active"));
|
|
1121
1158
|
$("local-agents").querySelectorAll("[data-disable]").forEach(b => b.onclick = () => setStatus(b.dataset.disable, "disabled"));
|
|
1122
1159
|
$("local-agents").querySelectorAll("[data-del]").forEach(b => b.onclick = async () => {
|
|
@@ -1187,6 +1224,17 @@
|
|
|
1187
1224
|
if ($("key-add-toggle")) $("key-add-toggle").textContent = "+ Add API key";
|
|
1188
1225
|
}
|
|
1189
1226
|
|
|
1227
|
+
// Drop an agent someone shared with you (they can share it again later).
|
|
1228
|
+
async function removeShared(shareId, name) {
|
|
1229
|
+
if (!confirm(`Remove “${name}” from your agents? The owner can share it with you again.`)) return;
|
|
1230
|
+
try {
|
|
1231
|
+
await api(`/api/ui/agents/shares/${encodeURIComponent(shareId)}`, { method: "DELETE" });
|
|
1232
|
+
toast("Removed " + name, "ok");
|
|
1233
|
+
} catch (e) { toast("Remove failed: " + (e.message || ""), "err"); }
|
|
1234
|
+
document.dispatchEvent(new CustomEvent("agentos:shares-changed"));
|
|
1235
|
+
}
|
|
1236
|
+
document.addEventListener("agentos:shares-changed", () => { loadLocalAgents(); cfLoad(); });
|
|
1237
|
+
|
|
1190
1238
|
function onAgentSelect() {
|
|
1191
1239
|
const m = caMeta[$("key-agent") && $("key-agent").value];
|
|
1192
1240
|
if ($("ca-about")) $("ca-about").textContent = m ? (m.description || "No description provided.") : "Pick an agent.";
|
|
@@ -1481,10 +1529,19 @@
|
|
|
1481
1529
|
<button class="btn danger cf-del" data-id="${esc(a.id)}" data-name="${esc(a.name)}">Delete</button>`
|
|
1482
1530
|
: `<span class="muted" style="font-size:12px">read-only</span>`}
|
|
1483
1531
|
</div>`;
|
|
1484
|
-
const personal = d.personal || [], dept = d.department || [];
|
|
1485
|
-
|
|
1532
|
+
const personal = d.personal || [], dept = d.department || [], shared = d.shared || [];
|
|
1533
|
+
const sharedRow = (a) => `<div class="cf-item">
|
|
1534
|
+
<span class="n">${esc(a.name)}</span>
|
|
1535
|
+
<span class="pill shared">shared</span>
|
|
1536
|
+
<span class="muted" style="font-size:12.5px">from <b>${esc(a.shared_by || "?")}</b>${a.via ? " · via " + esc(a.via) : ""}</span>
|
|
1537
|
+
<a class="btn" href="/agents.html?agent=${encodeURIComponent(a.name)}">Run</a>
|
|
1538
|
+
${a.share_id ? `<button class="btn ghost danger cf-unshare" data-id="${esc(a.share_id)}" data-name="${esc(a.name)}">Remove</button>` : ""}
|
|
1539
|
+
</div>`;
|
|
1540
|
+
box.innerHTML = (personal.length || dept.length || shared.length)
|
|
1486
1541
|
? personal.map(a => row(a, true)).join("") + dept.map(a => row(a, false)).join("")
|
|
1542
|
+
+ (shared.length ? `<div class="cf-group">Shared with you</div>` + shared.map(sharedRow).join("") : "")
|
|
1487
1543
|
: `<div class="empty">No platform agents yet — press <b>New agent</b>.</div>`;
|
|
1544
|
+
box.querySelectorAll(".cf-unshare").forEach(b => b.onclick = () => removeShared(b.dataset.id, b.dataset.name));
|
|
1488
1545
|
box.querySelectorAll(".cf-edit").forEach(b => b.onclick = () => cfOpen(b.dataset.id));
|
|
1489
1546
|
box.querySelectorAll(".cf-del").forEach(b => b.onclick = () => cfDelete(b.dataset.id, b.dataset.name));
|
|
1490
1547
|
} catch (e) {
|
|
@@ -2353,7 +2410,7 @@
|
|
|
2353
2410
|
ev.preventDefault();
|
|
2354
2411
|
loadHistory(a.getAttribute("data-file"));
|
|
2355
2412
|
}));
|
|
2356
|
-
$("sc-file-body").
|
|
2413
|
+
$("sc-file-body").innerHTML = colorLog(d.content);
|
|
2357
2414
|
} catch (e) { toast("Could not load run history", "err"); }
|
|
2358
2415
|
}
|
|
2359
2416
|
|
|
@@ -2869,7 +2926,7 @@
|
|
|
2869
2926
|
const es = new EventSource("/api/ui/logs/stream?token=" + encodeURIComponent(TOKEN));
|
|
2870
2927
|
es.onmessage = (ev) => {
|
|
2871
2928
|
const atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 40;
|
|
2872
|
-
box.
|
|
2929
|
+
box.insertAdjacentHTML("beforeend", (box.childNodes.length ? "\n" : "") + colorLogLine(ev.data));
|
|
2873
2930
|
if (atBottom) box.scrollTop = box.scrollHeight;
|
|
2874
2931
|
};
|
|
2875
2932
|
es.onerror = () => { es.close(); setTimeout(connectLog, 3000); };
|
|
@@ -139,6 +139,17 @@ tr:last-child td { border-bottom: none; }
|
|
|
139
139
|
white-space: pre-wrap; word-break: break-word;
|
|
140
140
|
}
|
|
141
141
|
.log:empty::before { content: "Waiting for activity…"; color: #64748b; }
|
|
142
|
+
/* Log colours — the panel's twin of sdk/log_colors.py (the .log box is always dark). */
|
|
143
|
+
.log b { color: #f1f5f9; }
|
|
144
|
+
.log .lg-done { color: #4ade80; }
|
|
145
|
+
.log .lg-todo { color: #22d3ee; }
|
|
146
|
+
.log .lg-prog { color: #facc15; }
|
|
147
|
+
.log .lg-back { color: #c084fc; }
|
|
148
|
+
.log .lg-bad { color: #f87171; }
|
|
149
|
+
.log .lg-warn { color: #fbbf24; }
|
|
150
|
+
.log .lg-good { color: #86efac; }
|
|
151
|
+
.log .lg-head { color: #93c5fd; font-weight: 700; }
|
|
152
|
+
.log .lg-dim { color: #64748b; }
|
|
142
153
|
|
|
143
154
|
/* ── Toast ── */
|
|
144
155
|
#toasts { position: fixed; right: 18px; bottom: 18px; display: flex; flex-direction: column; gap: 10px; z-index: 100; }
|
|
@@ -857,3 +868,7 @@ select:focus, input[type=number]:focus, input[type=time]:focus, input[type=datet
|
|
|
857
868
|
.hp-ask-row { display: grid; gap: 6px; font-size: 14px; }
|
|
858
869
|
.hp-ask-row input { width: 100%; }
|
|
859
870
|
.hp-asks-send { justify-self: start; }
|
|
871
|
+
|
|
872
|
+
/* Agents shared with you, on the Custom Agents page */
|
|
873
|
+
.pill.shared { background: color-mix(in srgb, var(--violet, #7c3aed) 14%, transparent); color: var(--violet, #7c3aed); }
|
|
874
|
+
.cf-group { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); padding: 14px 0 4px; }
|
|
@@ -168,3 +168,91 @@ class TestUiAgentsAll:
|
|
|
168
168
|
with patch("sdk.client.BackendClient.from_config", return_value=client):
|
|
169
169
|
out = await api.agents_all(department_id="d-1")
|
|
170
170
|
assert [(a["name"], a["source"]) for a in out["agents"]] == [("Shared", "custom")]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _shared_row(name, *, by="Javaid Iqbal", via=None, share_id="sh-1"):
|
|
174
|
+
return {**_row(name, variables=("repo",)), "relation_id": "someone-else", "source": "shared",
|
|
175
|
+
"share_id": share_id, "shared_by": {"id": "u-j", "name": by, "email": "j@x"},
|
|
176
|
+
"via_team": via}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TestSharedAgentsOnCustomAgentsPage:
|
|
180
|
+
"""Accepted shares appear on Custom Agents next to your own — by NAME, read-only."""
|
|
181
|
+
|
|
182
|
+
@pytest.mark.asyncio
|
|
183
|
+
async def test_installed_list_includes_accepted_shares(self):
|
|
184
|
+
from sdk.ui import api
|
|
185
|
+
|
|
186
|
+
client = SimpleNamespace(
|
|
187
|
+
list_custom_agents=AsyncMock(return_value=[_row("mine"), _row("clash")]),
|
|
188
|
+
list_shared_agents=AsyncMock(return_value=[
|
|
189
|
+
_shared_row("release-notes", via={"id": "d1", "name": "Machine Learning", "kind": "department"}),
|
|
190
|
+
_shared_row("clash")]),
|
|
191
|
+
)
|
|
192
|
+
with patch("sdk.client.BackendClient.from_config", return_value=client):
|
|
193
|
+
out = await api.agents()
|
|
194
|
+
names = [(a["name"], a.get("source")) for a in out["agents"]]
|
|
195
|
+
assert names == [("mine", None), ("clash", None), ("release-notes", "shared")]
|
|
196
|
+
shared = out["agents"][-1]
|
|
197
|
+
assert shared["shared_by"] == "Javaid Iqbal" and shared["share_id"] == "sh-1"
|
|
198
|
+
assert shared["via"] == "department Machine Learning"
|
|
199
|
+
assert out["shared_error"] == ""
|
|
200
|
+
|
|
201
|
+
@pytest.mark.asyncio
|
|
202
|
+
async def test_backend_without_sharing_is_not_an_error(self):
|
|
203
|
+
from sdk.exceptions import BackendFeatureMissing
|
|
204
|
+
from sdk.ui import api
|
|
205
|
+
|
|
206
|
+
client = SimpleNamespace(
|
|
207
|
+
list_custom_agents=AsyncMock(return_value=[_row("mine")]),
|
|
208
|
+
list_shared_agents=AsyncMock(side_effect=BackendFeatureMissing("Agent sharing", "u")),
|
|
209
|
+
)
|
|
210
|
+
with patch("sdk.client.BackendClient.from_config", return_value=client):
|
|
211
|
+
out = await api.agents()
|
|
212
|
+
assert [a["name"] for a in out["agents"]] == ["mine"] and out["shared_error"] == ""
|
|
213
|
+
|
|
214
|
+
@pytest.mark.asyncio
|
|
215
|
+
async def test_platform_list_has_a_shared_group(self):
|
|
216
|
+
from sdk.ui import api
|
|
217
|
+
|
|
218
|
+
client = SimpleNamespace(
|
|
219
|
+
list_gateway_agents_by_scope=AsyncMock(return_value={"personal": [], "department": []}),
|
|
220
|
+
list_shared_agents=AsyncMock(return_value=[_shared_row("release-notes", via={"id": "t", "name": "AI Team"})]),
|
|
221
|
+
)
|
|
222
|
+
with patch("sdk.client.BackendClient.from_config", return_value=client):
|
|
223
|
+
out = await api.custom_agents_list()
|
|
224
|
+
assert out["shared"] == [{"id": "id-release-notes", "name": "release-notes", "status": "active",
|
|
225
|
+
"shared_by": "Javaid Iqbal", "via": "team AI Team", "share_id": "sh-1"}]
|
|
226
|
+
|
|
227
|
+
@pytest.mark.asyncio
|
|
228
|
+
async def test_inputs_of_a_platform_or_shared_agent_render(self):
|
|
229
|
+
from sdk.ui import api
|
|
230
|
+
|
|
231
|
+
client = SimpleNamespace(
|
|
232
|
+
list_custom_agents=AsyncMock(return_value=[]),
|
|
233
|
+
list_department_agents=AsyncMock(return_value=[]),
|
|
234
|
+
list_shared_agents=AsyncMock(return_value=[
|
|
235
|
+
{**_shared_row("release-notes"),
|
|
236
|
+
"prompt": {"system_prompt": "Write notes for {{repo}} since {{tag}}.",
|
|
237
|
+
"variables": [{"name": "repo"}, {"name": "tag"}]}}]),
|
|
238
|
+
)
|
|
239
|
+
with patch("sdk.client.BackendClient.from_config", return_value=client), \
|
|
240
|
+
patch("sdk.agents.manifest.load_agents", return_value=[]), \
|
|
241
|
+
patch("sdk.agents.chat._find_agent", return_value=None):
|
|
242
|
+
out = await api.agent_vars(name="release-notes")
|
|
243
|
+
assert out["variables"] == ["repo", "tag"]
|
|
244
|
+
|
|
245
|
+
@pytest.mark.asyncio
|
|
246
|
+
async def test_405_from_an_old_backend_means_not_deployed(self):
|
|
247
|
+
from sdk.client import BackendClient
|
|
248
|
+
from sdk.exceptions import BackendFeatureMissing
|
|
249
|
+
|
|
250
|
+
client = BackendClient.__new__(BackendClient)
|
|
251
|
+
client._base_url, client._headers = "https://b", {}
|
|
252
|
+
client._url = lambda p: "https://b" + p
|
|
253
|
+
resp = SimpleNamespace(status_code=405, json=lambda: {})
|
|
254
|
+
http = AsyncMock()
|
|
255
|
+
http.__aenter__.return_value.get = AsyncMock(return_value=resp)
|
|
256
|
+
with patch("sdk.client.httpx.AsyncClient", return_value=http):
|
|
257
|
+
with pytest.raises(BackendFeatureMissing):
|
|
258
|
+
await client.list_shared_agents()
|