meshagent-cli 0.45.7__tar.gz → 0.46.0__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.45.7/meshagent_cli.egg-info → meshagent_cli-0.46.0}/PKG-INFO +17 -17
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/containers.py +5 -1
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/containers_test.py +58 -6
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/image.py +28 -49
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/image_test.py +32 -88
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_services.py +27 -4
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_services_test.py +62 -2
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sessions.py +10 -1
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sessions_test.py +83 -0
- meshagent_cli-0.46.0/meshagent/cli/version.py +1 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0/meshagent_cli.egg-info}/PKG-INFO +17 -17
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/requires.txt +16 -16
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/pyproject.toml +16 -16
- meshagent_cli-0.45.7/meshagent/cli/version.py +0 -1
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/LICENSE +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/README.md +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/__init__.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/_filedrop.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent_cli_options_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent_package_cli.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agent_package_cli_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agents.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/agents_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/ask.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/ask_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/async_typer.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/async_typer_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth_async.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth_async_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/auth_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/call.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/chatbot.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/cli.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/cli_mcp.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/cli_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/common_options.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/config.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/config_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/__init__.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/backend-agent/server.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/contact-form/server.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/task-queue-dashboard/server.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_project_templates/python/webserver/server.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/create_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/dataset.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/dataset_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/developer.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/developer_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor_dockerfiles/__init__.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor_templates/__init__.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/doctor_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/feeds.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/helper.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/helper_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/helpers.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/host.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/iam.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/iam_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/launch.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/launch_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/llm.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/llm_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/local_settings.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/local_settings_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/mailbot.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/mailbot_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/mailboxes.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/meeting_transcriber.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/memory.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/memory_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/meshagent_images.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/meshagent_images_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/messaging.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/messaging_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/multi.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/oci_archive.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/oci_archive_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/participant_token.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/port.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/port_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/preamble_rules.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/preamble_rules_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/process.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/process_live_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/process_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/projects.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/projects_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/queue.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/queue_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/registry.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/registry_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_connect.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/room_connect_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/rooms.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/rooms_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/root_commands.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/root_commands_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/routes.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/scheduled_tasks.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/scheduled_tasks_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/secrets.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/secrets_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/service_accounts.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/service_accounts_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/services.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/services_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/storage.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/storage_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/subscriptions.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sync.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/sync_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/task_runner.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/task_runner_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/testing.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/thread_inspect.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/thread_inspect_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/thread_sidebar.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_call_summary.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_call_summary_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_integrations.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tool_integrations_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/__init__.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/auth_switch.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/auth_switch_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/create.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/deploy_room.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/deploy_room_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/project_activate.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/project_activate_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/setup.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/setup_splash_frames.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/tui/setup_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/version_check.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/version_check_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/voicebot.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/webserver.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/webserver_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/worker.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent/cli/worker_test.py +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/SOURCES.txt +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/dependency_links.txt +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/entry_points.txt +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/meshagent_cli.egg-info/top_level.txt +0 -0
- {meshagent_cli-0.45.7 → meshagent_cli-0.46.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.46.0
|
|
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.
|
|
26
|
-
Requires-Dist: meshagent-codex==0.
|
|
25
|
+
Requires-Dist: meshagent-llm-proxy==0.46.0
|
|
26
|
+
Requires-Dist: meshagent-codex==0.46.0
|
|
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.
|
|
36
|
-
Requires-Dist: meshagent-api[all]==0.
|
|
37
|
-
Requires-Dist: meshagent-commoncrawl==0.
|
|
38
|
-
Requires-Dist: meshagent-scrapy==0.
|
|
39
|
-
Requires-Dist: meshagent-computers==0.
|
|
40
|
-
Requires-Dist: meshagent-openai==0.
|
|
41
|
-
Requires-Dist: meshagent-anthropic==0.
|
|
42
|
-
Requires-Dist: meshagent-otel==0.
|
|
43
|
-
Requires-Dist: meshagent-mcp==0.
|
|
44
|
-
Requires-Dist: meshagent-tools==0.
|
|
35
|
+
Requires-Dist: meshagent-agents[all]==0.46.0; extra == "all"
|
|
36
|
+
Requires-Dist: meshagent-api[all]==0.46.0; extra == "all"
|
|
37
|
+
Requires-Dist: meshagent-commoncrawl==0.46.0; extra == "all"
|
|
38
|
+
Requires-Dist: meshagent-scrapy==0.46.0; extra == "all"
|
|
39
|
+
Requires-Dist: meshagent-computers==0.46.0; extra == "all"
|
|
40
|
+
Requires-Dist: meshagent-openai==0.46.0; extra == "all"
|
|
41
|
+
Requires-Dist: meshagent-anthropic==0.46.0; extra == "all"
|
|
42
|
+
Requires-Dist: meshagent-otel==0.46.0; extra == "all"
|
|
43
|
+
Requires-Dist: meshagent-mcp==0.46.0; extra == "all"
|
|
44
|
+
Requires-Dist: meshagent-tools==0.46.0; 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.
|
|
49
|
-
Requires-Dist: meshagent-api==0.
|
|
50
|
-
Requires-Dist: meshagent-mcp==0.
|
|
51
|
-
Requires-Dist: meshagent-tools==0.
|
|
48
|
+
Requires-Dist: meshagent-agents[all]==0.46.0; extra == "mcp-service"
|
|
49
|
+
Requires-Dist: meshagent-api==0.46.0; extra == "mcp-service"
|
|
50
|
+
Requires-Dist: meshagent-mcp==0.46.0; extra == "mcp-service"
|
|
51
|
+
Requires-Dist: meshagent-tools==0.46.0; extra == "mcp-service"
|
|
52
52
|
Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
|
|
53
53
|
Dynamic: license-file
|
|
54
54
|
|
|
@@ -538,8 +538,12 @@ async def _with_client(
|
|
|
538
538
|
|
|
539
539
|
connection = await account_client.connect_room(project_id=project_id, room=room)
|
|
540
540
|
|
|
541
|
+
room_url = connection.room_url
|
|
542
|
+
if os.getenv("MESHAGENT_ROOM_URL"):
|
|
543
|
+
room_url = websocket_room_url(room_name=room)
|
|
544
|
+
|
|
541
545
|
proto = WebSocketClientProtocol(
|
|
542
|
-
url=
|
|
546
|
+
url=room_url,
|
|
543
547
|
token=connection.jwt,
|
|
544
548
|
)
|
|
545
549
|
client_cm = RoomClient(protocol_factory=proto.create_factory())
|
|
@@ -525,6 +525,7 @@ async def test_images_inspect_renders_detail_tables(
|
|
|
525
525
|
async def test_with_client_uses_configured_websocket_room_url(
|
|
526
526
|
monkeypatch: pytest.MonkeyPatch,
|
|
527
527
|
) -> None:
|
|
528
|
+
monkeypatch.delenv("MESHAGENT_ROOM_URL", raising=False)
|
|
528
529
|
connection = SimpleNamespace(
|
|
529
530
|
jwt="jwt-token",
|
|
530
531
|
room_url="wss://room-router.meshagent.dev/custom/room-endpoint",
|
|
@@ -560,11 +561,6 @@ async def test_with_client_uses_configured_websocket_room_url(
|
|
|
560
561
|
monkeypatch.setattr(containers, "get_client", _fake_get_client)
|
|
561
562
|
monkeypatch.setattr(containers, "resolve_project_id", _fake_resolve_project_id)
|
|
562
563
|
monkeypatch.setattr(containers, "resolve_room", lambda room: f"{room}-resolved")
|
|
563
|
-
monkeypatch.setattr(
|
|
564
|
-
containers,
|
|
565
|
-
"websocket_room_url",
|
|
566
|
-
lambda room_name: f"wss://configured-router/{room_name}",
|
|
567
|
-
)
|
|
568
564
|
monkeypatch.setattr(containers, "WebSocketClientProtocol", _FakeProtocol)
|
|
569
565
|
monkeypatch.setattr(containers, "RoomClient", _FakeRoomClient)
|
|
570
566
|
|
|
@@ -579,7 +575,63 @@ async def test_with_client_uses_configured_websocket_room_url(
|
|
|
579
575
|
]
|
|
580
576
|
assert protocol_calls == [
|
|
581
577
|
{
|
|
582
|
-
"url": "wss://
|
|
578
|
+
"url": "wss://room-router.meshagent.dev/custom/room-endpoint",
|
|
579
|
+
"token": "jwt-token",
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
assert isinstance(client, _FakeRoomClient)
|
|
583
|
+
assert client.enter_calls == 1
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@pytest.mark.asyncio
|
|
587
|
+
async def test_with_client_allows_environment_room_url_override(
|
|
588
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
589
|
+
) -> None:
|
|
590
|
+
monkeypatch.setenv("MESHAGENT_ROOM_URL", "wss://override.meshagent.test")
|
|
591
|
+
connection = SimpleNamespace(
|
|
592
|
+
jwt="jwt-token",
|
|
593
|
+
room_url="wss://room-router.meshagent.dev/custom/room-endpoint",
|
|
594
|
+
)
|
|
595
|
+
account_client = _FakeConnectAccountClient(connection=connection)
|
|
596
|
+
protocol_calls: list[dict[str, str]] = []
|
|
597
|
+
|
|
598
|
+
class _FakeProtocol:
|
|
599
|
+
def __init__(self, *, url: str, token: str) -> None:
|
|
600
|
+
protocol_calls.append({"url": url, "token": token})
|
|
601
|
+
|
|
602
|
+
def create_factory(self):
|
|
603
|
+
return lambda: self
|
|
604
|
+
|
|
605
|
+
class _FakeRoomClient:
|
|
606
|
+
def __init__(self, *, protocol_factory) -> None:
|
|
607
|
+
self.protocol = protocol_factory()
|
|
608
|
+
self.enter_calls = 0
|
|
609
|
+
|
|
610
|
+
async def __aenter__(self) -> "_FakeRoomClient":
|
|
611
|
+
self.enter_calls += 1
|
|
612
|
+
return self
|
|
613
|
+
|
|
614
|
+
async def _fake_get_client() -> _FakeConnectAccountClient:
|
|
615
|
+
return account_client
|
|
616
|
+
|
|
617
|
+
async def _fake_resolve_project_id(*, project_id):
|
|
618
|
+
return project_id
|
|
619
|
+
|
|
620
|
+
monkeypatch.setattr(containers, "get_client", _fake_get_client)
|
|
621
|
+
monkeypatch.setattr(containers, "resolve_project_id", _fake_resolve_project_id)
|
|
622
|
+
monkeypatch.setattr(containers, "resolve_room", lambda room: room)
|
|
623
|
+
monkeypatch.setattr(containers, "WebSocketClientProtocol", _FakeProtocol)
|
|
624
|
+
monkeypatch.setattr(containers, "RoomClient", _FakeRoomClient)
|
|
625
|
+
|
|
626
|
+
returned_account_client, client = await containers._with_client(
|
|
627
|
+
project_id="project-1",
|
|
628
|
+
room="room-1",
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
assert returned_account_client is account_client
|
|
632
|
+
assert protocol_calls == [
|
|
633
|
+
{
|
|
634
|
+
"url": "wss://override.meshagent.test/rooms/room-1",
|
|
583
635
|
"token": "jwt-token",
|
|
584
636
|
}
|
|
585
637
|
]
|
|
@@ -15,14 +15,12 @@ from pathlib import Path, PurePosixPath
|
|
|
15
15
|
from typing import Annotated, Literal, Optional, Protocol, TYPE_CHECKING
|
|
16
16
|
from urllib.parse import urlparse
|
|
17
17
|
|
|
18
|
-
from aiohttp import ClientTimeout
|
|
19
18
|
import typer
|
|
20
19
|
from pydantic import ValidationError
|
|
21
20
|
from pydantic_yaml import parse_yaml_raw_as
|
|
22
21
|
from rich import print
|
|
23
22
|
import yaml
|
|
24
23
|
|
|
25
|
-
from meshagent.api.http import new_client_session
|
|
26
24
|
from meshagent.cli import async_typer
|
|
27
25
|
from meshagent.cli.containers import (
|
|
28
26
|
_drain_stream_plain,
|
|
@@ -70,6 +68,7 @@ from meshagent.api.room_server_client import (
|
|
|
70
68
|
LogStream,
|
|
71
69
|
PublishedBuildImage,
|
|
72
70
|
RoomException,
|
|
71
|
+
ServicePortRuntimeState,
|
|
73
72
|
ServiceRuntimeState,
|
|
74
73
|
)
|
|
75
74
|
from meshagent.api.room_ports import RESERVED_ROOM_SERVICE_PORTS
|
|
@@ -129,7 +128,6 @@ _CLIENT_CLOSE_TIMEOUT_SECONDS = 2.0
|
|
|
129
128
|
_DEFAULT_CONTEXT_MOUNT_PATH = "/context"
|
|
130
129
|
_DEFAULT_REPOSITORY_TOKEN_TTL_SECONDS = 3600
|
|
131
130
|
_DEPLOY_CACHE_CLEANUP_TIMEOUT_SECONDS = 30.0
|
|
132
|
-
_DEPLOY_LIVENESS_REQUEST_TIMEOUT_SECONDS = 2.0
|
|
133
131
|
_DEPLOY_SERVICE_APPLY_TIMEOUT_SECONDS = 60.0
|
|
134
132
|
_DEPLOY_SERVICE_RESTART_TIMEOUT_SECONDS = 60.0
|
|
135
133
|
_DEPLOY_WAIT_TIMEOUT_SECONDS = 300.0
|
|
@@ -3216,37 +3214,6 @@ def _resolve_domain_liveness_path(
|
|
|
3216
3214
|
return service_port.liveness
|
|
3217
3215
|
|
|
3218
3216
|
|
|
3219
|
-
def _build_domain_liveness_url(*, domain: str, liveness_path: str) -> str:
|
|
3220
|
-
parsed = urlparse(domain if "://" in domain else f"https://{domain}")
|
|
3221
|
-
scheme = parsed.scheme or "https"
|
|
3222
|
-
netloc = parsed.netloc
|
|
3223
|
-
base_path = parsed.path
|
|
3224
|
-
if netloc == "":
|
|
3225
|
-
netloc = parsed.path
|
|
3226
|
-
base_path = ""
|
|
3227
|
-
if base_path != "/" and base_path.endswith("/"):
|
|
3228
|
-
base_path = base_path.rstrip("/")
|
|
3229
|
-
return f"{scheme}://{netloc}{base_path}{liveness_path}"
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
async def _probe_liveness_url(*, url: str) -> bool:
|
|
3233
|
-
try:
|
|
3234
|
-
async with new_client_session(
|
|
3235
|
-
timeout=ClientTimeout(total=_DEPLOY_LIVENESS_REQUEST_TIMEOUT_SECONDS)
|
|
3236
|
-
) as session:
|
|
3237
|
-
async with session.get(
|
|
3238
|
-
url,
|
|
3239
|
-
headers={
|
|
3240
|
-
"Accept": "*/*",
|
|
3241
|
-
"User-Agent": f"meshagent-cli/{__version__}",
|
|
3242
|
-
},
|
|
3243
|
-
) as resp:
|
|
3244
|
-
status_code = resp.status
|
|
3245
|
-
except Exception:
|
|
3246
|
-
return False
|
|
3247
|
-
return 200 <= status_code < 400 or status_code in {401, 403}
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
3217
|
async def _find_room_service_by_name(
|
|
3251
3218
|
*,
|
|
3252
3219
|
account_client,
|
|
@@ -3580,10 +3547,23 @@ async def _get_service_runtime_state(
|
|
|
3580
3547
|
client: RoomClient,
|
|
3581
3548
|
service_id: str,
|
|
3582
3549
|
) -> ServiceRuntimeState | None:
|
|
3583
|
-
service_list = await client.services.
|
|
3550
|
+
service_list = await client.services.list()
|
|
3584
3551
|
return service_list.service_states.get(service_id)
|
|
3585
3552
|
|
|
3586
3553
|
|
|
3554
|
+
def _find_service_liveness_port(
|
|
3555
|
+
*,
|
|
3556
|
+
state: ServiceRuntimeState,
|
|
3557
|
+
liveness_path: str | None,
|
|
3558
|
+
) -> ServicePortRuntimeState | None:
|
|
3559
|
+
if liveness_path is None:
|
|
3560
|
+
return None
|
|
3561
|
+
for port in state.status.ports:
|
|
3562
|
+
if port.liveness == liveness_path:
|
|
3563
|
+
return port
|
|
3564
|
+
return None
|
|
3565
|
+
|
|
3566
|
+
|
|
3587
3567
|
async def _drain_deploy_log_stream_tui(
|
|
3588
3568
|
stream: LogStream,
|
|
3589
3569
|
*,
|
|
@@ -3699,33 +3679,28 @@ async def _wait_for_deployed_service_live(
|
|
|
3699
3679
|
log_handler: Callable[[str], Awaitable[None]] | None = None,
|
|
3700
3680
|
) -> None:
|
|
3701
3681
|
active_logs: _ActiveDeployLogStream | None = None
|
|
3702
|
-
liveness_url = (
|
|
3703
|
-
_build_domain_liveness_url(domain=domain, liveness_path=liveness_path)
|
|
3704
|
-
if domain is not None and liveness_path is not None
|
|
3705
|
-
else None
|
|
3706
|
-
)
|
|
3707
3682
|
|
|
3708
3683
|
await _emit_deploy_status(
|
|
3709
3684
|
status_handler,
|
|
3710
3685
|
rich_message=f"[cyan]Waiting for service to go live:[/] {service_name} ({service_id})",
|
|
3711
3686
|
plain_message=f"Waiting for service to go live: {service_name} ({service_id})",
|
|
3712
3687
|
)
|
|
3713
|
-
if
|
|
3688
|
+
if liveness_path is not None:
|
|
3714
3689
|
await _emit_deploy_status(
|
|
3715
3690
|
status_handler,
|
|
3716
|
-
rich_message=f"[cyan]Waiting for liveness
|
|
3717
|
-
plain_message=f"Waiting for liveness
|
|
3691
|
+
rich_message=f"[cyan]Waiting for service liveness:[/] {liveness_path}",
|
|
3692
|
+
plain_message=f"Waiting for service liveness: {liveness_path}",
|
|
3718
3693
|
)
|
|
3719
3694
|
elif domain is not None:
|
|
3720
3695
|
await _emit_deploy_status(
|
|
3721
3696
|
status_handler,
|
|
3722
3697
|
rich_message=(
|
|
3723
3698
|
f"[yellow]Route created for {domain}, but the service has no HTTP "
|
|
3724
|
-
"liveness path to
|
|
3699
|
+
"liveness path to check.[/]"
|
|
3725
3700
|
),
|
|
3726
3701
|
plain_message=(
|
|
3727
3702
|
f"Route created for {domain}, but the service has no HTTP "
|
|
3728
|
-
"liveness path to
|
|
3703
|
+
"liveness path to check."
|
|
3729
3704
|
),
|
|
3730
3705
|
)
|
|
3731
3706
|
|
|
@@ -3803,7 +3778,7 @@ async def _wait_for_deployed_service_live(
|
|
|
3803
3778
|
await asyncio.sleep(_DEPLOY_WAIT_POLL_INTERVAL_SECONDS)
|
|
3804
3779
|
continue
|
|
3805
3780
|
|
|
3806
|
-
if
|
|
3781
|
+
if liveness_path is None:
|
|
3807
3782
|
await _emit_deploy_status(
|
|
3808
3783
|
status_handler,
|
|
3809
3784
|
rich_message=f"[green]Service is live:[/] {service_name} ({service_id})",
|
|
@@ -3811,11 +3786,15 @@ async def _wait_for_deployed_service_live(
|
|
|
3811
3786
|
)
|
|
3812
3787
|
return
|
|
3813
3788
|
|
|
3814
|
-
|
|
3789
|
+
liveness_port = _find_service_liveness_port(
|
|
3790
|
+
state=state,
|
|
3791
|
+
liveness_path=liveness_path,
|
|
3792
|
+
)
|
|
3793
|
+
if liveness_port is not None and liveness_port.liveness_status == "ready":
|
|
3815
3794
|
await _emit_deploy_status(
|
|
3816
3795
|
status_handler,
|
|
3817
|
-
rich_message=f"[green]
|
|
3818
|
-
plain_message=f"
|
|
3796
|
+
rich_message=f"[green]Service liveness is ready:[/] {liveness_path}",
|
|
3797
|
+
plain_message=f"Service liveness is ready: {liveness_path}",
|
|
3819
3798
|
)
|
|
3820
3799
|
return
|
|
3821
3800
|
|
|
@@ -38,6 +38,8 @@ from meshagent.cli import cli, image
|
|
|
38
38
|
from meshagent.api.room_server_client import (
|
|
39
39
|
PublishedBuildImage,
|
|
40
40
|
RoomException,
|
|
41
|
+
ServicePortRuntimeState,
|
|
42
|
+
ServiceRuntimeStatus,
|
|
41
43
|
ServiceRuntimeState,
|
|
42
44
|
)
|
|
43
45
|
from meshagent.api.specs.service import (
|
|
@@ -2274,7 +2276,7 @@ def test_parse_meshagent_token_scope_supports_presets_and_json() -> None:
|
|
|
2274
2276
|
user_default = image._parse_meshagent_token_scope(value="userDefault")
|
|
2275
2277
|
custom = image._parse_meshagent_token_scope(value='{"queues":{"send":["jobs"]}}')
|
|
2276
2278
|
|
|
2277
|
-
assert user_default.secrets is
|
|
2279
|
+
assert user_default.secrets is None
|
|
2278
2280
|
assert user_default.admin is None
|
|
2279
2281
|
assert custom.queues is not None
|
|
2280
2282
|
assert custom.queues.send == ["jobs"]
|
|
@@ -2335,71 +2337,7 @@ async def test_validate_deploy_environment_secrets_requires_run_as() -> None:
|
|
|
2335
2337
|
|
|
2336
2338
|
|
|
2337
2339
|
@pytest.mark.asyncio
|
|
2338
|
-
|
|
2339
|
-
async def test_probe_liveness_url_accepts_reachable_statuses(
|
|
2340
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
2341
|
-
status_code: int,
|
|
2342
|
-
) -> None:
|
|
2343
|
-
class _FakeResponse:
|
|
2344
|
-
status = status_code
|
|
2345
|
-
|
|
2346
|
-
async def __aenter__(self):
|
|
2347
|
-
return self
|
|
2348
|
-
|
|
2349
|
-
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
2350
|
-
del exc_type, exc, tb
|
|
2351
|
-
|
|
2352
|
-
class _FakeSession:
|
|
2353
|
-
async def __aenter__(self):
|
|
2354
|
-
return self
|
|
2355
|
-
|
|
2356
|
-
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
2357
|
-
del exc_type, exc, tb
|
|
2358
|
-
|
|
2359
|
-
def get(self, *args, **kwargs):
|
|
2360
|
-
del args, kwargs
|
|
2361
|
-
return _FakeResponse()
|
|
2362
|
-
|
|
2363
|
-
monkeypatch.setattr(
|
|
2364
|
-
image, "new_client_session", lambda *args, **kwargs: _FakeSession()
|
|
2365
|
-
)
|
|
2366
|
-
|
|
2367
|
-
assert await image._probe_liveness_url(url="https://sheets.meshagent.dev/") is True
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
@pytest.mark.asyncio
|
|
2371
|
-
async def test_probe_liveness_url_rejects_server_errors(
|
|
2372
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
2373
|
-
) -> None:
|
|
2374
|
-
class _FakeResponse:
|
|
2375
|
-
status = 502
|
|
2376
|
-
|
|
2377
|
-
async def __aenter__(self):
|
|
2378
|
-
return self
|
|
2379
|
-
|
|
2380
|
-
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
2381
|
-
del exc_type, exc, tb
|
|
2382
|
-
|
|
2383
|
-
class _FakeSession:
|
|
2384
|
-
async def __aenter__(self):
|
|
2385
|
-
return self
|
|
2386
|
-
|
|
2387
|
-
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
2388
|
-
del exc_type, exc, tb
|
|
2389
|
-
|
|
2390
|
-
def get(self, *args, **kwargs):
|
|
2391
|
-
del args, kwargs
|
|
2392
|
-
return _FakeResponse()
|
|
2393
|
-
|
|
2394
|
-
monkeypatch.setattr(
|
|
2395
|
-
image, "new_client_session", lambda *args, **kwargs: _FakeSession()
|
|
2396
|
-
)
|
|
2397
|
-
|
|
2398
|
-
assert await image._probe_liveness_url(url="https://sheets.meshagent.dev/") is False
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
@pytest.mark.asyncio
|
|
2402
|
-
async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
|
|
2340
|
+
async def test_wait_for_deployed_service_live_streams_logs_and_checks_service_liveness(
|
|
2403
2341
|
monkeypatch: pytest.MonkeyPatch,
|
|
2404
2342
|
_stub_deploy_wait: dict[str, object],
|
|
2405
2343
|
) -> None:
|
|
@@ -2407,7 +2345,6 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
|
|
|
2407
2345
|
"prints": [],
|
|
2408
2346
|
"started_logs": [],
|
|
2409
2347
|
"stopped_logs": [],
|
|
2410
|
-
"probe_urls": [],
|
|
2411
2348
|
}
|
|
2412
2349
|
states = [
|
|
2413
2350
|
ServiceRuntimeState(service_id="service-1", state="starting"),
|
|
@@ -2420,11 +2357,29 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
|
|
|
2420
2357
|
service_id="service-1",
|
|
2421
2358
|
state="running",
|
|
2422
2359
|
container_id="container-1",
|
|
2360
|
+
status=ServiceRuntimeStatus(
|
|
2361
|
+
ports=[
|
|
2362
|
+
ServicePortRuntimeState(
|
|
2363
|
+
num=8000,
|
|
2364
|
+
liveness="/ready",
|
|
2365
|
+
liveness_status="not_ready",
|
|
2366
|
+
)
|
|
2367
|
+
]
|
|
2368
|
+
),
|
|
2423
2369
|
),
|
|
2424
2370
|
ServiceRuntimeState(
|
|
2425
2371
|
service_id="service-1",
|
|
2426
2372
|
state="running",
|
|
2427
2373
|
container_id="container-1",
|
|
2374
|
+
status=ServiceRuntimeStatus(
|
|
2375
|
+
ports=[
|
|
2376
|
+
ServicePortRuntimeState(
|
|
2377
|
+
num=8000,
|
|
2378
|
+
liveness="/ready",
|
|
2379
|
+
liveness_status="ready",
|
|
2380
|
+
)
|
|
2381
|
+
]
|
|
2382
|
+
),
|
|
2428
2383
|
),
|
|
2429
2384
|
]
|
|
2430
2385
|
fake_active_logs = SimpleNamespace(container_id="container-1")
|
|
@@ -2434,7 +2389,7 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
|
|
|
2434
2389
|
self._runtime_states = runtime_states
|
|
2435
2390
|
self._index = 0
|
|
2436
2391
|
|
|
2437
|
-
async def
|
|
2392
|
+
async def list(self):
|
|
2438
2393
|
state = self._runtime_states[
|
|
2439
2394
|
min(self._index, len(self._runtime_states) - 1)
|
|
2440
2395
|
]
|
|
@@ -2454,19 +2409,12 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
|
|
|
2454
2409
|
async def _fake_stop_deploy_log_stream(*, active_logs) -> None:
|
|
2455
2410
|
captured["stopped_logs"].append(active_logs)
|
|
2456
2411
|
|
|
2457
|
-
probe_results = iter([False, True])
|
|
2458
|
-
|
|
2459
|
-
async def _fake_probe_liveness_url(*, url: str) -> bool:
|
|
2460
|
-
captured["probe_urls"].append(url)
|
|
2461
|
-
return next(probe_results)
|
|
2462
|
-
|
|
2463
2412
|
monkeypatch.setattr(image.asyncio, "to_thread", _fake_to_thread)
|
|
2464
2413
|
monkeypatch.setattr(image, "_DEPLOY_WAIT_POLL_INTERVAL_SECONDS", 0)
|
|
2465
2414
|
monkeypatch.setattr(
|
|
2466
2415
|
image, "_start_deploy_log_stream", _fake_start_deploy_log_stream
|
|
2467
2416
|
)
|
|
2468
2417
|
monkeypatch.setattr(image, "_stop_deploy_log_stream", _fake_stop_deploy_log_stream)
|
|
2469
|
-
monkeypatch.setattr(image, "_probe_liveness_url", _fake_probe_liveness_url)
|
|
2470
2418
|
monkeypatch.setattr(
|
|
2471
2419
|
image,
|
|
2472
2420
|
"print",
|
|
@@ -2487,11 +2435,7 @@ async def test_wait_for_deployed_service_live_streams_logs_and_checks_liveness(
|
|
|
2487
2435
|
|
|
2488
2436
|
assert captured["started_logs"] == ["container-1"]
|
|
2489
2437
|
assert captured["stopped_logs"] == [fake_active_logs]
|
|
2490
|
-
assert captured["
|
|
2491
|
-
"https://app.meshagent.app/ready",
|
|
2492
|
-
"https://app.meshagent.app/ready",
|
|
2493
|
-
]
|
|
2494
|
-
assert any("Liveness URL responded" in message for message in captured["prints"])
|
|
2438
|
+
assert any("Service liveness is ready" in message for message in captured["prints"])
|
|
2495
2439
|
|
|
2496
2440
|
|
|
2497
2441
|
@pytest.mark.asyncio
|
|
@@ -2525,7 +2469,7 @@ async def test_wait_for_deployed_service_live_exits_when_container_restarts(
|
|
|
2525
2469
|
self._runtime_states = runtime_states
|
|
2526
2470
|
self._index = 0
|
|
2527
2471
|
|
|
2528
|
-
async def
|
|
2472
|
+
async def list(self):
|
|
2529
2473
|
state = self._runtime_states[
|
|
2530
2474
|
min(self._index, len(self._runtime_states) - 1)
|
|
2531
2475
|
]
|
|
@@ -2832,7 +2776,7 @@ async def test_deploy_image_creates_room_service_with_mounts_env_secret_and_toke
|
|
|
2832
2776
|
async def restart(self, *, service_id: str) -> None:
|
|
2833
2777
|
captured["restarted_service_id"] = service_id
|
|
2834
2778
|
|
|
2835
|
-
async def
|
|
2779
|
+
async def list(self):
|
|
2836
2780
|
return SimpleNamespace(
|
|
2837
2781
|
service_states={
|
|
2838
2782
|
"service-1": ServiceRuntimeState(
|
|
@@ -4944,7 +4888,7 @@ async def test_deploy_image_updates_existing_service_route_and_replaces_environm
|
|
|
4944
4888
|
async def restart(self, *, service_id: str) -> None:
|
|
4945
4889
|
captured["restarted_service_id"] = service_id
|
|
4946
4890
|
|
|
4947
|
-
async def
|
|
4891
|
+
async def list(self):
|
|
4948
4892
|
return SimpleNamespace(
|
|
4949
4893
|
service_states={
|
|
4950
4894
|
"service-1": ServiceRuntimeState(
|
|
@@ -5048,7 +4992,7 @@ async def test_deploy_image_updates_existing_service_route_and_replaces_environm
|
|
|
5048
4992
|
assert env_by_name["MESHAGENT_TOKEN"].token.role == "agent"
|
|
5049
4993
|
assert env_by_name["MESHAGENT_TOKEN"].token.api is not None
|
|
5050
4994
|
assert env_by_name["MESHAGENT_TOKEN"].token.api.admin is not None
|
|
5051
|
-
assert env_by_name["MESHAGENT_TOKEN"].token.api.secrets is
|
|
4995
|
+
assert env_by_name["MESHAGENT_TOKEN"].token.api.secrets is None
|
|
5052
4996
|
assert env_by_name["MESHAGENT_TOKEN"].token.api.tunnels is not None
|
|
5053
4997
|
for env_name in ("OPENAI_API_KEY", "ANTHROPIC_API_KEY"):
|
|
5054
4998
|
assert env_by_name[env_name].token is not None
|
|
@@ -5098,7 +5042,7 @@ async def test_deploy_image_sets_cookie_validation_on_private_published_ports(
|
|
|
5098
5042
|
async def restart(self, *, service_id: str) -> None:
|
|
5099
5043
|
captured["restarted_service_id"] = service_id
|
|
5100
5044
|
|
|
5101
|
-
async def
|
|
5045
|
+
async def list(self):
|
|
5102
5046
|
return SimpleNamespace(
|
|
5103
5047
|
service_states={
|
|
5104
5048
|
"service-1": ServiceRuntimeState(
|
|
@@ -5223,7 +5167,7 @@ async def test_deploy_image_preserves_existing_liveness_when_default_is_used(
|
|
|
5223
5167
|
async def restart(self, *, service_id: str) -> None:
|
|
5224
5168
|
captured["restarted_service_id"] = service_id
|
|
5225
5169
|
|
|
5226
|
-
async def
|
|
5170
|
+
async def list(self):
|
|
5227
5171
|
return SimpleNamespace(
|
|
5228
5172
|
service_states={
|
|
5229
5173
|
"service-1": ServiceRuntimeState(
|
|
@@ -5325,7 +5269,7 @@ async def test_deploy_image_liveness_flag_overrides_http_ports_only(
|
|
|
5325
5269
|
async def restart(self, *, service_id: str) -> None:
|
|
5326
5270
|
captured["restarted_service_id"] = service_id
|
|
5327
5271
|
|
|
5328
|
-
async def
|
|
5272
|
+
async def list(self):
|
|
5329
5273
|
return SimpleNamespace(
|
|
5330
5274
|
service_states={
|
|
5331
5275
|
"service-1": ServiceRuntimeState(
|
|
@@ -5417,7 +5361,7 @@ async def test_deploy_image_domain_requires_exactly_one_published_port(
|
|
|
5417
5361
|
)
|
|
5418
5362
|
|
|
5419
5363
|
class _FakeServices:
|
|
5420
|
-
async def
|
|
5364
|
+
async def list(self):
|
|
5421
5365
|
return SimpleNamespace(
|
|
5422
5366
|
service_states={
|
|
5423
5367
|
"service-1": ServiceRuntimeState(
|
|
@@ -9,7 +9,12 @@ from meshagent.cli.helper import (
|
|
|
9
9
|
)
|
|
10
10
|
from meshagent.api import RoomClient, WebSocketClientProtocol
|
|
11
11
|
from meshagent.api.helpers import websocket_room_url
|
|
12
|
-
from meshagent.api.room_server_client import
|
|
12
|
+
from meshagent.api.room_server_client import (
|
|
13
|
+
ListServicesResult,
|
|
14
|
+
ServicePortRuntimeState,
|
|
15
|
+
ServiceRuntimeState,
|
|
16
|
+
ServicesClient,
|
|
17
|
+
)
|
|
13
18
|
from meshagent.api.specs.service import ServiceSpec
|
|
14
19
|
from datetime import datetime
|
|
15
20
|
from typing import Annotated, Any, Optional
|
|
@@ -25,6 +30,22 @@ def _format_unix_timestamp(value: float | None) -> str | None:
|
|
|
25
30
|
return datetime.fromtimestamp(value).isoformat()
|
|
26
31
|
|
|
27
32
|
|
|
33
|
+
def _format_port_status(port: ServicePortRuntimeState) -> str:
|
|
34
|
+
if port.liveness_status == "not_configured":
|
|
35
|
+
status = "no liveness"
|
|
36
|
+
elif port.liveness_status == "ready":
|
|
37
|
+
status = "ready"
|
|
38
|
+
else:
|
|
39
|
+
status = "not ready"
|
|
40
|
+
return f"{port.num}: {status}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _format_service_ports(state: ServiceRuntimeState | None) -> str | None:
|
|
44
|
+
if state is None:
|
|
45
|
+
return None
|
|
46
|
+
return ", ".join(_format_port_status(port) for port in state.status.ports)
|
|
47
|
+
|
|
48
|
+
|
|
28
49
|
async def _connect_services_client(
|
|
29
50
|
*, project_id: str | None, room: str | None
|
|
30
51
|
) -> tuple[Any, RoomClient, ServicesClient]:
|
|
@@ -74,7 +95,7 @@ async def room_services_list_command(
|
|
|
74
95
|
)
|
|
75
96
|
try:
|
|
76
97
|
print("[bold green]Fetching services...[/bold green]")
|
|
77
|
-
result: ListServicesResult = await services_client.
|
|
98
|
+
result: ListServicesResult = await services_client.list()
|
|
78
99
|
services: list[ServiceSpec] = result.services
|
|
79
100
|
|
|
80
101
|
rows = []
|
|
@@ -86,6 +107,7 @@ async def room_services_list_command(
|
|
|
86
107
|
"name": svc.metadata.name,
|
|
87
108
|
"image": svc.container.image if svc.container is not None else None,
|
|
88
109
|
"state": state.state if state is not None else None,
|
|
110
|
+
"ports": _format_service_ports(state),
|
|
89
111
|
"container_id": state.container_id if state is not None else None,
|
|
90
112
|
"restart_scheduled_at": _format_unix_timestamp(
|
|
91
113
|
state.restart_scheduled_at if state is not None else None
|
|
@@ -121,6 +143,7 @@ async def room_services_list_command(
|
|
|
121
143
|
"name",
|
|
122
144
|
"image",
|
|
123
145
|
"state",
|
|
146
|
+
"ports",
|
|
124
147
|
"container_id",
|
|
125
148
|
"started_at",
|
|
126
149
|
"restart_scheduled_at",
|
|
@@ -157,7 +180,7 @@ async def room_services_describe_command(
|
|
|
157
180
|
project_id=project_id, room=room
|
|
158
181
|
)
|
|
159
182
|
try:
|
|
160
|
-
result: ListServicesResult = await services_client.
|
|
183
|
+
result: ListServicesResult = await services_client.list()
|
|
161
184
|
service = _find_service(
|
|
162
185
|
services=result.services, service_id=service_id, name=name
|
|
163
186
|
)
|
|
@@ -282,7 +305,7 @@ async def room_services_restart_command(
|
|
|
282
305
|
).create_factory()
|
|
283
306
|
) as client:
|
|
284
307
|
services_client = ServicesClient(room=client)
|
|
285
|
-
result: ListServicesResult = await services_client.
|
|
308
|
+
result: ListServicesResult = await services_client.list()
|
|
286
309
|
services: list[ServiceSpec] = result.services
|
|
287
310
|
|
|
288
311
|
service: ServiceSpec | None = None
|