meshagent-cli 0.51.4__tar.gz → 0.51.6__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.
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/PKG-INFO +17 -17
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/containers.py +13 -4
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/containers_test.py +32 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/image.py +159 -73
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/image_test.py +69 -6
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/routes.py +12 -0
- meshagent_cli-0.51.6/meshagent/cli/version.py +1 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent_cli.egg-info/PKG-INFO +17 -17
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent_cli.egg-info/requires.txt +16 -16
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/pyproject.toml +16 -16
- meshagent_cli-0.51.4/meshagent/cli/version.py +0 -1
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/LICENSE +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/README.md +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/__init__.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/_filedrop.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/agent.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/agent_cli_options_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/agent_package_cli.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/agent_package_cli_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/agents.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/agents_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/ask.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/ask_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/async_typer.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/async_typer_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/auth.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/auth_async.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/auth_async_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/auth_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/call.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/chatbot.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/cli.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/cli_mcp.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/cli_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/common_options.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/config.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/config_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/__init__.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/backend-agent/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/contact-form/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/slack-channel/channel.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/slack-channel/scripts/configure-slack.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/slack-channel/scripts/setup-slack-dev-route.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/slack-channel/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/task-queue-dashboard/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/telegram-channel/channel.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/telegram-channel/scripts/configure-telegram.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/telegram-channel/scripts/create-bot-token.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/telegram-channel/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/twilio-channel/channel.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/twilio-channel/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/webserver/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/whatsapp-channel/channel.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_project_templates/python/whatsapp-channel/server.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/create_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/dataset.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/dataset_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/developer.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/developer_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/doctor.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/doctor_dockerfiles/__init__.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/doctor_templates/__init__.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/doctor_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/feeds.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/feeds_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/helper.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/helper_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/helpers.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/host.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/iam.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/iam_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/launch.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/launch_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/llm.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/llm_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/local_settings.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/local_settings_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/mailbot.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/mailbot_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/mailboxes.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/mailboxes_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/meeting_transcriber.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/memory.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/memory_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/meshagent_images.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/meshagent_images_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/messaging.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/messaging_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/mounts.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/multi.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/oci_archive.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/oci_archive_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/participant_token.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/port.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/port_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/preamble_rules.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/preamble_rules_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/process.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/process_live_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/process_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/projects.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/projects_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/queue.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/queue_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/registry.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/registry_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/room.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/room_connect.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/room_connect_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/room_services.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/room_services_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/rooms.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/rooms_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/root_commands.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/root_commands_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/routes_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sample_channel_http_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sample_channel_live_e2e_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/scheduled_tasks.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/scheduled_tasks_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/secrets.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/secrets_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/service_accounts.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/service_accounts_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/services.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/services_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sessions.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sessions_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sqlite.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sqlite_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/storage.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/storage_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/subscriptions.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sync.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/sync_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/task_runner.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/task_runner_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/testing.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/thread_inspect.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/thread_inspect_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/thread_sidebar.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tool_call_summary.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tool_call_summary_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tool_integrations.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tool_integrations_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/__init__.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/auth_switch.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/auth_switch_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/create.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/deploy_room.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/deploy_room_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/project_activate.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/project_activate_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/setup.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/setup_splash_frames.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/tui/setup_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/version_check.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/version_check_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/voicebot.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/volumes.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/volumes_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/webserver.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/webserver_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/worker.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent/cli/worker_test.py +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent_cli.egg-info/SOURCES.txt +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent_cli.egg-info/dependency_links.txt +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent_cli.egg-info/entry_points.txt +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/meshagent_cli.egg-info/top_level.txt +0 -0
- {meshagent_cli-0.51.4 → meshagent_cli-0.51.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.51.
|
|
3
|
+
Version: 0.51.6
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -22,8 +22,8 @@ Requires-Dist: openpyxl~=3.1
|
|
|
22
22
|
Requires-Dist: xlsxwriter~=3.2
|
|
23
23
|
Requires-Dist: pathspec<2,>=1.0.3
|
|
24
24
|
Requires-Dist: zstandard~=0.25.0
|
|
25
|
-
Requires-Dist: meshagent-llm-proxy==0.51.
|
|
26
|
-
Requires-Dist: meshagent-codex==0.51.
|
|
25
|
+
Requires-Dist: meshagent-llm-proxy==0.51.6
|
|
26
|
+
Requires-Dist: meshagent-codex==0.51.6
|
|
27
27
|
Requires-Dist: rich~=14.3.0
|
|
28
28
|
Requires-Dist: sounddevice~=0.5
|
|
29
29
|
Requires-Dist: textual<9.0,>=8.2.3
|
|
@@ -32,23 +32,23 @@ Requires-Dist: prompt-toolkit~=3.0.52
|
|
|
32
32
|
Requires-Dist: ascii-magic~=2.3
|
|
33
33
|
Requires-Dist: pillow~=11.3.0
|
|
34
34
|
Provides-Extra: all
|
|
35
|
-
Requires-Dist: meshagent-agents[all]==0.51.
|
|
36
|
-
Requires-Dist: meshagent-api[all]==0.51.
|
|
37
|
-
Requires-Dist: meshagent-commoncrawl==0.51.
|
|
38
|
-
Requires-Dist: meshagent-scrapy==0.51.
|
|
39
|
-
Requires-Dist: meshagent-computers==0.51.
|
|
40
|
-
Requires-Dist: meshagent-openai==0.51.
|
|
41
|
-
Requires-Dist: meshagent-anthropic==0.51.
|
|
42
|
-
Requires-Dist: meshagent-otel==0.51.
|
|
43
|
-
Requires-Dist: meshagent-mcp==0.51.
|
|
44
|
-
Requires-Dist: meshagent-tools==0.51.
|
|
35
|
+
Requires-Dist: meshagent-agents[all]==0.51.6; extra == "all"
|
|
36
|
+
Requires-Dist: meshagent-api[all]==0.51.6; extra == "all"
|
|
37
|
+
Requires-Dist: meshagent-commoncrawl==0.51.6; extra == "all"
|
|
38
|
+
Requires-Dist: meshagent-scrapy==0.51.6; extra == "all"
|
|
39
|
+
Requires-Dist: meshagent-computers==0.51.6; extra == "all"
|
|
40
|
+
Requires-Dist: meshagent-openai==0.51.6; extra == "all"
|
|
41
|
+
Requires-Dist: meshagent-anthropic==0.51.6; extra == "all"
|
|
42
|
+
Requires-Dist: meshagent-otel==0.51.6; extra == "all"
|
|
43
|
+
Requires-Dist: meshagent-mcp==0.51.6; extra == "all"
|
|
44
|
+
Requires-Dist: meshagent-tools==0.51.6; extra == "all"
|
|
45
45
|
Requires-Dist: supabase-auth~=2.28.0; extra == "all"
|
|
46
46
|
Requires-Dist: prompt-toolkit~=3.0.52; extra == "all"
|
|
47
47
|
Provides-Extra: mcp-service
|
|
48
|
-
Requires-Dist: meshagent-agents[all]==0.51.
|
|
49
|
-
Requires-Dist: meshagent-api==0.51.
|
|
50
|
-
Requires-Dist: meshagent-mcp==0.51.
|
|
51
|
-
Requires-Dist: meshagent-tools==0.51.
|
|
48
|
+
Requires-Dist: meshagent-agents[all]==0.51.6; extra == "mcp-service"
|
|
49
|
+
Requires-Dist: meshagent-api==0.51.6; extra == "mcp-service"
|
|
50
|
+
Requires-Dist: meshagent-mcp==0.51.6; extra == "mcp-service"
|
|
51
|
+
Requires-Dist: meshagent-tools==0.51.6; extra == "mcp-service"
|
|
52
52
|
Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
|
|
53
53
|
Dynamic: license-file
|
|
54
54
|
|
|
@@ -365,9 +365,15 @@ async def _drain_stream_plain(stream, *, show_progress: bool = True):
|
|
|
365
365
|
|
|
366
366
|
t1 = asyncio.create_task(_logs())
|
|
367
367
|
t2 = asyncio.create_task(_prog())
|
|
368
|
+
completed = False
|
|
368
369
|
try:
|
|
369
|
-
|
|
370
|
+
result = await stream
|
|
371
|
+
completed = True
|
|
372
|
+
return result
|
|
370
373
|
finally:
|
|
374
|
+
if not completed:
|
|
375
|
+
t1.cancel()
|
|
376
|
+
t2.cancel()
|
|
371
377
|
await asyncio.gather(t1, t2, return_exceptions=True)
|
|
372
378
|
|
|
373
379
|
|
|
@@ -511,8 +517,10 @@ async def _drain_stream_pretty(stream):
|
|
|
511
517
|
t_logs = asyncio.create_task(_logs())
|
|
512
518
|
t_prog = asyncio.create_task(_prog())
|
|
513
519
|
t_ui = asyncio.create_task(_refresh())
|
|
520
|
+
completed = False
|
|
514
521
|
try:
|
|
515
522
|
result = await stream
|
|
523
|
+
completed = True
|
|
516
524
|
return result
|
|
517
525
|
finally:
|
|
518
526
|
# Hide any still-visible tasks (e.g., indeterminate ones with total=None)
|
|
@@ -520,10 +528,11 @@ async def _drain_stream_pretty(stream):
|
|
|
520
528
|
progress.update(tid, visible=False)
|
|
521
529
|
live.update(render())
|
|
522
530
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
531
|
+
if not completed:
|
|
532
|
+
t_logs.cancel()
|
|
533
|
+
t_prog.cancel()
|
|
526
534
|
t_ui.cancel()
|
|
535
|
+
await asyncio.gather(t_logs, t_prog, t_ui, return_exceptions=True)
|
|
527
536
|
|
|
528
537
|
|
|
529
538
|
async def _with_client(
|
|
@@ -323,6 +323,38 @@ async def test_drain_stream_plain_strips_cri_log_prefixes(
|
|
|
323
323
|
assert capsys.readouterr().out == "step 1\nstep 2\n"
|
|
324
324
|
|
|
325
325
|
|
|
326
|
+
@pytest.mark.asyncio
|
|
327
|
+
async def test_drain_stream_plain_cancels_consumers_when_stream_is_cancelled() -> None:
|
|
328
|
+
consumers_cancelled = 0
|
|
329
|
+
|
|
330
|
+
class _BlockingStream:
|
|
331
|
+
async def _consume(self):
|
|
332
|
+
nonlocal consumers_cancelled
|
|
333
|
+
try:
|
|
334
|
+
await asyncio.Future()
|
|
335
|
+
yield None
|
|
336
|
+
finally:
|
|
337
|
+
consumers_cancelled += 1
|
|
338
|
+
|
|
339
|
+
def logs(self):
|
|
340
|
+
return self._consume()
|
|
341
|
+
|
|
342
|
+
def progress(self):
|
|
343
|
+
return self._consume()
|
|
344
|
+
|
|
345
|
+
def __await__(self):
|
|
346
|
+
return asyncio.Future().__await__()
|
|
347
|
+
|
|
348
|
+
task = asyncio.create_task(containers._drain_stream_plain(_BlockingStream()))
|
|
349
|
+
await asyncio.sleep(0)
|
|
350
|
+
task.cancel()
|
|
351
|
+
|
|
352
|
+
with pytest.raises(asyncio.CancelledError):
|
|
353
|
+
await asyncio.wait_for(task, timeout=0.5)
|
|
354
|
+
|
|
355
|
+
assert consumers_cancelled == 2
|
|
356
|
+
|
|
357
|
+
|
|
326
358
|
@pytest.mark.asyncio
|
|
327
359
|
async def test_stream_build_job_logs_and_wait_for_exit_uses_build_log_stream(
|
|
328
360
|
monkeypatch: pytest.MonkeyPatch,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import contextlib
|
|
4
5
|
import hashlib
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
@@ -12,7 +13,7 @@ import tempfile
|
|
|
12
13
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
13
14
|
from dataclasses import dataclass, field
|
|
14
15
|
from pathlib import Path, PurePosixPath
|
|
15
|
-
from typing import Annotated, Literal, Optional, Protocol, TYPE_CHECKING
|
|
16
|
+
from typing import Annotated, Literal, Optional, Protocol, TYPE_CHECKING, TypeVar
|
|
16
17
|
from urllib.parse import urlparse
|
|
17
18
|
|
|
18
19
|
import typer
|
|
@@ -124,8 +125,8 @@ class _DeployDomainPromptHandler(Protocol):
|
|
|
124
125
|
|
|
125
126
|
app = async_typer.AsyncTyper(help="Pack local directories as OCI images")
|
|
126
127
|
_BUILD_CONTEXT_CHUNK_SIZE = 1024 * 1024
|
|
127
|
-
_BUILD_WAIT_TIMEOUT_SECONDS = 600.0
|
|
128
128
|
_CLIENT_CLOSE_TIMEOUT_SECONDS = 2.0
|
|
129
|
+
_OPERATION_CANCEL_TIMEOUT_SECONDS = 1.0
|
|
129
130
|
_DEFAULT_CONTEXT_MOUNT_PATH = "/context"
|
|
130
131
|
_DEFAULT_REPOSITORY_TOKEN_TTL_SECONDS = 3600
|
|
131
132
|
_DEPLOY_CACHE_CLEANUP_TIMEOUT_SECONDS = 30.0
|
|
@@ -151,6 +152,7 @@ _PROJECT_KEY_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
|
151
152
|
_COOKIE_VALIDATION_METHOD = "cookie"
|
|
152
153
|
DeployValidationMode = Literal["default", "cookie", "none"]
|
|
153
154
|
ContainerTemplateOption = Literal["agent", "none"]
|
|
155
|
+
_T = TypeVar("_T")
|
|
154
156
|
_RESERVED_ROOM_SERVICE_PORTS_TEXT = ", ".join(
|
|
155
157
|
str(port) for port in sorted(RESERVED_ROOM_SERVICE_PORTS)
|
|
156
158
|
)
|
|
@@ -3758,6 +3760,7 @@ async def _wait_for_deployed_service_live(
|
|
|
3758
3760
|
domain: str | None,
|
|
3759
3761
|
liveness_path: str | None,
|
|
3760
3762
|
queue_backed_route: bool = False,
|
|
3763
|
+
expected_restart_count: int = 0,
|
|
3761
3764
|
status_handler: Callable[[str], Awaitable[None]] | None = None,
|
|
3762
3765
|
log_handler: Callable[[str], Awaitable[None]] | None = None,
|
|
3763
3766
|
) -> None:
|
|
@@ -3825,7 +3828,10 @@ async def _wait_for_deployed_service_live(
|
|
|
3825
3828
|
await asyncio.sleep(_DEPLOY_WAIT_POLL_INTERVAL_SECONDS)
|
|
3826
3829
|
continue
|
|
3827
3830
|
|
|
3828
|
-
if
|
|
3831
|
+
if (
|
|
3832
|
+
state.restart_count > expected_restart_count
|
|
3833
|
+
and state.last_exit_code not in (None, 0)
|
|
3834
|
+
):
|
|
3829
3835
|
if status_handler is None:
|
|
3830
3836
|
_print_service_exited_before_live(
|
|
3831
3837
|
service_name=service_name,
|
|
@@ -3860,23 +3866,17 @@ async def _wait_for_deployed_service_live(
|
|
|
3860
3866
|
and container_id is not None
|
|
3861
3867
|
and container_id != active_logs.container_id
|
|
3862
3868
|
):
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
)
|
|
3875
|
-
await status_handler(
|
|
3876
|
-
"Service container changed before the service was live: "
|
|
3877
|
-
f"{service_name} ({service_id}), exit code {exit_code_text}"
|
|
3878
|
-
)
|
|
3879
|
-
raise typer.Exit(code=1)
|
|
3869
|
+
await _stop_deploy_log_stream(active_logs=active_logs)
|
|
3870
|
+
await _emit_deploy_status(
|
|
3871
|
+
status_handler,
|
|
3872
|
+
rich_message=f"[cyan]Tailing container logs:[/] {container_id}",
|
|
3873
|
+
plain_message=f"Tailing container logs: {container_id}",
|
|
3874
|
+
)
|
|
3875
|
+
active_logs = _start_deploy_log_stream(
|
|
3876
|
+
client=client,
|
|
3877
|
+
container_id=container_id,
|
|
3878
|
+
log_handler=log_handler,
|
|
3879
|
+
)
|
|
3880
3880
|
|
|
3881
3881
|
if container_id is None or state.state != "running":
|
|
3882
3882
|
await asyncio.sleep(_DEPLOY_WAIT_POLL_INTERVAL_SECONDS)
|
|
@@ -3974,6 +3974,70 @@ async def _build_local_context_archive(
|
|
|
3974
3974
|
raise
|
|
3975
3975
|
|
|
3976
3976
|
|
|
3977
|
+
def _consume_background_task_result(task: asyncio.Future[object]) -> None:
|
|
3978
|
+
if task.cancelled():
|
|
3979
|
+
return
|
|
3980
|
+
with contextlib.suppress(Exception):
|
|
3981
|
+
task.exception()
|
|
3982
|
+
|
|
3983
|
+
|
|
3984
|
+
async def _await_room_build_operation(
|
|
3985
|
+
*,
|
|
3986
|
+
client: RoomClient,
|
|
3987
|
+
operation: Awaitable[_T],
|
|
3988
|
+
operation_name: str,
|
|
3989
|
+
) -> _T:
|
|
3990
|
+
loop = asyncio.get_running_loop()
|
|
3991
|
+
disconnected = loop.create_future()
|
|
3992
|
+
|
|
3993
|
+
def on_disconnected(*, reason: str | None = None) -> None:
|
|
3994
|
+
if not disconnected.done():
|
|
3995
|
+
disconnected.set_result(reason)
|
|
3996
|
+
|
|
3997
|
+
observes_disconnects = isinstance(client, RoomClient)
|
|
3998
|
+
if observes_disconnects:
|
|
3999
|
+
client.on("disconnected", on_disconnected)
|
|
4000
|
+
operation_task = asyncio.ensure_future(operation)
|
|
4001
|
+
try:
|
|
4002
|
+
done, _ = await asyncio.wait(
|
|
4003
|
+
{operation_task, disconnected},
|
|
4004
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
4005
|
+
)
|
|
4006
|
+
if operation_task in done:
|
|
4007
|
+
return await operation_task
|
|
4008
|
+
reason = disconnected.result()
|
|
4009
|
+
detail = f": {reason}" if reason else ""
|
|
4010
|
+
raise RuntimeError(
|
|
4011
|
+
f"room connection lost while {operation_name}{detail}; "
|
|
4012
|
+
"the in-flight operation cannot be resumed safely. Retry the deploy."
|
|
4013
|
+
)
|
|
4014
|
+
finally:
|
|
4015
|
+
if observes_disconnects:
|
|
4016
|
+
client.off("disconnected", on_disconnected)
|
|
4017
|
+
if not disconnected.done():
|
|
4018
|
+
disconnected.cancel()
|
|
4019
|
+
if not operation_task.done():
|
|
4020
|
+
operation_task.cancel()
|
|
4021
|
+
done, _ = await asyncio.wait(
|
|
4022
|
+
{operation_task}, timeout=_OPERATION_CANCEL_TIMEOUT_SECONDS
|
|
4023
|
+
)
|
|
4024
|
+
if operation_task not in done:
|
|
4025
|
+
operation_task.add_done_callback(_consume_background_task_result)
|
|
4026
|
+
|
|
4027
|
+
|
|
4028
|
+
async def _close_room_client(client: RoomClient) -> None:
|
|
4029
|
+
close_task = asyncio.create_task(client.__aexit__(None, None, None))
|
|
4030
|
+
done, _ = await asyncio.wait({close_task}, timeout=_CLIENT_CLOSE_TIMEOUT_SECONDS)
|
|
4031
|
+
if close_task in done:
|
|
4032
|
+
await close_task
|
|
4033
|
+
return
|
|
4034
|
+
close_task.cancel()
|
|
4035
|
+
close_task.add_done_callback(_consume_background_task_result)
|
|
4036
|
+
sys.stderr.write(
|
|
4037
|
+
"Room client did not close promptly after the build; abandoning cleanup.\n"
|
|
4038
|
+
)
|
|
4039
|
+
|
|
4040
|
+
|
|
3977
4041
|
async def _run_image_pack_stage(
|
|
3978
4042
|
*,
|
|
3979
4043
|
resolved_project_id: str | None,
|
|
@@ -4024,36 +4088,38 @@ async def _run_image_pack_stage(
|
|
|
4024
4088
|
)
|
|
4025
4089
|
},
|
|
4026
4090
|
)
|
|
4027
|
-
build_id = await
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4091
|
+
build_id = await _await_room_build_operation(
|
|
4092
|
+
client=client,
|
|
4093
|
+
operation=client.containers.build(
|
|
4094
|
+
tags=[parsed_tag.value],
|
|
4095
|
+
mount_path=_DEFAULT_CONTEXT_MOUNT_PATH,
|
|
4096
|
+
context_path=_DEFAULT_CONTEXT_MOUNT_PATH,
|
|
4097
|
+
dockerfile_path=_generated_pack_dockerfile_path(
|
|
4098
|
+
mount_path=_DEFAULT_CONTEXT_MOUNT_PATH
|
|
4099
|
+
),
|
|
4100
|
+
optimize_image=True,
|
|
4101
|
+
private=False,
|
|
4102
|
+
credentials=registry_credentials,
|
|
4103
|
+
builder_name=_default_builder_name(client=client),
|
|
4104
|
+
chunks=_iter_file_chunks(archive_path),
|
|
4105
|
+
size=archive_size,
|
|
4033
4106
|
),
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4107
|
+
operation_name="uploading the build context",
|
|
4108
|
+
)
|
|
4109
|
+
exit_code = await _await_room_build_operation(
|
|
4110
|
+
client=client,
|
|
4111
|
+
operation=_stream_build_job_logs_and_wait_for_exit(
|
|
4112
|
+
client=client,
|
|
4113
|
+
build_id=build_id,
|
|
4114
|
+
),
|
|
4115
|
+
operation_name="waiting for the image build and publish",
|
|
4040
4116
|
)
|
|
4041
|
-
try:
|
|
4042
|
-
exit_code = await asyncio.wait_for(
|
|
4043
|
-
_stream_build_job_logs_and_wait_for_exit(
|
|
4044
|
-
client=client,
|
|
4045
|
-
build_id=build_id,
|
|
4046
|
-
),
|
|
4047
|
-
timeout=_BUILD_WAIT_TIMEOUT_SECONDS,
|
|
4048
|
-
)
|
|
4049
|
-
except TimeoutError as exc:
|
|
4050
|
-
raise RuntimeError("timed out waiting for image build") from exc
|
|
4051
4117
|
if exit_code != 0:
|
|
4052
4118
|
raise typer.Exit(code=exit_code)
|
|
4053
4119
|
finally:
|
|
4054
4120
|
if context_archive_temp_dir is not None:
|
|
4055
4121
|
context_archive_temp_dir.cleanup()
|
|
4056
|
-
await client
|
|
4122
|
+
await _close_room_client(client)
|
|
4057
4123
|
await account_client.close()
|
|
4058
4124
|
|
|
4059
4125
|
|
|
@@ -4134,46 +4200,53 @@ async def _run_image_build_stage(
|
|
|
4134
4200
|
rich_message=f"[cyan]Uploading build context ({archive_size_text})...[/cyan]",
|
|
4135
4201
|
plain_message=f"Uploading build context ({archive_size_text})...",
|
|
4136
4202
|
)
|
|
4137
|
-
build_id = await
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4203
|
+
build_id = await _await_room_build_operation(
|
|
4204
|
+
client=client,
|
|
4205
|
+
operation=client.containers.build(
|
|
4206
|
+
tags=tags,
|
|
4207
|
+
mount_path=build_inputs.pack_spec.mount_path,
|
|
4208
|
+
context_path=build_inputs.context_path,
|
|
4209
|
+
dockerfile_path=build_inputs.dockerfile_path,
|
|
4210
|
+
optimize_image=optimize,
|
|
4211
|
+
private=private,
|
|
4212
|
+
credentials=credentials,
|
|
4213
|
+
builder_name=resolved_builder_name,
|
|
4214
|
+
chunks=_iter_file_chunks_with_progress(
|
|
4215
|
+
path=archive_path,
|
|
4216
|
+
size=archive_size,
|
|
4217
|
+
status_handler=status_handler,
|
|
4218
|
+
),
|
|
4148
4219
|
size=archive_size,
|
|
4149
|
-
status_handler=status_handler,
|
|
4150
4220
|
),
|
|
4151
|
-
|
|
4221
|
+
operation_name="uploading the build context",
|
|
4152
4222
|
)
|
|
4153
4223
|
await _emit_deploy_status(
|
|
4154
4224
|
status_handler,
|
|
4155
4225
|
rich_message="[cyan]Starting image build...[/cyan]",
|
|
4156
4226
|
plain_message="Starting image build...",
|
|
4157
4227
|
)
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4228
|
+
if log_handler is None:
|
|
4229
|
+
exit_code = await _await_room_build_operation(
|
|
4230
|
+
client=client,
|
|
4231
|
+
operation=(
|
|
4161
4232
|
_stream_build_job_logs_and_wait_for_exit(
|
|
4162
4233
|
client=client, build_id=build_id
|
|
4163
|
-
)
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4234
|
+
)
|
|
4235
|
+
),
|
|
4236
|
+
operation_name="waiting for the image build and publish",
|
|
4237
|
+
)
|
|
4238
|
+
else:
|
|
4239
|
+
exit_code = await _await_room_build_operation(
|
|
4240
|
+
client=client,
|
|
4241
|
+
operation=(
|
|
4168
4242
|
_stream_build_job_logs_and_wait_for_exit_tui(
|
|
4169
4243
|
client=client,
|
|
4170
4244
|
build_id=build_id,
|
|
4171
4245
|
log_handler=log_handler,
|
|
4172
|
-
)
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
raise RuntimeError("timed out waiting for image build") from exc
|
|
4246
|
+
)
|
|
4247
|
+
),
|
|
4248
|
+
operation_name="waiting for the image build and publish",
|
|
4249
|
+
)
|
|
4177
4250
|
if exit_code != 0:
|
|
4178
4251
|
await _emit_deploy_status(
|
|
4179
4252
|
status_handler,
|
|
@@ -4181,10 +4254,14 @@ async def _run_image_build_stage(
|
|
|
4181
4254
|
plain_message=f"Image build failed: exit code {exit_code}",
|
|
4182
4255
|
)
|
|
4183
4256
|
raise typer.Exit(code=exit_code)
|
|
4184
|
-
published_image = await
|
|
4257
|
+
published_image = await _await_room_build_operation(
|
|
4185
4258
|
client=client,
|
|
4186
|
-
|
|
4187
|
-
|
|
4259
|
+
operation=_resolve_completed_build_image(
|
|
4260
|
+
client=client,
|
|
4261
|
+
build_id=build_id,
|
|
4262
|
+
parsed_tag=parsed_tag,
|
|
4263
|
+
),
|
|
4264
|
+
operation_name="resolving the published image",
|
|
4188
4265
|
)
|
|
4189
4266
|
await _emit_deploy_status(
|
|
4190
4267
|
status_handler,
|
|
@@ -4200,7 +4277,7 @@ async def _run_image_build_stage(
|
|
|
4200
4277
|
finally:
|
|
4201
4278
|
if context_archive_temp_dir is not None:
|
|
4202
4279
|
context_archive_temp_dir.cleanup()
|
|
4203
|
-
await client
|
|
4280
|
+
await _close_room_client(client)
|
|
4204
4281
|
await account_client.close()
|
|
4205
4282
|
|
|
4206
4283
|
|
|
@@ -5165,6 +5242,14 @@ async def deploy_image(
|
|
|
5165
5242
|
if previous_runtime_state is not None
|
|
5166
5243
|
else None
|
|
5167
5244
|
)
|
|
5245
|
+
previous_restart_count = (
|
|
5246
|
+
previous_runtime_state.restart_count
|
|
5247
|
+
if previous_runtime_state is not None
|
|
5248
|
+
else 0
|
|
5249
|
+
)
|
|
5250
|
+
expected_restart_count = previous_restart_count + (
|
|
5251
|
+
0 if deploy_result.created else 1
|
|
5252
|
+
)
|
|
5168
5253
|
liveness_path = (
|
|
5169
5254
|
_resolve_domain_liveness_path(
|
|
5170
5255
|
service_spec=deploy_plan.spec,
|
|
@@ -5191,6 +5276,7 @@ async def deploy_image(
|
|
|
5191
5276
|
domain=domain,
|
|
5192
5277
|
liveness_path=liveness_path,
|
|
5193
5278
|
queue_backed_route=queue_backed_route,
|
|
5279
|
+
expected_restart_count=expected_restart_count,
|
|
5194
5280
|
status_handler=status_handler,
|
|
5195
5281
|
log_handler=log_handler,
|
|
5196
5282
|
),
|
|
@@ -83,6 +83,58 @@ def _fake_published_build_image(
|
|
|
83
83
|
)
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
@pytest.mark.asyncio
|
|
87
|
+
async def test_room_build_operation_fails_immediately_on_disconnect(
|
|
88
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
89
|
+
) -> None:
|
|
90
|
+
class _FakeRoomClient:
|
|
91
|
+
def __init__(self) -> None:
|
|
92
|
+
self.handlers: dict[str, list[object]] = {}
|
|
93
|
+
|
|
94
|
+
def on(self, event_name: str, handler) -> None:
|
|
95
|
+
self.handlers.setdefault(event_name, []).append(handler)
|
|
96
|
+
|
|
97
|
+
def off(self, event_name: str, handler) -> None:
|
|
98
|
+
self.handlers[event_name].remove(handler)
|
|
99
|
+
|
|
100
|
+
def disconnect(self, reason: str) -> None:
|
|
101
|
+
for handler in list(self.handlers.get("disconnected", [])):
|
|
102
|
+
handler(reason=reason)
|
|
103
|
+
|
|
104
|
+
operation_cancelled = asyncio.Event()
|
|
105
|
+
|
|
106
|
+
async def operation() -> None:
|
|
107
|
+
try:
|
|
108
|
+
await asyncio.Future()
|
|
109
|
+
finally:
|
|
110
|
+
operation_cancelled.set()
|
|
111
|
+
|
|
112
|
+
client = _FakeRoomClient()
|
|
113
|
+
monkeypatch.setattr(image, "RoomClient", _FakeRoomClient)
|
|
114
|
+
task = asyncio.create_task(
|
|
115
|
+
image._await_room_build_operation(
|
|
116
|
+
client=client,
|
|
117
|
+
operation=operation(),
|
|
118
|
+
operation_name="waiting for the image build and publish",
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
await asyncio.sleep(0)
|
|
122
|
+
client.disconnect("websocket closed with code 1006")
|
|
123
|
+
|
|
124
|
+
with pytest.raises(
|
|
125
|
+
RuntimeError,
|
|
126
|
+
match=(
|
|
127
|
+
"room connection lost while waiting for the image build and publish: "
|
|
128
|
+
"websocket closed with code 1006; the in-flight operation cannot be "
|
|
129
|
+
"resumed safely"
|
|
130
|
+
),
|
|
131
|
+
):
|
|
132
|
+
await asyncio.wait_for(task, timeout=0.5)
|
|
133
|
+
|
|
134
|
+
assert operation_cancelled.is_set()
|
|
135
|
+
assert client.handlers["disconnected"] == []
|
|
136
|
+
|
|
137
|
+
|
|
86
138
|
def _route_for_service(
|
|
87
139
|
*, domain: str, room_name: str, port: str, service_id: str
|
|
88
140
|
) -> Route:
|
|
@@ -2133,6 +2185,13 @@ async def test_run_image_build_stage_requests_repository_token_and_prepends_regi
|
|
|
2133
2185
|
class _FakeRoomClient:
|
|
2134
2186
|
def __init__(self) -> None:
|
|
2135
2187
|
self.containers = _FakeContainersClient()
|
|
2188
|
+
self.handlers: dict[str, list[object]] = {}
|
|
2189
|
+
|
|
2190
|
+
def on(self, event_name: str, handler) -> None:
|
|
2191
|
+
self.handlers.setdefault(event_name, []).append(handler)
|
|
2192
|
+
|
|
2193
|
+
def off(self, event_name: str, handler) -> None:
|
|
2194
|
+
self.handlers[event_name].remove(handler)
|
|
2136
2195
|
|
|
2137
2196
|
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
2138
2197
|
del exc_type, exc, tb
|
|
@@ -2431,7 +2490,8 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_service_li
|
|
|
2431
2490
|
ServiceRuntimeState(
|
|
2432
2491
|
service_id="service-1",
|
|
2433
2492
|
state="running",
|
|
2434
|
-
container_id="container-
|
|
2493
|
+
container_id="container-2",
|
|
2494
|
+
restart_count=3,
|
|
2435
2495
|
status=ServiceRuntimeStatus(
|
|
2436
2496
|
ports=[
|
|
2437
2497
|
ServicePortRuntimeState(
|
|
@@ -2445,7 +2505,8 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_service_li
|
|
|
2445
2505
|
ServiceRuntimeState(
|
|
2446
2506
|
service_id="service-1",
|
|
2447
2507
|
state="running",
|
|
2448
|
-
container_id="container-
|
|
2508
|
+
container_id="container-2",
|
|
2509
|
+
restart_count=3,
|
|
2449
2510
|
status=ServiceRuntimeStatus(
|
|
2450
2511
|
ports=[
|
|
2451
2512
|
ServicePortRuntimeState(
|
|
@@ -2457,7 +2518,6 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_service_li
|
|
|
2457
2518
|
),
|
|
2458
2519
|
),
|
|
2459
2520
|
]
|
|
2460
|
-
fake_active_logs = SimpleNamespace(container_id="container-1")
|
|
2461
2521
|
|
|
2462
2522
|
class _FakeServices:
|
|
2463
2523
|
def __init__(self, runtime_states: list[ServiceRuntimeState]) -> None:
|
|
@@ -2479,7 +2539,7 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_service_li
|
|
|
2479
2539
|
def _fake_start_deploy_log_stream(*, client, container_id: str, log_handler=None):
|
|
2480
2540
|
del client, log_handler
|
|
2481
2541
|
captured["started_logs"].append(container_id)
|
|
2482
|
-
return
|
|
2542
|
+
return SimpleNamespace(container_id=container_id)
|
|
2483
2543
|
|
|
2484
2544
|
async def _fake_stop_deploy_log_stream(*, active_logs) -> None:
|
|
2485
2545
|
captured["stopped_logs"].append(active_logs)
|
|
@@ -2508,8 +2568,11 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_service_li
|
|
|
2508
2568
|
liveness_path="/ready",
|
|
2509
2569
|
)
|
|
2510
2570
|
|
|
2511
|
-
assert captured["started_logs"] == ["container-1"]
|
|
2512
|
-
assert captured["stopped_logs"] == [
|
|
2571
|
+
assert captured["started_logs"] == ["container-1", "container-2"]
|
|
2572
|
+
assert [active.container_id for active in captured["stopped_logs"]] == [
|
|
2573
|
+
"container-1",
|
|
2574
|
+
"container-2",
|
|
2575
|
+
]
|
|
2513
2576
|
assert any("Service liveness is ready" in message for message in captured["prints"])
|
|
2514
2577
|
|
|
2515
2578
|
|
|
@@ -460,6 +460,11 @@ async def route_update(
|
|
|
460
460
|
if current_content is not None
|
|
461
461
|
else ""
|
|
462
462
|
),
|
|
463
|
+
notFound=(
|
|
464
|
+
current_content.notFound
|
|
465
|
+
if current_content is not None
|
|
466
|
+
else None
|
|
467
|
+
),
|
|
463
468
|
cors=(
|
|
464
469
|
parsed_cors
|
|
465
470
|
if parsed_cors is not None
|
|
@@ -512,6 +517,13 @@ async def route_update(
|
|
|
512
517
|
current_path.pathType if current_path is not None else "prefix"
|
|
513
518
|
),
|
|
514
519
|
"stripPrefix": strip_prefix,
|
|
520
|
+
**(
|
|
521
|
+
{"unavailable": current_path.unavailable}
|
|
522
|
+
if current_path is not None
|
|
523
|
+
and target.get("targetPort") is not None
|
|
524
|
+
and current_path.unavailable is not None
|
|
525
|
+
else {}
|
|
526
|
+
),
|
|
515
527
|
**target,
|
|
516
528
|
},
|
|
517
529
|
*[item.model_dump(mode="python") for item in route.spec.paths[1:]],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.51.6"
|