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.
Files changed (83) hide show
  1. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/PKG-INFO +13 -13
  2. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/chatbot.py +11 -22
  3. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/common_options.py +8 -0
  4. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/containers.py +1 -2
  5. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/containers_test.py +73 -0
  6. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/helper.py +21 -1
  7. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/helper_test.py +35 -3
  8. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/image.py +973 -165
  9. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/image_test.py +1044 -56
  10. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/oci_archive.py +25 -0
  11. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/oci_archive_test.py +36 -0
  12. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/process_test.py +12 -1
  13. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/services.py +2 -0
  14. meshagent_cli-0.36.0/meshagent/cli/version.py +1 -0
  15. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/PKG-INFO +13 -13
  16. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/requires.txt +12 -12
  17. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/pyproject.toml +12 -12
  18. meshagent_cli-0.35.7/meshagent/cli/version.py +0 -1
  19. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/README.md +0 -0
  20. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/__init__.py +0 -0
  21. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/agent.py +0 -0
  22. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/api_keys.py +0 -0
  23. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/api_keys_test.py +0 -0
  24. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/async_typer.py +0 -0
  25. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/async_typer_test.py +0 -0
  26. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/auth.py +0 -0
  27. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/auth_async.py +0 -0
  28. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/call.py +0 -0
  29. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli.py +0 -0
  30. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli_mcp.py +0 -0
  31. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli_secrets.py +0 -0
  32. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/cli_test.py +0 -0
  33. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/codex.py +0 -0
  34. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/database.py +0 -0
  35. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/developer.py +0 -0
  36. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/developer_test.py +0 -0
  37. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/helpers.py +0 -0
  38. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/host.py +0 -0
  39. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/mailbot.py +0 -0
  40. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/mailboxes.py +0 -0
  41. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/meeting_transcriber.py +0 -0
  42. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/memory.py +0 -0
  43. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/memory_test.py +0 -0
  44. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/messaging.py +0 -0
  45. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/multi.py +0 -0
  46. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/oauth2.py +0 -0
  47. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/participant_token.py +0 -0
  48. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/port.py +0 -0
  49. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/port_test.py +0 -0
  50. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/process.py +0 -0
  51. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/projects.py +0 -0
  52. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/queue.py +0 -0
  53. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/queue_test.py +0 -0
  54. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/room.py +0 -0
  55. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/room_services.py +0 -0
  56. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/rooms.py +0 -0
  57. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/root_commands.py +0 -0
  58. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/routes.py +0 -0
  59. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/scheduled_tasks.py +0 -0
  60. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/scheduled_tasks_test.py +0 -0
  61. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/services_test.py +0 -0
  62. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sessions.py +0 -0
  63. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sessions_test.py +0 -0
  64. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/storage.py +0 -0
  65. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/storage_test.py +0 -0
  66. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sync.py +0 -0
  67. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/sync_test.py +0 -0
  68. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/task_runner.py +0 -0
  69. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/test.py +0 -0
  70. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/__init__.py +0 -0
  71. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/setup.py +0 -0
  72. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/setup_splash_frames.py +0 -0
  73. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/tui/setup_test.py +0 -0
  74. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/voicebot.py +0 -0
  75. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/webhook.py +0 -0
  76. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/webserver.py +0 -0
  77. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/webserver_test.py +0 -0
  78. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent/cli/worker.py +0 -0
  79. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/SOURCES.txt +0 -0
  80. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/dependency_links.txt +0 -0
  81. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/entry_points.txt +0 -0
  82. {meshagent_cli-0.35.7 → meshagent_cli-0.36.0}/meshagent_cli.egg-info/top_level.txt +0 -0
  83. {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.35.7
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.35.7; extra == "all"
24
- Requires-Dist: meshagent-api[all]~=0.35.7; extra == "all"
25
- Requires-Dist: meshagent-computers~=0.35.7; extra == "all"
26
- Requires-Dist: meshagent-openai~=0.35.7; extra == "all"
27
- Requires-Dist: meshagent-anthropic~=0.35.7; extra == "all"
28
- Requires-Dist: meshagent-codex~=0.35.7; extra == "all"
29
- Requires-Dist: meshagent-mcp~=0.35.7; extra == "all"
30
- Requires-Dist: meshagent-tools~=0.35.7; extra == "all"
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.7; extra == "mcp-service"
35
- Requires-Dist: meshagent-api~=0.35.7; extra == "mcp-service"
36
- Requires-Dist: meshagent-mcp~=0.35.7; extra == "mcp-service"
37
- Requires-Dist: meshagent-tools~=0.35.7; extra == "mcp-service"
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=websocket_room_url(room_name=room),
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:3.13"
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, "python:3.13"),
38
- ("", "python:3.13"),
39
- (" ", "python:3.13"),
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