meshagent-cli 0.35.7__tar.gz → 0.36.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.35.7 → meshagent_cli-0.36.0}/PKG-INFO +13 -13
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/chatbot.py +11 -22
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/common_options.py +8 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/containers.py +1 -2
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/containers_test.py +73 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/helper.py +21 -1
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/helper_test.py +35 -3
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/image.py +973 -165
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/image_test.py +1044 -56
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/oci_archive.py +25 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/oci_archive_test.py +36 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/process_test.py +12 -1
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/services.py +2 -0
- meshagent_cli-0.36.0/meshagent/cli/version.py +1 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/PKG-INFO +13 -13
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/requires.txt +12 -12
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/pyproject.toml +12 -12
- meshagent_cli-0.35.7/meshagent/cli/version.py +0 -1
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/README.md +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/__init__.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/agent.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/api_keys.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/api_keys_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/async_typer.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/async_typer_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/auth.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/auth_async.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/call.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli_mcp.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli_secrets.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/codex.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/database.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/developer.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/developer_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/helpers.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/host.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/mailbot.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/mailboxes.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/meeting_transcriber.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/memory.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/memory_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/messaging.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/multi.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/oauth2.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/participant_token.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/port.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/port_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/process.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/projects.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/queue.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/queue_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/room.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/room_services.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/rooms.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/root_commands.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/routes.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/scheduled_tasks.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/scheduled_tasks_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/services_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sessions.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sessions_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/storage.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/storage_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sync.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sync_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/task_runner.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/__init__.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/setup.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/setup_splash_frames.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/setup_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/voicebot.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/webhook.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/webserver.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/webserver_test.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/worker.py +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/SOURCES.txt +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/dependency_links.txt +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/entry_points.txt +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/top_level.txt +0 -0
- {meshagent_cli-0.35.7 → meshagent_cli-0.36.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.36.0
|
|
4
4
|
Summary: CLI for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -20,21 +20,21 @@ Requires-Dist: rich~=14.3.0
|
|
|
20
20
|
Requires-Dist: textual<2.0,>=0.50
|
|
21
21
|
Requires-Dist: prompt-toolkit~=3.0.52
|
|
22
22
|
Provides-Extra: all
|
|
23
|
-
Requires-Dist: meshagent-agents[all]~=0.
|
|
24
|
-
Requires-Dist: meshagent-api[all]~=0.
|
|
25
|
-
Requires-Dist: meshagent-computers~=0.
|
|
26
|
-
Requires-Dist: meshagent-openai~=0.
|
|
27
|
-
Requires-Dist: meshagent-anthropic~=0.
|
|
28
|
-
Requires-Dist: meshagent-codex~=0.
|
|
29
|
-
Requires-Dist: meshagent-mcp~=0.
|
|
30
|
-
Requires-Dist: meshagent-tools~=0.
|
|
23
|
+
Requires-Dist: meshagent-agents[all]~=0.36.0; extra == "all"
|
|
24
|
+
Requires-Dist: meshagent-api[all]~=0.36.0; extra == "all"
|
|
25
|
+
Requires-Dist: meshagent-computers~=0.36.0; extra == "all"
|
|
26
|
+
Requires-Dist: meshagent-openai~=0.36.0; extra == "all"
|
|
27
|
+
Requires-Dist: meshagent-anthropic~=0.36.0; extra == "all"
|
|
28
|
+
Requires-Dist: meshagent-codex~=0.36.0; extra == "all"
|
|
29
|
+
Requires-Dist: meshagent-mcp~=0.36.0; extra == "all"
|
|
30
|
+
Requires-Dist: meshagent-tools~=0.36.0; extra == "all"
|
|
31
31
|
Requires-Dist: supabase-auth~=2.28.0; extra == "all"
|
|
32
32
|
Requires-Dist: prompt-toolkit~=3.0.52; extra == "all"
|
|
33
33
|
Provides-Extra: mcp-service
|
|
34
|
-
Requires-Dist: meshagent-agents[all]~=0.
|
|
35
|
-
Requires-Dist: meshagent-api~=0.
|
|
36
|
-
Requires-Dist: meshagent-mcp~=0.
|
|
37
|
-
Requires-Dist: meshagent-tools~=0.
|
|
34
|
+
Requires-Dist: meshagent-agents[all]~=0.36.0; extra == "mcp-service"
|
|
35
|
+
Requires-Dist: meshagent-api~=0.36.0; extra == "mcp-service"
|
|
36
|
+
Requires-Dist: meshagent-mcp~=0.36.0; extra == "mcp-service"
|
|
37
|
+
Requires-Dist: meshagent-tools~=0.36.0; extra == "mcp-service"
|
|
38
38
|
Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
|
|
39
39
|
|
|
40
40
|
# [Meshagent](https://www.meshagent.com)
|
|
@@ -33,6 +33,7 @@ from meshagent.agents.widget_schema import widget_schema
|
|
|
33
33
|
from meshagent.cli.common_options import (
|
|
34
34
|
AllowGotoUrlOption,
|
|
35
35
|
ProjectIdOption,
|
|
36
|
+
ShellConfigMountOption,
|
|
36
37
|
RoomOption,
|
|
37
38
|
ShellEmptyDirMountLegacyOption,
|
|
38
39
|
ShellEmptyDirMountOption,
|
|
@@ -68,7 +69,6 @@ from meshagent.cli.helper import (
|
|
|
68
69
|
resolve_project_id,
|
|
69
70
|
resolve_room,
|
|
70
71
|
supports_openai_shell_tool,
|
|
71
|
-
upload_room_bytes_stream,
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
from meshagent.openai import OpenAIResponsesAdapter
|
|
@@ -823,17 +823,6 @@ def build_chatbot(
|
|
|
823
823
|
rules.extend(cr)
|
|
824
824
|
|
|
825
825
|
except RoomException:
|
|
826
|
-
try:
|
|
827
|
-
logger.info("attempting to initialize rules file")
|
|
828
|
-
await upload_room_bytes_stream(
|
|
829
|
-
room=self.room,
|
|
830
|
-
path=path,
|
|
831
|
-
data="# Add rules to this file to customize your agent's behavior, lines starting with # will be ignored.\n\n".encode(),
|
|
832
|
-
overwrite=False,
|
|
833
|
-
)
|
|
834
|
-
|
|
835
|
-
except RoomException:
|
|
836
|
-
pass
|
|
837
826
|
logger.info(
|
|
838
827
|
f"unable to load rules from {path}, continuing with default rules"
|
|
839
828
|
)
|
|
@@ -1480,16 +1469,6 @@ def build_process_agent(
|
|
|
1480
1469
|
if selected_rules is not None:
|
|
1481
1470
|
rules.extend(selected_rules)
|
|
1482
1471
|
except RoomException:
|
|
1483
|
-
try:
|
|
1484
|
-
logger.info("attempting to initialize rules file")
|
|
1485
|
-
await upload_room_bytes_stream(
|
|
1486
|
-
room=self.room,
|
|
1487
|
-
path=path,
|
|
1488
|
-
data="# Add rules to this file to customize your agent's behavior, lines starting with # will be ignored.\n\n".encode(),
|
|
1489
|
-
overwrite=False,
|
|
1490
|
-
)
|
|
1491
|
-
except RoomException:
|
|
1492
|
-
pass
|
|
1493
1472
|
logger.info(
|
|
1494
1473
|
f"unable to load rules from {path}, continuing with default rules"
|
|
1495
1474
|
)
|
|
@@ -1947,6 +1926,7 @@ async def join(
|
|
|
1947
1926
|
shell_tool_project_path: ShellProjectMountLegacyOption = [],
|
|
1948
1927
|
shell_empty_dir_mount: ShellEmptyDirMountOption = [],
|
|
1949
1928
|
shell_tool_empty_dir: ShellEmptyDirMountLegacyOption = [],
|
|
1929
|
+
shell_tool_config_mount: ShellConfigMountOption = [],
|
|
1950
1930
|
shell_image_mount: Annotated[
|
|
1951
1931
|
List[str],
|
|
1952
1932
|
typer.Option(
|
|
@@ -2150,6 +2130,7 @@ async def join(
|
|
|
2150
2130
|
shell_empty_dir_mount,
|
|
2151
2131
|
shell_tool_empty_dir,
|
|
2152
2132
|
),
|
|
2133
|
+
config_paths=shell_tool_config_mount,
|
|
2153
2134
|
image_paths=shell_image_mount,
|
|
2154
2135
|
)
|
|
2155
2136
|
|
|
@@ -2342,6 +2323,7 @@ async def service(
|
|
|
2342
2323
|
shell_tool_project_path: ShellProjectMountLegacyOption = [],
|
|
2343
2324
|
shell_empty_dir_mount: ShellEmptyDirMountOption = [],
|
|
2344
2325
|
shell_tool_empty_dir: ShellEmptyDirMountLegacyOption = [],
|
|
2326
|
+
shell_tool_config_mount: ShellConfigMountOption = [],
|
|
2345
2327
|
shell_image_mount: Annotated[
|
|
2346
2328
|
List[str],
|
|
2347
2329
|
typer.Option(
|
|
@@ -2509,6 +2491,7 @@ async def service(
|
|
|
2509
2491
|
shell_empty_dir_mount,
|
|
2510
2492
|
shell_tool_empty_dir,
|
|
2511
2493
|
),
|
|
2494
|
+
config_paths=shell_tool_config_mount,
|
|
2512
2495
|
image_paths=shell_image_mount,
|
|
2513
2496
|
)
|
|
2514
2497
|
|
|
@@ -2703,6 +2686,7 @@ async def spec(
|
|
|
2703
2686
|
shell_tool_project_path: ShellProjectMountLegacyOption = [],
|
|
2704
2687
|
shell_empty_dir_mount: ShellEmptyDirMountOption = [],
|
|
2705
2688
|
shell_tool_empty_dir: ShellEmptyDirMountLegacyOption = [],
|
|
2689
|
+
shell_tool_config_mount: ShellConfigMountOption = [],
|
|
2706
2690
|
require_image_generation: Annotated[
|
|
2707
2691
|
Optional[str], typer.Option(..., help="Name of an image gen model")
|
|
2708
2692
|
] = None,
|
|
@@ -2855,6 +2839,7 @@ async def spec(
|
|
|
2855
2839
|
shell_empty_dir_mount,
|
|
2856
2840
|
shell_tool_empty_dir,
|
|
2857
2841
|
),
|
|
2842
|
+
config_paths=shell_tool_config_mount,
|
|
2858
2843
|
)
|
|
2859
2844
|
|
|
2860
2845
|
path = "/agent"
|
|
@@ -3067,6 +3052,7 @@ async def deploy(
|
|
|
3067
3052
|
shell_tool_project_path: ShellProjectMountLegacyOption = [],
|
|
3068
3053
|
shell_empty_dir_mount: ShellEmptyDirMountOption = [],
|
|
3069
3054
|
shell_tool_empty_dir: ShellEmptyDirMountLegacyOption = [],
|
|
3055
|
+
shell_tool_config_mount: ShellConfigMountOption = [],
|
|
3070
3056
|
require_image_generation: Annotated[
|
|
3071
3057
|
Optional[str], typer.Option(..., help="Name of an image gen model")
|
|
3072
3058
|
] = None,
|
|
@@ -3226,6 +3212,7 @@ async def deploy(
|
|
|
3226
3212
|
shell_empty_dir_mount,
|
|
3227
3213
|
shell_tool_empty_dir,
|
|
3228
3214
|
),
|
|
3215
|
+
config_paths=shell_tool_config_mount,
|
|
3229
3216
|
)
|
|
3230
3217
|
|
|
3231
3218
|
path = "/agent"
|
|
@@ -4935,6 +4922,7 @@ async def run(
|
|
|
4935
4922
|
shell_tool_project_path: ShellProjectMountLegacyOption = [],
|
|
4936
4923
|
shell_empty_dir_mount: ShellEmptyDirMountOption = [],
|
|
4937
4924
|
shell_tool_empty_dir: ShellEmptyDirMountLegacyOption = [],
|
|
4925
|
+
shell_tool_config_mount: ShellConfigMountOption = [],
|
|
4938
4926
|
require_image_generation: Annotated[
|
|
4939
4927
|
Optional[str], typer.Option(..., help="Name of an image gen model")
|
|
4940
4928
|
] = None,
|
|
@@ -5150,6 +5138,7 @@ async def run(
|
|
|
5150
5138
|
shell_empty_dir_mount,
|
|
5151
5139
|
shell_tool_empty_dir,
|
|
5152
5140
|
),
|
|
5141
|
+
config_paths=shell_tool_config_mount,
|
|
5153
5142
|
)
|
|
5154
5143
|
|
|
5155
5144
|
client = RoomClient(
|
|
@@ -97,6 +97,14 @@ ShellEmptyDirMountLegacyOption = Annotated[
|
|
|
97
97
|
),
|
|
98
98
|
]
|
|
99
99
|
|
|
100
|
+
ShellConfigMountOption = Annotated[
|
|
101
|
+
list[str],
|
|
102
|
+
typer.Option(
|
|
103
|
+
"--shell-tool-config-mount",
|
|
104
|
+
help="Mount meshagent runtime config files read-only into <mount>",
|
|
105
|
+
),
|
|
106
|
+
]
|
|
107
|
+
|
|
100
108
|
AllowGotoUrlOption = Annotated[
|
|
101
109
|
bool,
|
|
102
110
|
typer.Option(
|
|
@@ -31,7 +31,6 @@ from meshagent.api import (
|
|
|
31
31
|
RoomClient,
|
|
32
32
|
WebSocketClientProtocol,
|
|
33
33
|
)
|
|
34
|
-
from meshagent.api.helpers import websocket_room_url
|
|
35
34
|
from meshagent.api.room_server_client import (
|
|
36
35
|
DockerSecret,
|
|
37
36
|
)
|
|
@@ -489,7 +488,7 @@ async def _with_client(
|
|
|
489
488
|
connection = await account_client.connect_room(project_id=project_id, room=room)
|
|
490
489
|
|
|
491
490
|
proto = WebSocketClientProtocol(
|
|
492
|
-
url=
|
|
491
|
+
url=connection.room_url,
|
|
493
492
|
token=connection.jwt,
|
|
494
493
|
)
|
|
495
494
|
client_cm = RoomClient(protocol=proto)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from types import SimpleNamespace
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
4
5
|
|
|
@@ -106,6 +107,20 @@ class _FakeAccountClient:
|
|
|
106
107
|
self.close_calls += 1
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
class _FakeConnectAccountClient:
|
|
111
|
+
def __init__(self, *, connection: SimpleNamespace) -> None:
|
|
112
|
+
self._connection = connection
|
|
113
|
+
self.close_calls = 0
|
|
114
|
+
self.connect_calls: list[dict[str, str]] = []
|
|
115
|
+
|
|
116
|
+
async def connect_room(self, *, project_id: str, room: str) -> SimpleNamespace:
|
|
117
|
+
self.connect_calls.append({"project_id": project_id, "room": room})
|
|
118
|
+
return self._connection
|
|
119
|
+
|
|
120
|
+
async def close(self) -> None:
|
|
121
|
+
self.close_calls += 1
|
|
122
|
+
|
|
123
|
+
|
|
109
124
|
@pytest.mark.asyncio
|
|
110
125
|
async def test_stream_container_job_logs_and_wait_for_exit_cancels_follow_stream(
|
|
111
126
|
monkeypatch: pytest.MonkeyPatch,
|
|
@@ -243,3 +258,61 @@ async def test_images_load_uses_room_storage_load_path(
|
|
|
243
258
|
capsys.readouterr().out
|
|
244
259
|
== "Image loaded: room.meshagent.com/images/example.tar:latest\n"
|
|
245
260
|
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@pytest.mark.asyncio
|
|
264
|
+
async def test_with_client_uses_room_url_from_connection_info(
|
|
265
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
266
|
+
) -> None:
|
|
267
|
+
connection = SimpleNamespace(
|
|
268
|
+
jwt="jwt-token",
|
|
269
|
+
room_url="wss://room-router.meshagent.dev/custom/room-endpoint",
|
|
270
|
+
)
|
|
271
|
+
account_client = _FakeConnectAccountClient(connection=connection)
|
|
272
|
+
protocol_calls: list[dict[str, str]] = []
|
|
273
|
+
|
|
274
|
+
class _FakeProtocol:
|
|
275
|
+
def __init__(self, *, url: str, token: str) -> None:
|
|
276
|
+
protocol_calls.append({"url": url, "token": token})
|
|
277
|
+
self.url = url
|
|
278
|
+
self.token = token
|
|
279
|
+
|
|
280
|
+
class _FakeRoomClient:
|
|
281
|
+
def __init__(self, *, protocol: _FakeProtocol) -> None:
|
|
282
|
+
self.protocol = protocol
|
|
283
|
+
self.enter_calls = 0
|
|
284
|
+
|
|
285
|
+
async def __aenter__(self) -> "_FakeRoomClient":
|
|
286
|
+
self.enter_calls += 1
|
|
287
|
+
return self
|
|
288
|
+
|
|
289
|
+
async def _fake_get_client() -> _FakeConnectAccountClient:
|
|
290
|
+
return account_client
|
|
291
|
+
|
|
292
|
+
async def _fake_resolve_project_id(*, project_id):
|
|
293
|
+
assert project_id == "project-1"
|
|
294
|
+
return "resolved-project"
|
|
295
|
+
|
|
296
|
+
monkeypatch.setattr(containers, "get_client", _fake_get_client)
|
|
297
|
+
monkeypatch.setattr(containers, "resolve_project_id", _fake_resolve_project_id)
|
|
298
|
+
monkeypatch.setattr(containers, "resolve_room", lambda room: f"{room}-resolved")
|
|
299
|
+
monkeypatch.setattr(containers, "WebSocketClientProtocol", _FakeProtocol)
|
|
300
|
+
monkeypatch.setattr(containers, "RoomClient", _FakeRoomClient)
|
|
301
|
+
|
|
302
|
+
returned_account_client, client = await containers._with_client(
|
|
303
|
+
project_id="project-1",
|
|
304
|
+
room="room-1",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
assert returned_account_client is account_client
|
|
308
|
+
assert account_client.connect_calls == [
|
|
309
|
+
{"project_id": "resolved-project", "room": "room-1-resolved"}
|
|
310
|
+
]
|
|
311
|
+
assert protocol_calls == [
|
|
312
|
+
{
|
|
313
|
+
"url": "wss://room-router.meshagent.dev/custom/room-endpoint",
|
|
314
|
+
"token": "jwt-token",
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
assert isinstance(client, _FakeRoomClient)
|
|
318
|
+
assert client.enter_calls == 1
|
|
@@ -11,6 +11,7 @@ from meshagent.api import RoomClient
|
|
|
11
11
|
from meshagent.api.helpers import meshagent_base_url
|
|
12
12
|
from meshagent.api.specs.service import (
|
|
13
13
|
ANNOTATION_SERVICE_README,
|
|
14
|
+
ConfigMountSpec,
|
|
14
15
|
ContainerMountSpec,
|
|
15
16
|
EmptyDirMountSpec,
|
|
16
17
|
ImageStorageMountSpec,
|
|
@@ -42,7 +43,7 @@ import json
|
|
|
42
43
|
from rich import print
|
|
43
44
|
|
|
44
45
|
SETTINGS_FILE = Path.home() / ".meshagent" / "project.json"
|
|
45
|
-
DEFAULT_SHELL_IMAGE = "python:
|
|
46
|
+
DEFAULT_SHELL_IMAGE = "meshagent/python:default"
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
def _ensure_cache_dir():
|
|
@@ -449,6 +450,15 @@ def split_empty_dir_mount(value: str, option_name: str) -> tuple[str, bool]:
|
|
|
449
450
|
return mount, read_only
|
|
450
451
|
|
|
451
452
|
|
|
453
|
+
def split_config_mount(value: str, option_name: str) -> str:
|
|
454
|
+
cleaned = value.strip()
|
|
455
|
+
if cleaned == "":
|
|
456
|
+
raise typer.BadParameter(f"{option_name} cannot be empty")
|
|
457
|
+
if ":" in cleaned:
|
|
458
|
+
raise typer.BadParameter(f"{option_name} must be in the form '<mount>'")
|
|
459
|
+
return cleaned
|
|
460
|
+
|
|
461
|
+
|
|
452
462
|
def split_image_mount(
|
|
453
463
|
value: str, option_name: str
|
|
454
464
|
) -> tuple[str, str, Optional[str], bool]:
|
|
@@ -525,16 +535,20 @@ def parse_shell_tool_mounts(
|
|
|
525
535
|
project_paths: list[str],
|
|
526
536
|
image_paths: Optional[list[str]] = None,
|
|
527
537
|
empty_dir_paths: Optional[list[str]] = None,
|
|
538
|
+
config_paths: Optional[list[str]] = None,
|
|
528
539
|
) -> Optional[ContainerMountSpec]:
|
|
529
540
|
room_mounts: list[RoomStorageMountSpec] = []
|
|
530
541
|
project_mounts: list[ProjectStorageMountSpec] = []
|
|
531
542
|
image_mounts: list[ImageStorageMountSpec] = []
|
|
532
543
|
empty_dir_mounts: list[EmptyDirMountSpec] = []
|
|
544
|
+
config_mounts: list[ConfigMountSpec] = []
|
|
533
545
|
|
|
534
546
|
if image_paths is None:
|
|
535
547
|
image_paths = []
|
|
536
548
|
if empty_dir_paths is None:
|
|
537
549
|
empty_dir_paths = []
|
|
550
|
+
if config_paths is None:
|
|
551
|
+
config_paths = []
|
|
538
552
|
|
|
539
553
|
for value in room_paths:
|
|
540
554
|
source, mount, read_only = split_container_mount(
|
|
@@ -577,11 +591,16 @@ def parse_shell_tool_mounts(
|
|
|
577
591
|
)
|
|
578
592
|
)
|
|
579
593
|
|
|
594
|
+
for value in config_paths:
|
|
595
|
+
mount = split_config_mount(value, "--shell-tool-config-mount")
|
|
596
|
+
config_mounts.append(ConfigMountSpec(path=mount))
|
|
597
|
+
|
|
580
598
|
if (
|
|
581
599
|
not room_mounts
|
|
582
600
|
and not project_mounts
|
|
583
601
|
and not image_mounts
|
|
584
602
|
and not empty_dir_mounts
|
|
603
|
+
and not config_mounts
|
|
585
604
|
):
|
|
586
605
|
return None
|
|
587
606
|
|
|
@@ -590,6 +609,7 @@ def parse_shell_tool_mounts(
|
|
|
590
609
|
project=project_mounts or None,
|
|
591
610
|
images=image_mounts or None,
|
|
592
611
|
empty_dirs=empty_dir_mounts or None,
|
|
612
|
+
configs=config_mounts or None,
|
|
593
613
|
)
|
|
594
614
|
|
|
595
615
|
|
|
@@ -34,9 +34,9 @@ def test_parse_memory_selector_with_namespace() -> None:
|
|
|
34
34
|
@pytest.mark.parametrize(
|
|
35
35
|
("value", "expected"),
|
|
36
36
|
[
|
|
37
|
-
(None,
|
|
38
|
-
("",
|
|
39
|
-
(" ",
|
|
37
|
+
(None, DEFAULT_SHELL_IMAGE),
|
|
38
|
+
("", DEFAULT_SHELL_IMAGE),
|
|
39
|
+
(" ", DEFAULT_SHELL_IMAGE),
|
|
40
40
|
("python:3.12", "python:3.12"),
|
|
41
41
|
(" none ", None),
|
|
42
42
|
("NONE", None),
|
|
@@ -148,6 +148,21 @@ def test_parse_shell_tool_mounts_parses_empty_dir_mounts() -> None:
|
|
|
148
148
|
]
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
def test_parse_shell_tool_mounts_parses_config_mounts() -> None:
|
|
152
|
+
mounts = parse_shell_tool_mounts(
|
|
153
|
+
room_paths=[],
|
|
154
|
+
project_paths=[],
|
|
155
|
+
config_paths=["/var/run/meshagent", "/tmp/meshagent-config"],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
assert mounts is not None
|
|
159
|
+
assert mounts.configs is not None
|
|
160
|
+
assert [mount.model_dump(mode="json") for mount in mounts.configs] == [
|
|
161
|
+
{"path": "/var/run/meshagent"},
|
|
162
|
+
{"path": "/tmp/meshagent-config"},
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
|
|
151
166
|
@pytest.mark.parametrize(
|
|
152
167
|
"value",
|
|
153
168
|
[
|
|
@@ -166,6 +181,23 @@ def test_parse_shell_tool_mounts_rejects_empty_dir_bind_syntax(value: str) -> No
|
|
|
166
181
|
)
|
|
167
182
|
|
|
168
183
|
|
|
184
|
+
@pytest.mark.parametrize(
|
|
185
|
+
"value",
|
|
186
|
+
[
|
|
187
|
+
"",
|
|
188
|
+
" ",
|
|
189
|
+
"/var/run/meshagent:ro",
|
|
190
|
+
],
|
|
191
|
+
)
|
|
192
|
+
def test_parse_shell_tool_mounts_rejects_invalid_config_mounts(value: str) -> None:
|
|
193
|
+
with pytest.raises(typer.BadParameter, match="--shell-tool-config-mount"):
|
|
194
|
+
parse_shell_tool_mounts(
|
|
195
|
+
room_paths=[],
|
|
196
|
+
project_paths=[],
|
|
197
|
+
config_paths=[value],
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
169
201
|
@pytest.mark.asyncio
|
|
170
202
|
async def test_init_context_from_spec_handles_missing_annotations(
|
|
171
203
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|