zjcode 0.0.1__py3-none-any.whl

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 (163) hide show
  1. deepagents_code/__init__.py +42 -0
  2. deepagents_code/__main__.py +6 -0
  3. deepagents_code/_ask_user_types.py +90 -0
  4. deepagents_code/_cli_context.py +96 -0
  5. deepagents_code/_constants.py +41 -0
  6. deepagents_code/_debug.py +148 -0
  7. deepagents_code/_debug_buffer.py +204 -0
  8. deepagents_code/_env_vars.py +411 -0
  9. deepagents_code/_fake_models.py +66 -0
  10. deepagents_code/_git.py +521 -0
  11. deepagents_code/_paths.py +69 -0
  12. deepagents_code/_server_config.py +576 -0
  13. deepagents_code/_session_stats.py +235 -0
  14. deepagents_code/_startup_error.py +45 -0
  15. deepagents_code/_testing_models.py +305 -0
  16. deepagents_code/_textual_patches.py +420 -0
  17. deepagents_code/_tool_stream.py +694 -0
  18. deepagents_code/_version.py +46 -0
  19. deepagents_code/agent.py +1976 -0
  20. deepagents_code/app.py +19239 -0
  21. deepagents_code/app.tcss +438 -0
  22. deepagents_code/approval_mode.py +131 -0
  23. deepagents_code/ask_user.py +306 -0
  24. deepagents_code/auth_display.py +185 -0
  25. deepagents_code/auth_store.py +545 -0
  26. deepagents_code/built_in_skills/__init__.py +5 -0
  27. deepagents_code/built_in_skills/remember/SKILL.md +118 -0
  28. deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
  29. deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
  30. deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
  31. deepagents_code/client/__init__.py +1 -0
  32. deepagents_code/client/commands/__init__.py +1 -0
  33. deepagents_code/client/commands/auth.py +541 -0
  34. deepagents_code/client/commands/config.py +714 -0
  35. deepagents_code/client/commands/mcp.py +250 -0
  36. deepagents_code/client/commands/tools.py +416 -0
  37. deepagents_code/client/launch/__init__.py +1 -0
  38. deepagents_code/client/launch/server.py +978 -0
  39. deepagents_code/client/launch/server_manager.py +540 -0
  40. deepagents_code/client/non_interactive.py +1758 -0
  41. deepagents_code/client/remote_client.py +794 -0
  42. deepagents_code/clipboard.py +217 -0
  43. deepagents_code/command_registry.py +450 -0
  44. deepagents_code/config.py +4829 -0
  45. deepagents_code/config_manifest.py +1451 -0
  46. deepagents_code/configurable_model.py +577 -0
  47. deepagents_code/default_agent_prompt.md +12 -0
  48. deepagents_code/doctor.py +559 -0
  49. deepagents_code/editor.py +142 -0
  50. deepagents_code/event_bus.py +411 -0
  51. deepagents_code/extras_info.py +661 -0
  52. deepagents_code/file_ops.py +576 -0
  53. deepagents_code/formatting.py +135 -0
  54. deepagents_code/goal_rubric.py +497 -0
  55. deepagents_code/goal_tools.py +459 -0
  56. deepagents_code/hooks.py +348 -0
  57. deepagents_code/input.py +1041 -0
  58. deepagents_code/integrations/__init__.py +1 -0
  59. deepagents_code/integrations/openai_codex.py +551 -0
  60. deepagents_code/integrations/sandbox_config.py +198 -0
  61. deepagents_code/integrations/sandbox_factory.py +1124 -0
  62. deepagents_code/integrations/sandbox_provider.py +137 -0
  63. deepagents_code/integrations/sandbox_registry.py +350 -0
  64. deepagents_code/iterm_cursor_guide.py +176 -0
  65. deepagents_code/local_context.py +926 -0
  66. deepagents_code/main.py +3985 -0
  67. deepagents_code/managed_tools.py +642 -0
  68. deepagents_code/mcp_auth.py +1950 -0
  69. deepagents_code/mcp_config.py +176 -0
  70. deepagents_code/mcp_disabled.py +212 -0
  71. deepagents_code/mcp_login_service.py +281 -0
  72. deepagents_code/mcp_oauth_ui.py +199 -0
  73. deepagents_code/mcp_providers/__init__.py +23 -0
  74. deepagents_code/mcp_providers/_registry.py +39 -0
  75. deepagents_code/mcp_providers/base.py +133 -0
  76. deepagents_code/mcp_providers/github.py +102 -0
  77. deepagents_code/mcp_providers/slack.py +175 -0
  78. deepagents_code/mcp_tools.py +2427 -0
  79. deepagents_code/mcp_trust.py +207 -0
  80. deepagents_code/media_utils.py +826 -0
  81. deepagents_code/memory_guard.py +474 -0
  82. deepagents_code/model_config.py +4156 -0
  83. deepagents_code/notifications.py +247 -0
  84. deepagents_code/offload.py +402 -0
  85. deepagents_code/onboarding.py +223 -0
  86. deepagents_code/output.py +69 -0
  87. deepagents_code/paste_collapse.py +103 -0
  88. deepagents_code/project_utils.py +231 -0
  89. deepagents_code/py.typed +0 -0
  90. deepagents_code/reasoning_effort.py +641 -0
  91. deepagents_code/reliable_rubric.py +97 -0
  92. deepagents_code/resume_state.py +237 -0
  93. deepagents_code/server_graph.py +310 -0
  94. deepagents_code/session_end_summary.py +329 -0
  95. deepagents_code/sessions.py +1716 -0
  96. deepagents_code/skills/__init__.py +18 -0
  97. deepagents_code/skills/commands.py +1252 -0
  98. deepagents_code/skills/invocation.py +112 -0
  99. deepagents_code/skills/load.py +196 -0
  100. deepagents_code/skills/trust.py +547 -0
  101. deepagents_code/state_migration.py +136 -0
  102. deepagents_code/subagents.py +278 -0
  103. deepagents_code/system_prompt.md +204 -0
  104. deepagents_code/terminal_capabilities.py +115 -0
  105. deepagents_code/terminal_escape.py +287 -0
  106. deepagents_code/theme.py +891 -0
  107. deepagents_code/todo_list_prompt.md +12 -0
  108. deepagents_code/tool_catalog.py +509 -0
  109. deepagents_code/tool_display.py +367 -0
  110. deepagents_code/tools.py +516 -0
  111. deepagents_code/tui/__init__.py +1 -0
  112. deepagents_code/tui/textual_adapter.py +2553 -0
  113. deepagents_code/tui/widgets/__init__.py +9 -0
  114. deepagents_code/tui/widgets/_js_eval_display.py +139 -0
  115. deepagents_code/tui/widgets/_links.py +261 -0
  116. deepagents_code/tui/widgets/agent_selector.py +420 -0
  117. deepagents_code/tui/widgets/approval.py +602 -0
  118. deepagents_code/tui/widgets/ask_user.py +515 -0
  119. deepagents_code/tui/widgets/auth.py +1997 -0
  120. deepagents_code/tui/widgets/autocomplete.py +890 -0
  121. deepagents_code/tui/widgets/chat_input.py +3181 -0
  122. deepagents_code/tui/widgets/codex_auth.py +452 -0
  123. deepagents_code/tui/widgets/cwd_switch.py +242 -0
  124. deepagents_code/tui/widgets/debug_console.py +868 -0
  125. deepagents_code/tui/widgets/diff.py +252 -0
  126. deepagents_code/tui/widgets/effort_selector.py +189 -0
  127. deepagents_code/tui/widgets/goal_review.py +390 -0
  128. deepagents_code/tui/widgets/goal_status.py +50 -0
  129. deepagents_code/tui/widgets/history.py +194 -0
  130. deepagents_code/tui/widgets/install_confirm.py +248 -0
  131. deepagents_code/tui/widgets/launch_init.py +482 -0
  132. deepagents_code/tui/widgets/loading.py +227 -0
  133. deepagents_code/tui/widgets/mcp_login.py +539 -0
  134. deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
  135. deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
  136. deepagents_code/tui/widgets/message_store.py +999 -0
  137. deepagents_code/tui/widgets/messages.py +3846 -0
  138. deepagents_code/tui/widgets/model_selector.py +2343 -0
  139. deepagents_code/tui/widgets/notification_center.py +456 -0
  140. deepagents_code/tui/widgets/notification_detail.py +257 -0
  141. deepagents_code/tui/widgets/notification_settings.py +170 -0
  142. deepagents_code/tui/widgets/restart_prompt.py +158 -0
  143. deepagents_code/tui/widgets/skill_trust.py +131 -0
  144. deepagents_code/tui/widgets/startup_tip.py +91 -0
  145. deepagents_code/tui/widgets/status.py +781 -0
  146. deepagents_code/tui/widgets/subagent_panel.py +969 -0
  147. deepagents_code/tui/widgets/theme_selector.py +401 -0
  148. deepagents_code/tui/widgets/thread_selector.py +2564 -0
  149. deepagents_code/tui/widgets/tool_renderers.py +190 -0
  150. deepagents_code/tui/widgets/tool_widgets.py +304 -0
  151. deepagents_code/tui/widgets/update_available.py +311 -0
  152. deepagents_code/tui/widgets/update_confirm.py +184 -0
  153. deepagents_code/tui/widgets/update_progress.py +327 -0
  154. deepagents_code/tui/widgets/welcome.py +508 -0
  155. deepagents_code/turn_end_summary.py +522 -0
  156. deepagents_code/ui.py +858 -0
  157. deepagents_code/unicode_security.py +563 -0
  158. deepagents_code/update_check.py +3124 -0
  159. zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
  160. zjcode-0.0.1.dist-info/METADATA +220 -0
  161. zjcode-0.0.1.dist-info/RECORD +163 -0
  162. zjcode-0.0.1.dist-info/WHEEL +4 -0
  163. zjcode-0.0.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,540 @@
1
+ """Server lifecycle orchestration for the app.
2
+
3
+ Provides `start_server_and_get_agent` which handles the full flow of:
4
+
5
+ 1. Building a `ServerConfig` from application arguments
6
+ 2. Writing config to env vars via `ServerConfig.to_env()`
7
+ 3. Scaffolding a workspace (langgraph.json, checkpointer, pyproject)
8
+ 4. Starting the `langgraph dev` server
9
+ 5. Returning a `RemoteAgent` client
10
+
11
+ Also provides `server_session`, an async context manager that wraps
12
+ server startup and guaranteed cleanup so callers don't need to
13
+ duplicate try/finally teardown.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import os
20
+ import tempfile
21
+ from contextlib import asynccontextmanager
22
+ from importlib.metadata import PackageNotFoundError, version
23
+ from pathlib import Path
24
+ from typing import TYPE_CHECKING, Any
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import AsyncIterator
28
+
29
+ from deepagents_code.client.launch.server import ServerProcess
30
+ from deepagents_code.client.remote_client import RemoteAgent
31
+ from deepagents_code.mcp_tools import MCPSessionManager
32
+
33
+ from deepagents_code._env_vars import SERVER_ENV_PREFIX
34
+ from deepagents_code._server_config import ServerConfig
35
+ from deepagents_code.client.launch.server import _EPHEMERAL_PORT
36
+ from deepagents_code.project_utils import ProjectContext
37
+
38
+ logger = logging.getLogger(__name__)
39
+ _DISTRIBUTION_NAME = "deepagents-code"
40
+
41
+
42
+ def _set_or_clear_server_env(name: str, value: str | None) -> None:
43
+ """Set or clear a `DEEPAGENTS_CODE_SERVER_*` environment variable.
44
+
45
+ Args:
46
+ name: Suffix after `DEEPAGENTS_CODE_SERVER_`.
47
+ value: String value to set, or `None` to clear the variable.
48
+ """
49
+ key = f"{SERVER_ENV_PREFIX}{name}"
50
+ if value is None:
51
+ os.environ.pop(key, None)
52
+ else:
53
+ os.environ[key] = value
54
+
55
+
56
+ def _apply_server_config(config: ServerConfig) -> None:
57
+ """Write a `ServerConfig` to `DEEPAGENTS_CODE_SERVER_*` env vars.
58
+
59
+ Uses `ServerConfig.to_env()` so that the set of variables and their
60
+ serialization format are defined in one place (the `ServerConfig` dataclass)
61
+ rather than maintained independently here and in the
62
+ reader (`ServerConfig.from_env()`).
63
+
64
+ Args:
65
+ config: Fully resolved server configuration.
66
+ """
67
+ for suffix, value in config.to_env().items():
68
+ _set_or_clear_server_env(suffix, value)
69
+
70
+
71
+ def _capture_project_context() -> ProjectContext | None:
72
+ """Capture the user's project context for the server subprocess.
73
+
74
+ Returns:
75
+ Explicit project context, or `None` when cwd cannot be determined.
76
+ """
77
+ try:
78
+ return ProjectContext.from_user_cwd(Path.cwd())
79
+ except OSError:
80
+ logger.warning("Could not determine working directory for server")
81
+ return None
82
+
83
+
84
+ # ------------------------------------------------------------------
85
+ # Workspace scaffolding
86
+ # ------------------------------------------------------------------
87
+
88
+
89
+ def _scaffold_workspace(work_dir: Path) -> None:
90
+ """Prepare the server working directory with all required files.
91
+
92
+ Generates the auxiliary files (checkpointer module, `pyproject.toml`,
93
+ `langgraph.json`) that `langgraph dev` needs to boot. The generated
94
+ graph reference imports the installed `deepagents_code` package directly.
95
+
96
+ Args:
97
+ work_dir: Temporary directory that will become the server's cwd.
98
+ """
99
+ from deepagents_code.client.launch.server import generate_langgraph_json
100
+
101
+ _write_checkpointer(work_dir)
102
+ _write_pyproject(work_dir)
103
+
104
+ # `graph_ref` is a dotted import of the installed `deepagents_code` package,
105
+ # but `checkpointer_path` stays cwd-relative: checkpointer.py is generated
106
+ # fresh into work_dir (which `ServerProcess.start()` sets as the subprocess
107
+ # cwd) and is not an importable package module. Don't "unify" these — a
108
+ # dotted ref for the checkpointer would fail to resolve.
109
+ generate_langgraph_json(
110
+ work_dir,
111
+ graph_ref="deepagents_code.server_graph:make_graph",
112
+ checkpointer_path="./checkpointer.py:create_checkpointer",
113
+ )
114
+
115
+
116
+ def _write_checkpointer(work_dir: Path) -> None:
117
+ """Write a checkpointer module that reads its DB path from the environment.
118
+
119
+ The generated module reads the DB path env var at runtime so the path
120
+ is never baked into generated source. This is consistent with the
121
+ `DEEPAGENTS_CODE_SERVER_*` env-var communication pattern used elsewhere.
122
+
123
+ Args:
124
+ work_dir: Server working directory.
125
+ """
126
+ from deepagents_code.sessions import get_db_path
127
+
128
+ # Set the env var that the generated module will read at import time.
129
+ os.environ[f"{SERVER_ENV_PREFIX}DB_PATH"] = str(get_db_path())
130
+
131
+ db_path_var = f"{SERVER_ENV_PREFIX}DB_PATH"
132
+ content = f'''\
133
+ """Persistent SQLite checkpointer for the LangGraph dev server."""
134
+
135
+ import os
136
+ from contextlib import asynccontextmanager
137
+
138
+
139
+ @asynccontextmanager
140
+ async def create_checkpointer():
141
+ """Yield an AsyncSqliteSaver connected to the app's sessions DB.
142
+
143
+ The database path is read from the `{db_path_var}` env var
144
+ (set by the app before server startup) rather than hard-coded, so
145
+ the checkpointer module works without code generation.
146
+ """
147
+ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
148
+
149
+ db_path = os.environ.get("{db_path_var}")
150
+ if not db_path:
151
+ raise RuntimeError(
152
+ "{db_path_var} not set. The app must set this "
153
+ "env var before server startup."
154
+ )
155
+ async with AsyncSqliteSaver.from_conn_string(db_path) as saver:
156
+ yield saver
157
+ '''
158
+ (work_dir / "checkpointer.py").write_text(content)
159
+
160
+
161
+ def _write_pyproject(work_dir: Path) -> None:
162
+ """Write a minimal pyproject.toml for the server working directory.
163
+
164
+ The `langgraph dev` server needs to install the project dependencies.
165
+ We point it at the app package which transitively pulls in the SDK.
166
+
167
+ Args:
168
+ work_dir: Server working directory.
169
+ """
170
+ content = f"""[project]
171
+ name = "deepagents-server-runtime"
172
+ version = "0.0.1"
173
+ requires-python = ">=3.11"
174
+ dependencies = [
175
+ "{_runtime_package_dependency()}",
176
+ ]
177
+
178
+ [build-system]
179
+ requires = ["hatchling"]
180
+ build-backend = "hatchling.build"
181
+ """
182
+ (work_dir / "pyproject.toml").write_text(content)
183
+
184
+
185
+ def _default_package_project_root() -> Path | None:
186
+ """Return the project root that contains the top-level package.
187
+
188
+ Returns:
189
+ The directory above `deepagents_code` — the editable project root in
190
+ source checkouts and `site-packages` for installed wheels — or
191
+ `None` when the package location cannot be determined (e.g. a frozen
192
+ or zipimport build where `__file__` is unset). Returning `None`
193
+ (rather than guessing `Path.cwd()`) lets the caller fall back to
194
+ the installed distribution version instead of pointing at whatever
195
+ unrelated project happens to sit in the launch directory.
196
+ """
197
+ import deepagents_code
198
+
199
+ # `getattr` with a default: `__file__` is unset on frozen/zipimport builds
200
+ # and namespace packages, so it is not guaranteed to exist at runtime.
201
+ package_init = getattr(deepagents_code, "__file__", None)
202
+ if package_init is None:
203
+ return None
204
+ return Path(package_init).resolve().parent.parent
205
+
206
+
207
+ def _runtime_package_dependency(package_root: Path | None = None) -> str:
208
+ """Return the dependency spec for the app package in the server runtime.
209
+
210
+ Editable source checkouts can use a local path dependency so the generated
211
+ runtime sees the working tree. Installed wheels cannot: the package parent is
212
+ `site-packages`, which is not an installable project. In that case, depend on
213
+ the installed distribution version instead.
214
+
215
+ Args:
216
+ package_root: Optional package project root for tests.
217
+
218
+ Returns:
219
+ Requirement string for the generated runtime `pyproject.toml`.
220
+ """
221
+ root = package_root or _default_package_project_root()
222
+ if root is not None and (root / "pyproject.toml").is_file():
223
+ return f"{_DISTRIBUTION_NAME} @ {root.as_uri()}"
224
+
225
+ try:
226
+ installed_version = version(_DISTRIBUTION_NAME)
227
+ except PackageNotFoundError:
228
+ return _DISTRIBUTION_NAME
229
+ return f"{_DISTRIBUTION_NAME}=={installed_version}"
230
+
231
+
232
+ # ------------------------------------------------------------------
233
+ # MCP pre-flight validation
234
+ # ------------------------------------------------------------------
235
+
236
+
237
+ def _preflight_validate_mcp_config(
238
+ *,
239
+ mcp_config_path: str | None,
240
+ no_mcp: bool,
241
+ ) -> None:
242
+ """Validate the explicit `--mcp-config` path before spawning the server.
243
+
244
+ Catches the common failure mode of passing a malformed MCP config: a
245
+ `ValueError` raised inside the server subprocess otherwise surfaces as an
246
+ opaque truncated log dump in `wait_for_server_healthy`. Running the same
247
+ validation in the parent process lets the TUI display a clean, actionable
248
+ message with the offending path and reason.
249
+
250
+ Project-level and user-level configs discovered by `resolve_and_load_mcp_tools`
251
+ are not validated here; their errors are already handled leniently via
252
+ `load_mcp_config_with_error` and surface as errored entries in the
253
+ `/mcp` viewer rather than as a fatal startup failure.
254
+
255
+ Args:
256
+ mcp_config_path: Explicit path passed via `--mcp-config`, or `None`.
257
+ no_mcp: When `True`, MCP is disabled and validation is skipped.
258
+
259
+ Raises:
260
+ MCPConfigError: If the config file is malformed or missing required
261
+ fields. Message includes the offending path for context.
262
+ """
263
+ if no_mcp or not mcp_config_path:
264
+ return
265
+
266
+ from deepagents_code.mcp_tools import MCPConfigError, load_mcp_config
267
+
268
+ try:
269
+ load_mcp_config(mcp_config_path)
270
+ except MCPConfigError:
271
+ raise
272
+ except FileNotFoundError as exc:
273
+ msg = f"MCP config file not found: {mcp_config_path}"
274
+ raise MCPConfigError(msg) from exc
275
+ except (ValueError, TypeError) as exc:
276
+ # `ValueError` covers `json.JSONDecodeError` (subclass) and the
277
+ # shape/field validators in `_validate_server_config`; `TypeError`
278
+ # covers the wrong-type branches. Bare `RuntimeError` is
279
+ # deliberately NOT caught — it would mask unrelated bugs
280
+ # (recursion, reentrancy, stdlib internals) as config errors.
281
+ msg = f"Invalid MCP config at {mcp_config_path}: {exc}"
282
+ raise MCPConfigError(msg) from exc
283
+
284
+
285
+ # ------------------------------------------------------------------
286
+ # Server startup
287
+ # ------------------------------------------------------------------
288
+
289
+
290
+ async def start_server_and_get_agent(
291
+ *,
292
+ assistant_id: str,
293
+ model_name: str | None = None,
294
+ model_params: dict[str, Any] | None = None,
295
+ auto_approve: bool = False,
296
+ interrupt_shell_only: bool = False,
297
+ shell_allow_list: list[str] | None = None,
298
+ sandbox_type: str = "none",
299
+ sandbox_id: str | None = None,
300
+ sandbox_snapshot_name: str | None = None,
301
+ sandbox_setup: str | None = None,
302
+ enable_shell: bool = True,
303
+ enable_ask_user: bool = False,
304
+ enable_interpreter: bool | None = None,
305
+ interpreter_ptc: str | list[str] | None = None,
306
+ interpreter_ptc_acknowledge_unsafe: bool = False,
307
+ rubric_model: str | None = None,
308
+ rubric_max_iterations: int | None = None,
309
+ mcp_config_path: str | None = None,
310
+ no_mcp: bool = False,
311
+ trust_project_mcp: bool | None = None,
312
+ interactive: bool = True,
313
+ host: str = "127.0.0.1",
314
+ port: int = _EPHEMERAL_PORT,
315
+ ) -> tuple[RemoteAgent, ServerProcess, MCPSessionManager | None]:
316
+ """Start a LangGraph server and return a connected remote agent client.
317
+
318
+ Args:
319
+ assistant_id: Agent identifier.
320
+ model_name: Model spec string.
321
+ model_params: Extra model kwargs.
322
+ auto_approve: Auto-approve all tools.
323
+ interrupt_shell_only: Validate shell commands via middleware instead of HITL.
324
+ shell_allow_list: Restrictive shell allow-list for `ShellAllowListMiddleware`.
325
+ sandbox_type: Sandbox type.
326
+ sandbox_id: Existing sandbox ID to reuse.
327
+ sandbox_snapshot_name: Snapshot (langsmith) or blueprint (runloop) name.
328
+ sandbox_setup: Path to setup script for the sandbox.
329
+ enable_shell: Enable shell execution tools.
330
+ enable_ask_user: Enable ask_user tool.
331
+ enable_interpreter: Enable the JS interpreter (`js_eval`) middleware on
332
+ the main agent. `None` uses the sandbox-aware default.
333
+ interpreter_ptc: Override for `settings.interpreter_ptc` (PTC allowlist).
334
+ interpreter_ptc_acknowledge_unsafe: Explicit acknowledgement for
335
+ `interpreter_ptc="all"` outside of `auto_approve`.
336
+ rubric_model: Grader model spec; `None` reuses the main model.
337
+ rubric_max_iterations: Explicit grader iterations per rubric attempt;
338
+ `None` uses the SDK default.
339
+ mcp_config_path: Path to MCP config.
340
+ no_mcp: Disable MCP.
341
+ trust_project_mcp: Trust project MCP servers.
342
+ interactive: Whether the agent is interactive.
343
+ host: Server host.
344
+ port: Server port. Defaults to `_EPHEMERAL_PORT` (0), letting the server
345
+ pick a free ephemeral port instead of the well-known `langgraph dev`
346
+ port 2024.
347
+
348
+ Returns:
349
+ Tuple of `(remote_agent, server_process, mcp_session_manager)`.
350
+ The `mcp_session_manager` is currently always `None` (MCP lifecycle
351
+ is handled server-side).
352
+
353
+ Raises:
354
+ MCPConfigError: The explicit `--mcp-config` path is malformed,
355
+ missing, or references contradictory transport fields. Raised
356
+ from the pre-flight validator before any subprocess is spawned.
357
+ """ # noqa: DOC502 - `_preflight_validate_mcp_config()` raises indirectly
358
+ from deepagents_code.client.launch.server import ServerProcess
359
+ from deepagents_code.client.remote_client import RemoteAgent
360
+
361
+ project_context = _capture_project_context()
362
+
363
+ _preflight_validate_mcp_config(
364
+ mcp_config_path=mcp_config_path,
365
+ no_mcp=no_mcp,
366
+ )
367
+
368
+ config = ServerConfig.from_cli_args(
369
+ project_context=project_context,
370
+ model_name=model_name,
371
+ model_params=model_params,
372
+ assistant_id=assistant_id,
373
+ auto_approve=auto_approve,
374
+ interrupt_shell_only=interrupt_shell_only,
375
+ shell_allow_list=shell_allow_list,
376
+ sandbox_type=sandbox_type,
377
+ sandbox_id=sandbox_id,
378
+ sandbox_snapshot_name=sandbox_snapshot_name,
379
+ sandbox_setup=sandbox_setup,
380
+ enable_shell=enable_shell,
381
+ enable_ask_user=enable_ask_user,
382
+ enable_interpreter=enable_interpreter,
383
+ interpreter_ptc=interpreter_ptc,
384
+ interpreter_ptc_acknowledge_unsafe=interpreter_ptc_acknowledge_unsafe,
385
+ rubric_model=rubric_model,
386
+ rubric_max_iterations=rubric_max_iterations,
387
+ mcp_config_path=mcp_config_path,
388
+ no_mcp=no_mcp,
389
+ trust_project_mcp=trust_project_mcp,
390
+ interactive=interactive,
391
+ )
392
+ _apply_server_config(config)
393
+
394
+ work_dir = Path(tempfile.mkdtemp(prefix="deepagents_server_"))
395
+ _scaffold_workspace(work_dir)
396
+
397
+ server = ServerProcess(
398
+ host=host,
399
+ port=port,
400
+ config_dir=work_dir,
401
+ owns_config_dir=True,
402
+ scaffold=_scaffold_workspace,
403
+ )
404
+ started = False
405
+ try:
406
+ await server.start()
407
+ await server.wait_for_graph_ready("agent")
408
+ agent = RemoteAgent(
409
+ url=server.url,
410
+ graph_name="agent",
411
+ )
412
+ started = True
413
+ return agent, server, None
414
+ finally:
415
+ if not started:
416
+ # Startup failed or was cancelled before the server was handed off
417
+ # to the caller (which records the reference only on success). If
418
+ # `start()` itself failed it already reaped its own subprocess, so
419
+ # this `stop()` is then an idempotent no-op; this cleanup is the sole
420
+ # reaper only when `start()` succeeded but `wait_for_graph_ready()`
421
+ # (or `RemoteAgent()`) failed afterward. A `finally` rather than
422
+ # `except Exception` is deliberate: `asyncio.CancelledError` is a
423
+ # `BaseException`, so an `except Exception` guard would skip cleanup
424
+ # and orphan the process. The inner guard stops a `stop()` error
425
+ # from masking the exception already propagating.
426
+ try:
427
+ server.stop()
428
+ except Exception:
429
+ logger.exception(
430
+ "Error stopping server during startup cleanup",
431
+ )
432
+
433
+
434
+ # ------------------------------------------------------------------
435
+ # Session context manager
436
+ # ------------------------------------------------------------------
437
+
438
+
439
+ @asynccontextmanager
440
+ async def server_session(
441
+ *,
442
+ assistant_id: str,
443
+ model_name: str | None = None,
444
+ model_params: dict[str, Any] | None = None,
445
+ auto_approve: bool = False,
446
+ interrupt_shell_only: bool = False,
447
+ shell_allow_list: list[str] | None = None,
448
+ sandbox_type: str = "none",
449
+ sandbox_id: str | None = None,
450
+ sandbox_snapshot_name: str | None = None,
451
+ sandbox_setup: str | None = None,
452
+ enable_shell: bool = True,
453
+ enable_ask_user: bool = False,
454
+ enable_interpreter: bool | None = None,
455
+ interpreter_ptc: str | list[str] | None = None,
456
+ interpreter_ptc_acknowledge_unsafe: bool = False,
457
+ rubric_model: str | None = None,
458
+ rubric_max_iterations: int | None = None,
459
+ mcp_config_path: str | None = None,
460
+ no_mcp: bool = False,
461
+ trust_project_mcp: bool | None = None,
462
+ interactive: bool = True,
463
+ host: str = "127.0.0.1",
464
+ port: int = _EPHEMERAL_PORT,
465
+ ) -> AsyncIterator[tuple[RemoteAgent, ServerProcess]]:
466
+ """Async context manager that starts a server and guarantees cleanup.
467
+
468
+ Wraps `start_server_and_get_agent` so callers don't need to duplicate the
469
+ try/finally pattern for stopping the server.
470
+
471
+ Args:
472
+ assistant_id: Agent identifier.
473
+ model_name: Model spec string.
474
+ model_params: Extra model kwargs.
475
+ auto_approve: Auto-approve all tools.
476
+ interrupt_shell_only: Validate shell commands via middleware instead of HITL.
477
+ shell_allow_list: Restrictive shell allow-list for `ShellAllowListMiddleware`.
478
+ sandbox_type: Sandbox type.
479
+ sandbox_id: Existing sandbox ID to reuse.
480
+ sandbox_snapshot_name: Snapshot (langsmith) or blueprint (runloop) name.
481
+ sandbox_setup: Path to setup script for the sandbox.
482
+ enable_shell: Enable shell execution tools.
483
+ enable_ask_user: Enable ask_user tool.
484
+ enable_interpreter: Enable the JS interpreter (`js_eval`) middleware on
485
+ the main agent. `None` uses the sandbox-aware default.
486
+ interpreter_ptc: Override for `settings.interpreter_ptc` (PTC allowlist).
487
+ interpreter_ptc_acknowledge_unsafe: Explicit acknowledgement for
488
+ `interpreter_ptc="all"` outside of `auto_approve`.
489
+ rubric_model: Grader model spec; `None` reuses the main model.
490
+ rubric_max_iterations: Explicit grader iterations per rubric attempt;
491
+ `None` uses the SDK default.
492
+ mcp_config_path: Path to MCP config.
493
+ no_mcp: Disable MCP.
494
+ trust_project_mcp: Trust project MCP servers.
495
+ interactive: Whether the agent is interactive.
496
+ host: Server host.
497
+ port: Server port. Defaults to `_EPHEMERAL_PORT` (0), letting the server
498
+ pick a free ephemeral port instead of the well-known `langgraph dev`
499
+ port 2024.
500
+
501
+ Yields:
502
+ Tuple of `(remote_agent, server_process)`.
503
+ """
504
+ server_proc: ServerProcess | None = None
505
+ mcp_session_manager: MCPSessionManager | None = None
506
+ try:
507
+ agent, server_proc, mcp_session_manager = await start_server_and_get_agent(
508
+ assistant_id=assistant_id,
509
+ model_name=model_name,
510
+ model_params=model_params,
511
+ auto_approve=auto_approve,
512
+ interrupt_shell_only=interrupt_shell_only,
513
+ shell_allow_list=shell_allow_list,
514
+ sandbox_type=sandbox_type,
515
+ sandbox_id=sandbox_id,
516
+ sandbox_snapshot_name=sandbox_snapshot_name,
517
+ sandbox_setup=sandbox_setup,
518
+ enable_shell=enable_shell,
519
+ enable_ask_user=enable_ask_user,
520
+ enable_interpreter=enable_interpreter,
521
+ interpreter_ptc=interpreter_ptc,
522
+ interpreter_ptc_acknowledge_unsafe=interpreter_ptc_acknowledge_unsafe,
523
+ rubric_model=rubric_model,
524
+ rubric_max_iterations=rubric_max_iterations,
525
+ mcp_config_path=mcp_config_path,
526
+ no_mcp=no_mcp,
527
+ trust_project_mcp=trust_project_mcp,
528
+ interactive=interactive,
529
+ host=host,
530
+ port=port,
531
+ )
532
+ yield agent, server_proc
533
+ finally:
534
+ if mcp_session_manager is not None:
535
+ try:
536
+ await mcp_session_manager.cleanup()
537
+ except Exception:
538
+ logger.warning("MCP session cleanup failed", exc_info=True)
539
+ if server_proc is not None:
540
+ server_proc.stop()