meshagent-cli 0.35.3__tar.gz → 0.35.4__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 (81) hide show
  1. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/PKG-INFO +13 -13
  2. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/chatbot.py +106 -39
  3. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/cli.py +6 -6
  4. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/containers.py +5 -62
  5. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/helper.py +55 -17
  6. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/helper_test.py +20 -5
  7. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/image.py +457 -148
  8. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/image_test.py +359 -290
  9. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/process_test.py +169 -4
  10. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/task_runner.py +1 -1
  11. meshagent_cli-0.35.4/meshagent/cli/version.py +1 -0
  12. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/worker.py +0 -1
  13. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent_cli.egg-info/PKG-INFO +13 -13
  14. meshagent_cli-0.35.4/meshagent_cli.egg-info/requires.txt +29 -0
  15. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/pyproject.toml +12 -12
  16. meshagent_cli-0.35.3/meshagent/cli/version.py +0 -1
  17. meshagent_cli-0.35.3/meshagent_cli.egg-info/requires.txt +0 -29
  18. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/README.md +0 -0
  19. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/__init__.py +0 -0
  20. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/agent.py +0 -0
  21. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/api_keys.py +0 -0
  22. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/async_typer.py +0 -0
  23. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/auth.py +0 -0
  24. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/auth_async.py +0 -0
  25. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/call.py +0 -0
  26. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/cli_mcp.py +0 -0
  27. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/cli_secrets.py +0 -0
  28. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/cli_test.py +0 -0
  29. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/codex.py +0 -0
  30. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/common_options.py +0 -0
  31. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/containers_test.py +0 -0
  32. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/database.py +0 -0
  33. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/developer.py +0 -0
  34. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/developer_test.py +0 -0
  35. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/helpers.py +0 -0
  36. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/host.py +0 -0
  37. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/mailbot.py +0 -0
  38. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/mailboxes.py +0 -0
  39. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/meeting_transcriber.py +0 -0
  40. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/memory.py +0 -0
  41. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/memory_test.py +0 -0
  42. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/messaging.py +0 -0
  43. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/multi.py +0 -0
  44. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/oauth2.py +0 -0
  45. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/oci_archive.py +0 -0
  46. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/oci_archive_test.py +0 -0
  47. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/participant_token.py +0 -0
  48. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/port.py +0 -0
  49. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/port_test.py +0 -0
  50. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/process.py +0 -0
  51. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/projects.py +0 -0
  52. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/queue.py +0 -0
  53. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/queue_test.py +0 -0
  54. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/room.py +0 -0
  55. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/room_services.py +0 -0
  56. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/rooms.py +0 -0
  57. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/routes.py +0 -0
  58. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/scheduled_tasks.py +0 -0
  59. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/scheduled_tasks_test.py +0 -0
  60. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/services.py +0 -0
  61. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/services_test.py +0 -0
  62. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/sessions.py +0 -0
  63. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/sessions_test.py +0 -0
  64. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/storage.py +0 -0
  65. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/storage_test.py +0 -0
  66. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/sync.py +0 -0
  67. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/sync_test.py +0 -0
  68. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/test.py +0 -0
  69. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/tui/__init__.py +0 -0
  70. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/tui/setup.py +0 -0
  71. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/tui/setup_splash_frames.py +0 -0
  72. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/tui/setup_test.py +0 -0
  73. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/voicebot.py +0 -0
  74. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/webhook.py +0 -0
  75. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/webserver.py +0 -0
  76. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent/cli/webserver_test.py +0 -0
  77. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent_cli.egg-info/SOURCES.txt +0 -0
  78. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent_cli.egg-info/dependency_links.txt +0 -0
  79. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent_cli.egg-info/entry_points.txt +0 -0
  80. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/meshagent_cli.egg-info/top_level.txt +0 -0
  81. {meshagent_cli-0.35.3 → meshagent_cli-0.35.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-cli
3
- Version: 0.35.3
3
+ Version: 0.35.4
4
4
  Summary: CLI for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -19,21 +19,21 @@ Requires-Dist: rich~=14.3.0
19
19
  Requires-Dist: textual<2.0,>=0.50
20
20
  Requires-Dist: prompt-toolkit~=3.0.52
21
21
  Provides-Extra: all
22
- Requires-Dist: meshagent-agents[all]~=0.35.3; extra == "all"
23
- Requires-Dist: meshagent-api[all]~=0.35.3; extra == "all"
24
- Requires-Dist: meshagent-computers~=0.35.3; extra == "all"
25
- Requires-Dist: meshagent-openai~=0.35.3; extra == "all"
26
- Requires-Dist: meshagent-anthropic~=0.35.3; extra == "all"
27
- Requires-Dist: meshagent-codex~=0.35.3; extra == "all"
28
- Requires-Dist: meshagent-mcp~=0.35.3; extra == "all"
29
- Requires-Dist: meshagent-tools~=0.35.3; extra == "all"
22
+ Requires-Dist: meshagent-agents[all]~=0.35.4; extra == "all"
23
+ Requires-Dist: meshagent-api[all]~=0.35.4; extra == "all"
24
+ Requires-Dist: meshagent-computers~=0.35.4; extra == "all"
25
+ Requires-Dist: meshagent-openai~=0.35.4; extra == "all"
26
+ Requires-Dist: meshagent-anthropic~=0.35.4; extra == "all"
27
+ Requires-Dist: meshagent-codex~=0.35.4; extra == "all"
28
+ Requires-Dist: meshagent-mcp~=0.35.4; extra == "all"
29
+ Requires-Dist: meshagent-tools~=0.35.4; extra == "all"
30
30
  Requires-Dist: supabase-auth~=2.28.0; extra == "all"
31
31
  Requires-Dist: prompt-toolkit~=3.0.52; extra == "all"
32
32
  Provides-Extra: mcp-service
33
- Requires-Dist: meshagent-agents[all]~=0.35.3; extra == "mcp-service"
34
- Requires-Dist: meshagent-api~=0.35.3; extra == "mcp-service"
35
- Requires-Dist: meshagent-mcp~=0.35.3; extra == "mcp-service"
36
- Requires-Dist: meshagent-tools~=0.35.3; extra == "mcp-service"
33
+ Requires-Dist: meshagent-agents[all]~=0.35.4; extra == "mcp-service"
34
+ Requires-Dist: meshagent-api~=0.35.4; extra == "mcp-service"
35
+ Requires-Dist: meshagent-mcp~=0.35.4; extra == "mcp-service"
36
+ Requires-Dist: meshagent-tools~=0.35.4; extra == "mcp-service"
37
37
  Requires-Dist: supabase-auth~=2.28.0; extra == "mcp-service"
38
38
 
39
39
  # [Meshagent](https://www.meshagent.com)
@@ -9,6 +9,7 @@ from meshagent.tools import (
9
9
  WebFetchTool,
10
10
  WebFetchToolkitBuilder,
11
11
  ContainerShellTool,
12
+ ContainerToolkit,
12
13
  MemoriesToolkit,
13
14
  )
14
15
  from meshagent.tools.storage import (
@@ -52,6 +53,7 @@ from meshagent.api import (
52
53
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
53
54
  from meshagent.cli import async_typer
54
55
  from meshagent.cli.helper import (
56
+ build_shell_tool,
55
57
  build_shell_toolkit_builder,
56
58
  cleanup_args,
57
59
  cleanup_args_strip_options,
@@ -186,6 +188,14 @@ ShellSetEnvOption = Annotated[
186
188
  ),
187
189
  ]
188
190
 
191
+ RequireAdvancedShellOption = Annotated[
192
+ Optional[bool],
193
+ typer.Option(
194
+ "--require-advanced-shell",
195
+ help=("Enable the managed container toolkit with start/list/stop/run tools."),
196
+ ),
197
+ ]
198
+
189
199
  WORKING_DIR_HELP = "The default working directory for shell commands"
190
200
 
191
201
  WorkingDirOption = Annotated[
@@ -527,6 +537,20 @@ def _set_shell_env_vars(*, set_env: Optional[list[str]]) -> dict[str, str]:
527
537
  return env
528
538
 
529
539
 
540
+ def _build_shell_tool_env(
541
+ *,
542
+ base_env: dict[str, str],
543
+ delegate_shell_token: Optional[bool],
544
+ room: RoomClient,
545
+ ) -> dict[str, str]:
546
+ env = dict(base_env)
547
+ if delegate_shell_token:
548
+ env["MESHAGENT_TOKEN"] = room.protocol.token
549
+ env["OPENAI_API_KEY"] = room.protocol.token
550
+ env["ANTHROPIC_API_KEY"] = room.protocol.token
551
+ return env
552
+
553
+
530
554
  def _resolve_working_dir_option(
531
555
  *,
532
556
  working_dir: Optional[str],
@@ -565,6 +589,7 @@ def build_chatbot(
565
589
  require_image_generation: Optional[str] = None,
566
590
  require_local_shell: Optional[str] = None,
567
591
  require_shell: Optional[bool] = None,
592
+ require_advanced_shell: Optional[bool] = None,
568
593
  require_apply_patch: Optional[str] = None,
569
594
  require_computer_use: Optional[str] = None,
570
595
  starting_url: Optional[str] = None,
@@ -707,16 +732,16 @@ def build_chatbot(
707
732
  )
708
733
 
709
734
  self.shell_tool = None
735
+ self.advanced_shell_toolkit = None
710
736
 
711
737
  async def start(self, *, room: RoomClient):
712
738
  await super().start(room=room)
713
739
 
714
- env = dict(base_shell_env)
715
-
716
- if delegate_shell_token:
717
- env["MESHAGENT_TOKEN"] = self.room.protocol.token
718
- env["OPENAI_API_KEY"] = self.room.protocol.token
719
- env["ANTHROPIC_API_KEY"] = self.room.protocol.token
740
+ env = _build_shell_tool_env(
741
+ base_env=base_shell_env,
742
+ delegate_shell_token=delegate_shell_token,
743
+ room=room,
744
+ )
720
745
 
721
746
  if require_shell:
722
747
  if supports_openai_shell:
@@ -733,6 +758,7 @@ def build_chatbot(
733
758
  shell_kwargs = {
734
759
  "image": resolved_shell_image,
735
760
  "name": "shell",
761
+ "working_dir": working_dir,
736
762
  "env": env,
737
763
  }
738
764
  if shell_tool_mounts is not None:
@@ -740,10 +766,28 @@ def build_chatbot(
740
766
 
741
767
  self.shell_tool = ContainerShellTool(**shell_kwargs)
742
768
 
769
+ if require_advanced_shell:
770
+ self.advanced_shell_toolkit = ContainerToolkit(
771
+ working_dir=working_dir,
772
+ default_image=resolved_shell_image,
773
+ mounts=shell_tool_mounts,
774
+ env=env or None,
775
+ )
776
+
743
777
  if room_rules_path is not None:
744
778
  for p in room_rules_path:
745
779
  await self._load_room_rules(path=p)
746
780
 
781
+ async def stop(self) -> None:
782
+ room = self._room
783
+ try:
784
+ if self.advanced_shell_toolkit is not None and room is not None:
785
+ await self.advanced_shell_toolkit.stop_all(room=room)
786
+ finally:
787
+ self.advanced_shell_toolkit = None
788
+ self.shell_tool = None
789
+ await super().stop()
790
+
747
791
  async def init_session(self):
748
792
  from meshagent.cli.helper import init_context_from_spec
749
793
 
@@ -854,6 +898,8 @@ def build_chatbot(
854
898
 
855
899
  if self.shell_tool is not None:
856
900
  add_tool(toolkit_name=self.shell_tool.name, tool=self.shell_tool)
901
+ if self.advanced_shell_toolkit is not None:
902
+ add_toolkit(self.advanced_shell_toolkit)
857
903
  if require_mcp:
858
904
  raise Exception(
859
905
  "mcp tool cannot be required by cli currently, use 'optional' instead"
@@ -991,7 +1037,7 @@ def build_chatbot(
991
1037
  shell_builder_kwargs["mounts"] = shell_tool_mounts
992
1038
  providers.append(
993
1039
  build_shell_toolkit_builder(
994
- use_openai_shell_tool=supports_openai_shell,
1040
+ llm_participant=llm_participant,
995
1041
  **shell_builder_kwargs,
996
1042
  )
997
1043
  )
@@ -1044,6 +1090,7 @@ def build_process_agent(
1044
1090
  require_image_generation: Optional[str] = None,
1045
1091
  require_local_shell: Optional[str] = None,
1046
1092
  require_shell: Optional[bool] = None,
1093
+ require_advanced_shell: Optional[bool] = None,
1047
1094
  require_apply_patch: Optional[str] = None,
1048
1095
  require_computer_use: Optional[str] = None,
1049
1096
  starting_url: Optional[str] = None,
@@ -1117,9 +1164,6 @@ def build_process_agent(
1117
1164
 
1118
1165
  is_claude_model = model.startswith("claude-")
1119
1166
  supports_openai_tools = llm_participant is None and not is_claude_model
1120
- supports_openai_shell = supports_openai_shell_tool(
1121
- model=model, llm_participant=llm_participant
1122
- )
1123
1167
  base_shell_env = _copy_shell_env_vars(copy_env=shell_copy_env)
1124
1168
  base_shell_env.update(_set_shell_env_vars(set_env=shell_set_env))
1125
1169
  resolved_shell_image = resolve_shell_image(shell_image)
@@ -1192,7 +1236,8 @@ def build_process_agent(
1192
1236
  self._mail_channels: list[MailChannel] = []
1193
1237
  self._queue_channels: list[QueueChannel] = []
1194
1238
  self._toolkit_channels: list[ToolkitChannel] = []
1195
- self._shell_tool: ShellTool | ContainerShellTool | None = None
1239
+ self._shell_env: dict[str, str] = dict(base_shell_env)
1240
+ self._advanced_shell_toolkit: ContainerToolkit | None = None
1196
1241
  self._resolved_threading_mode: str | None = None
1197
1242
  if threading_mode != "none":
1198
1243
  self._resolved_threading_mode = threading_mode
@@ -1272,32 +1317,22 @@ def build_process_agent(
1272
1317
  try:
1273
1318
  await self.install_requirements()
1274
1319
 
1275
- env = dict(base_shell_env)
1276
- if delegate_shell_token:
1277
- env["MESHAGENT_TOKEN"] = self.room.protocol.token
1278
- env["OPENAI_API_KEY"] = self.room.protocol.token
1279
- env["ANTHROPIC_API_KEY"] = self.room.protocol.token
1320
+ env = _build_shell_tool_env(
1321
+ base_env=base_shell_env,
1322
+ delegate_shell_token=delegate_shell_token,
1323
+ room=room,
1324
+ )
1280
1325
 
1281
1326
  if require_shell:
1282
- if supports_openai_shell:
1283
- shell_kwargs = {
1284
- "working_dir": working_dir,
1285
- "config": ShellConfig(name="shell"),
1286
- "image": resolved_shell_image,
1287
- "env": env,
1288
- }
1289
- if shell_tool_mounts is not None:
1290
- shell_kwargs["mounts"] = shell_tool_mounts
1291
- self._shell_tool = ShellTool(**shell_kwargs)
1292
- else:
1293
- shell_kwargs = {
1294
- "image": resolved_shell_image,
1295
- "name": "shell",
1296
- "env": env,
1297
- }
1298
- if shell_tool_mounts is not None:
1299
- shell_kwargs["mounts"] = shell_tool_mounts
1300
- self._shell_tool = ContainerShellTool(**shell_kwargs)
1327
+ self._shell_env = env
1328
+
1329
+ if require_advanced_shell:
1330
+ self._advanced_shell_toolkit = ContainerToolkit(
1331
+ working_dir=working_dir,
1332
+ default_image=resolved_shell_image,
1333
+ mounts=shell_tool_mounts,
1334
+ env=env or None,
1335
+ )
1301
1336
 
1302
1337
  if room_rules_path is not None:
1303
1338
  for room_rules_file in room_rules_path:
@@ -1330,6 +1365,7 @@ def build_process_agent(
1330
1365
  self._mail_channels = []
1331
1366
  self._queue_channels = []
1332
1367
  self._toolkit_channels = []
1368
+ self._advanced_shell_toolkit = None
1333
1369
  self._room = None
1334
1370
  raise
1335
1371
 
@@ -1342,7 +1378,14 @@ def build_process_agent(
1342
1378
  self._mail_channels = []
1343
1379
  self._queue_channels = []
1344
1380
  self._toolkit_channels = []
1345
- await super().stop()
1381
+ room = self._room
1382
+ try:
1383
+ if self._advanced_shell_toolkit is not None and room is not None:
1384
+ await self._advanced_shell_toolkit.stop_all(room=room)
1385
+ finally:
1386
+ self._advanced_shell_toolkit = None
1387
+ self._shell_env = dict(base_shell_env)
1388
+ await super().stop()
1346
1389
 
1347
1390
  async def init_session(self) -> AgentSessionContext:
1348
1391
  from meshagent.cli.helper import init_context_from_spec
@@ -1447,7 +1490,7 @@ def build_process_agent(
1447
1490
  shell_builder_kwargs["mounts"] = shell_tool_mounts
1448
1491
  providers.append(
1449
1492
  build_shell_toolkit_builder(
1450
- use_openai_shell_tool=supports_openai_shell,
1493
+ llm_participant=llm_participant,
1451
1494
  **shell_builder_kwargs,
1452
1495
  )
1453
1496
  )
@@ -1524,8 +1567,22 @@ def build_process_agent(
1524
1567
  ),
1525
1568
  )
1526
1569
 
1527
- if self._shell_tool is not None:
1528
- add_tool(toolkit_name=self._shell_tool.name, tool=self._shell_tool)
1570
+ if require_shell:
1571
+ shell_config = ShellConfig(name="shell")
1572
+ add_tool(
1573
+ toolkit_name=shell_config.name,
1574
+ tool=build_shell_tool(
1575
+ model=model,
1576
+ llm_participant=llm_participant,
1577
+ config=shell_config,
1578
+ working_dir=working_dir,
1579
+ image=resolved_shell_image,
1580
+ mounts=shell_tool_mounts,
1581
+ env=self._shell_env,
1582
+ ),
1583
+ )
1584
+ if self._advanced_shell_toolkit is not None:
1585
+ add_toolkit(self._advanced_shell_toolkit)
1529
1586
 
1530
1587
  if require_mcp:
1531
1588
  raise Exception(
@@ -1851,6 +1908,7 @@ async def join(
1851
1908
  Optional[bool],
1852
1909
  typer.Option(..., help="Enable function shell tool calling"),
1853
1910
  ] = False,
1911
+ require_advanced_shell: RequireAdvancedShellOption = False,
1854
1912
  require_apply_patch: Annotated[
1855
1913
  Optional[bool],
1856
1914
  typer.Option(..., help="Enable apply patch tool calling"),
@@ -2056,6 +2114,7 @@ async def join(
2056
2114
  require_web_fetch=require_web_fetch,
2057
2115
  require_local_shell=require_local_shell,
2058
2116
  require_shell=require_shell,
2117
+ require_advanced_shell=require_advanced_shell,
2059
2118
  require_image_generation=require_image_generation,
2060
2119
  require_mcp=require_mcp,
2061
2120
  require_storage=require_storage,
@@ -2244,6 +2303,7 @@ async def service(
2244
2303
  Optional[bool],
2245
2304
  typer.Option(..., help="Enable function shell tool calling"),
2246
2305
  ] = False,
2306
+ require_advanced_shell: RequireAdvancedShellOption = False,
2247
2307
  require_apply_patch: Annotated[
2248
2308
  Optional[bool], typer.Option(..., help="Enable apply patch tool")
2249
2309
  ] = False,
@@ -2434,6 +2494,7 @@ async def service(
2434
2494
  require_web_search=require_web_search,
2435
2495
  require_web_fetch=require_web_fetch,
2436
2496
  require_shell=require_shell,
2497
+ require_advanced_shell=require_advanced_shell,
2437
2498
  require_apply_patch=require_apply_patch,
2438
2499
  require_local_shell=require_local_shell,
2439
2500
  require_image_generation=require_image_generation,
@@ -2596,6 +2657,7 @@ async def spec(
2596
2657
  Optional[bool],
2597
2658
  typer.Option(..., help="Enable function shell tool calling"),
2598
2659
  ] = False,
2660
+ require_advanced_shell: RequireAdvancedShellOption = False,
2599
2661
  require_apply_patch: Annotated[
2600
2662
  Optional[bool], typer.Option(..., help="Enable apply patch tool")
2601
2663
  ] = False,
@@ -2776,6 +2838,7 @@ async def spec(
2776
2838
  require_web_search=require_web_search,
2777
2839
  require_web_fetch=require_web_fetch,
2778
2840
  require_shell=require_shell,
2841
+ require_advanced_shell=require_advanced_shell,
2779
2842
  require_apply_patch=require_apply_patch,
2780
2843
  require_local_shell=require_local_shell,
2781
2844
  require_image_generation=require_image_generation,
@@ -2958,6 +3021,7 @@ async def deploy(
2958
3021
  Optional[bool],
2959
3022
  typer.Option(..., help="Enable function shell tool calling"),
2960
3023
  ] = False,
3024
+ require_advanced_shell: RequireAdvancedShellOption = False,
2961
3025
  require_apply_patch: Annotated[
2962
3026
  Optional[bool], typer.Option(..., help="Enable apply patch tool")
2963
3027
  ] = False,
@@ -3145,6 +3209,7 @@ async def deploy(
3145
3209
  require_web_search=require_web_search,
3146
3210
  require_web_fetch=require_web_fetch,
3147
3211
  require_shell=require_shell,
3212
+ require_advanced_shell=require_advanced_shell,
3148
3213
  require_apply_patch=require_apply_patch,
3149
3214
  require_local_shell=require_local_shell,
3150
3215
  require_image_generation=require_image_generation,
@@ -4824,6 +4889,7 @@ async def run(
4824
4889
  Optional[bool],
4825
4890
  typer.Option(..., help="Enable function shell tool calling"),
4826
4891
  ] = False,
4892
+ require_advanced_shell: RequireAdvancedShellOption = False,
4827
4893
  require_apply_patch: Annotated[
4828
4894
  Optional[bool],
4829
4895
  typer.Option(..., help="Enable apply patch tool calling"),
@@ -5053,6 +5119,7 @@ async def run(
5053
5119
  require_web_fetch=require_web_fetch,
5054
5120
  require_local_shell=require_local_shell,
5055
5121
  require_shell=require_shell,
5122
+ require_advanced_shell=require_advanced_shell,
5056
5123
  require_image_generation=require_image_generation,
5057
5124
  require_mcp=require_mcp,
5058
5125
  require_storage=require_storage,
@@ -86,18 +86,18 @@ app.add_typer(routes.app, name="route")
86
86
  app.add_typer(scheduled_tasks.app, name="scheduled-task")
87
87
  app.add_typer(meeting_transcriber.app, name="meeting-transcriber")
88
88
  app.add_typer(port.app, name="port")
89
- app.add_typer(webserver.app, name="webserver")
90
- app.add_typer(codex.app, name="codex")
89
+ app.add_typer(webserver.app, name="webserver", hidden=True)
90
+ app.add_typer(codex.app, name="codex", hidden=True)
91
91
  if not os.getenv("MESHAGENT_CLI_BUILD"):
92
92
  app.add_typer(test.app, name="test", hidden=True)
93
93
 
94
94
  app.add_typer(multi.app, name="multi")
95
95
  app.add_typer(voicebot.app, name="voicebot")
96
- app.add_typer(chatbot.app, name="chatbot")
96
+ app.add_typer(chatbot.app, name="chatbot", hidden=True)
97
97
  app.add_typer(process.app, name="process")
98
- app.add_typer(mailbot.app, name="mailbot")
99
- app.add_typer(task_runner.app, name="task-runner")
100
- app.add_typer(worker.app, name="worker")
98
+ app.add_typer(mailbot.app, name="mailbot", hidden=True)
99
+ app.add_typer(task_runner.app, name="task-runner", hidden=True)
100
+ app.add_typer(worker.app, name="worker", hidden=True)
101
101
 
102
102
  app.add_typer(room.app, name="room")
103
103
  app.add_typer(image.app, name="image")
@@ -605,7 +605,6 @@ async def exec_container(
605
605
  Optional[str],
606
606
  typer.Option(..., help="Command to execute in the container (quoted string)"),
607
607
  ] = None,
608
- tty: Annotated[bool, typer.Option(..., help="Allocate a TTY")] = False,
609
608
  ):
610
609
  account_client, client = await _with_client(
611
610
  project_id=project_id,
@@ -614,16 +613,13 @@ async def exec_container(
614
613
  result = 1
615
614
 
616
615
  try:
617
- import termios
618
616
  import shlex
619
617
 
620
- from contextlib import contextmanager
621
-
622
618
  parsed_command = shlex.split(command) if command else None
623
619
  container = await client.containers.exec(
624
620
  container_id=container_id,
625
621
  command=parsed_command,
626
- tty=tty,
622
+ tty=False,
627
623
  )
628
624
 
629
625
  async def write_all(fd, data: bytes) -> None:
@@ -650,17 +646,6 @@ async def exec_container(
650
646
  async for output in container.stdout():
651
647
  await write_all(sys.stdout.fileno(), output)
652
648
 
653
- @contextmanager
654
- def raw_mode(fd: int):
655
- import tty
656
-
657
- old = termios.tcgetattr(fd)
658
- try:
659
- tty.setraw(fd) # immediate bytes
660
- yield
661
- finally:
662
- termios.tcsetattr(fd, termios.TCSADRAIN, old)
663
-
664
649
  async def read_piped_stdin(bufsize: int = 1024):
665
650
  while True:
666
651
  chunk = await asyncio.to_thread(sys.stdin.buffer.read, bufsize)
@@ -671,53 +656,11 @@ async def exec_container(
671
656
 
672
657
  await container.write(chunk)
673
658
 
674
- async def read_stdin(bufsize: int = 1024):
675
- # If stdin is piped, just read normally (blocking is fine; no TTY semantics)
676
- if not sys.stdin.isatty():
677
- while True:
678
- chunk = sys.stdin.buffer.read(bufsize)
679
- if not chunk:
680
- return
681
- await container.write(chunk)
682
- return
683
-
684
- fd = sys.stdin.fileno()
685
-
686
- # Make reads non-blocking so we never hang shutdown
687
- prev_blocking = os.get_blocking(fd)
688
- os.set_blocking(fd, False)
689
-
690
- try:
691
- with raw_mode(fd):
692
- while True:
693
- try:
694
- chunk = os.read(fd, bufsize)
695
- except BlockingIOError:
696
- # nothing typed yet
697
- await asyncio.sleep(0.01)
698
- continue
699
-
700
- if chunk == b"":
701
- return
702
-
703
- # optional: allow Ctrl-C to exit
704
- if chunk == b"\x03":
705
- return
706
-
707
- await container.write(chunk)
708
- finally:
709
- os.set_blocking(fd, prev_blocking)
710
-
711
- if not tty and not sys.stdin.isatty():
712
- await asyncio.gather(read_stdout(), read_stderr(), read_piped_stdin())
713
- else:
714
- if not sys.stdin.isatty():
715
- print("[red]TTY requested but not a TTY[/red]")
716
- raise typer.Exit(-1)
717
-
718
- reader = asyncio.create_task(read_stdin())
659
+ if sys.stdin.isatty():
660
+ await container.close_stdin()
719
661
  await asyncio.gather(read_stdout(), read_stderr())
720
- reader.cancel()
662
+ else:
663
+ await asyncio.gather(read_stdout(), read_stderr(), read_piped_stdin())
721
664
 
722
665
  result = await container.result
723
666
  finally:
@@ -21,8 +21,13 @@ from meshagent.api.specs.service import (
21
21
  from meshagent.agents.context import AgentSessionContext
22
22
  from meshagent.api.client import Meshagent, RoomConnectionInfo
23
23
  from meshagent.cli import async_typer, auth_async
24
- from meshagent.openai.tools.responses_adapter import ShellConfig, ShellToolkitBuilder
25
- from meshagent.tools import ContainerShellTool, Toolkit, ToolkitBuilder
24
+ from meshagent.openai.tools.responses_adapter import ShellConfig, ShellTool
25
+ from meshagent.tools import (
26
+ ContainerShellTool,
27
+ ProcessShellTool,
28
+ Toolkit,
29
+ ToolkitBuilder,
30
+ )
26
31
  from meshagent.tools.container_shell import DEFAULT_CONTAINER_MOUNT_SPEC
27
32
  from meshagent.tools.storage import (
28
33
  StorageToolLocalMount,
@@ -64,16 +69,56 @@ def supports_openai_shell_tool(
64
69
  return llm_participant is None and model.startswith("gpt-")
65
70
 
66
71
 
67
- class _ContainerBackedShellToolkitBuilder(ToolkitBuilder):
72
+ def build_shell_tool(
73
+ *,
74
+ model: str,
75
+ llm_participant: Optional[str] = None,
76
+ config: Optional[ShellConfig] = None,
77
+ working_dir: Optional[str] = None,
78
+ image: Optional[str] = DEFAULT_SHELL_IMAGE,
79
+ mounts: Optional[ContainerMountSpec] = DEFAULT_CONTAINER_MOUNT_SPEC,
80
+ env: Optional[dict[str, str]] = None,
81
+ ) -> ShellTool | ContainerShellTool | ProcessShellTool:
82
+ if config is None:
83
+ config = ShellConfig(name="shell")
84
+
85
+ if supports_openai_shell_tool(model=model, llm_participant=llm_participant):
86
+ return ShellTool(
87
+ config=config,
88
+ working_dir=working_dir,
89
+ image=image,
90
+ mounts=mounts,
91
+ env=env,
92
+ )
93
+
94
+ if image is None:
95
+ return ProcessShellTool(
96
+ name=config.name,
97
+ working_dir=working_dir,
98
+ env=env,
99
+ )
100
+
101
+ return ContainerShellTool(
102
+ name=config.name,
103
+ working_dir=working_dir,
104
+ image=image,
105
+ mounts=mounts,
106
+ env=env,
107
+ )
108
+
109
+
110
+ class _RuntimeAwareShellToolkitBuilder(ToolkitBuilder):
68
111
  def __init__(
69
112
  self,
70
113
  *,
114
+ llm_participant: Optional[str] = None,
71
115
  working_dir: Optional[str] = None,
72
116
  image: Optional[str] = DEFAULT_SHELL_IMAGE,
73
117
  mounts: Optional[ContainerMountSpec] = DEFAULT_CONTAINER_MOUNT_SPEC,
74
118
  env: Optional[dict[str, str]] = None,
75
119
  ) -> None:
76
120
  super().__init__(name="shell", type=ShellConfig)
121
+ self.llm_participant = llm_participant
77
122
  self.working_dir = working_dir
78
123
  self.image = image
79
124
  self.mounts = mounts
@@ -83,13 +128,13 @@ class _ContainerBackedShellToolkitBuilder(ToolkitBuilder):
83
128
  self, *, room: RoomClient, model: str, config: ShellConfig
84
129
  ) -> Toolkit:
85
130
  del room
86
- del model
87
- del config
88
131
  return Toolkit(
89
132
  name="shell",
90
133
  tools=[
91
- ContainerShellTool(
92
- name="shell",
134
+ build_shell_tool(
135
+ model=model,
136
+ llm_participant=self.llm_participant,
137
+ config=config,
93
138
  working_dir=self.working_dir,
94
139
  image=self.image,
95
140
  mounts=self.mounts,
@@ -101,21 +146,14 @@ class _ContainerBackedShellToolkitBuilder(ToolkitBuilder):
101
146
 
102
147
  def build_shell_toolkit_builder(
103
148
  *,
104
- use_openai_shell_tool: bool,
149
+ llm_participant: Optional[str] = None,
105
150
  working_dir: Optional[str] = None,
106
151
  image: Optional[str] = DEFAULT_SHELL_IMAGE,
107
152
  mounts: Optional[ContainerMountSpec] = DEFAULT_CONTAINER_MOUNT_SPEC,
108
153
  env: Optional[dict[str, str]] = None,
109
154
  ) -> ToolkitBuilder:
110
- if use_openai_shell_tool:
111
- return ShellToolkitBuilder(
112
- working_dir=working_dir,
113
- image=image,
114
- mounts=mounts,
115
- env=env,
116
- )
117
-
118
- return _ContainerBackedShellToolkitBuilder(
155
+ return _RuntimeAwareShellToolkitBuilder(
156
+ llm_participant=llm_participant,
119
157
  working_dir=working_dir,
120
158
  image=image,
121
159
  mounts=mounts,