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,576 @@
1
+ """Typed configuration for the app-to-server subprocess communication channel.
2
+
3
+ The app spawns a `langgraph dev` subprocess and passes configuration via
4
+ environment variables prefixed with `DEEPAGENTS_CODE_SERVER_`. This module
5
+ provides a single
6
+ `ServerConfig` dataclass that both sides share so that the set of variables,
7
+ their serialization format, and their default values are defined in one place.
8
+ The app writes config with `to_env()` and the server graph reads it back
9
+ with `from_env()`.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import TYPE_CHECKING, Any
19
+
20
+ from deepagents_code._constants import DEFAULT_AGENT_NAME as DEFAULT_ASSISTANT_ID
21
+ from deepagents_code._env_vars import SERVER_ENV_PREFIX
22
+
23
+ if TYPE_CHECKING:
24
+ from deepagents_code.project_utils import ProjectContext
25
+
26
+
27
+ def _read_env_bool(suffix: str, *, default: bool = False) -> bool:
28
+ """Read a `DEEPAGENTS_CODE_SERVER_*` boolean from the environment.
29
+
30
+ Boolean env vars use the `'true'` / `'false'` convention (case insensitive).
31
+ Missing variables fall back to *default*.
32
+
33
+ Args:
34
+ suffix: Variable name suffix after the `DEEPAGENTS_CODE_SERVER_` prefix.
35
+ default: Value when the variable is absent.
36
+
37
+ Returns:
38
+ Parsed boolean.
39
+ """
40
+ raw = os.environ.get(f"{SERVER_ENV_PREFIX}{suffix}")
41
+ if raw is None:
42
+ return default
43
+ return raw.lower() == "true"
44
+
45
+
46
+ def _read_env_json(suffix: str) -> Any: # noqa: ANN401
47
+ """Read a JSON-encoded `DEEPAGENTS_CODE_SERVER_*` variable.
48
+
49
+ Args:
50
+ suffix: Variable name suffix after the `DEEPAGENTS_CODE_SERVER_` prefix.
51
+
52
+ Returns:
53
+ Parsed JSON value, or `None` if the variable is absent.
54
+
55
+ Raises:
56
+ ValueError: If the variable is present but not valid JSON.
57
+ """
58
+ raw = os.environ.get(f"{SERVER_ENV_PREFIX}{suffix}")
59
+ if raw is None:
60
+ return None
61
+ try:
62
+ return json.loads(raw)
63
+ except json.JSONDecodeError as exc:
64
+ msg = (
65
+ f"Failed to parse {SERVER_ENV_PREFIX}{suffix} as JSON: {exc}. "
66
+ f"Value was: {raw[:200]!r}"
67
+ )
68
+ raise ValueError(msg) from exc
69
+
70
+
71
+ def _read_env_str(suffix: str) -> str | None:
72
+ """Read an optional `DEEPAGENTS_CODE_SERVER_*` string variable.
73
+
74
+ Args:
75
+ suffix: Variable name suffix after the `DEEPAGENTS_CODE_SERVER_` prefix.
76
+
77
+ Returns:
78
+ The string value, or `None` if absent.
79
+ """
80
+ return os.environ.get(f"{SERVER_ENV_PREFIX}{suffix}")
81
+
82
+
83
+ def _read_env_int(suffix: str, *, default: int | None) -> int | None:
84
+ """Read a `DEEPAGENTS_CODE_SERVER_*` integer from the environment.
85
+
86
+ Args:
87
+ suffix: Variable name suffix after the `DEEPAGENTS_CODE_SERVER_` prefix.
88
+ default: Value when the variable is absent or malformed.
89
+
90
+ Returns:
91
+ Parsed integer, or the default when absent or parsing fails.
92
+ """
93
+ raw = os.environ.get(f"{SERVER_ENV_PREFIX}{suffix}")
94
+ if raw is None:
95
+ return default
96
+ try:
97
+ return int(raw)
98
+ except ValueError:
99
+ return default
100
+
101
+
102
+ def _read_env_optional_bool(suffix: str) -> bool | None:
103
+ """Read a tri-state `DEEPAGENTS_CODE_SERVER_*` boolean (`True` / `False` / `None`).
104
+
105
+ Used for settings where `None` carries a distinct meaning (e.g. "not
106
+ specified, use default logic").
107
+
108
+ Args:
109
+ suffix: Variable name suffix after the `DEEPAGENTS_CODE_SERVER_` prefix.
110
+
111
+ Returns:
112
+ `True`, `False`, or `None` when the variable is absent.
113
+ """
114
+ raw = os.environ.get(f"{SERVER_ENV_PREFIX}{suffix}")
115
+ if raw is None:
116
+ return None
117
+ return raw.lower() == "true"
118
+
119
+
120
+ def _resolve_enable_interpreter(
121
+ enable_interpreter: bool | None, sandbox_type: str | None
122
+ ) -> bool:
123
+ """Resolve the interpreter's tri-state caller option to a concrete boolean.
124
+
125
+ Args:
126
+ enable_interpreter: Explicit caller preference, or `None` to use the
127
+ sandbox-aware default.
128
+ sandbox_type: Sandbox backend identifier. Any falsy value (`None`, `""`)
129
+ or `"none"` is treated as local execution.
130
+
131
+ Returns:
132
+ The explicit `enable_interpreter` value when not `None`; `False` for
133
+ remote-sandbox defaults; otherwise the configured local default
134
+ (`settings.enable_interpreter`).
135
+ """
136
+ if enable_interpreter is not None:
137
+ return enable_interpreter
138
+ if sandbox_type and sandbox_type != "none":
139
+ return False
140
+
141
+ from deepagents_code.config import settings
142
+
143
+ return settings.enable_interpreter
144
+
145
+
146
+ def _interpreter_suppressed_by_sandbox(
147
+ *, enable_interpreter: bool | None, sandbox_type: str | None, local_default: bool
148
+ ) -> bool:
149
+ """Whether a remote sandbox suppressed the otherwise-default interpreter.
150
+
151
+ Used to decide whether to surface an advisory: returns `True` only when the
152
+ user made no explicit choice, a remote sandbox is active, and the local
153
+ default would have enabled it — i.e. the sandbox (not an explicit
154
+ `--no-interpreter` opt-out, nor a disabled `[interpreter]` config) is why
155
+ `js_eval` is unavailable.
156
+
157
+ Takes the *raw* tri-state caller intent rather than the resolved boolean: a
158
+ sandbox-suppressed default and an explicit `--no-interpreter` both resolve to
159
+ `False`, so the resolved value cannot distinguish them. Any explicit choice
160
+ (`not None`) is the user's own decision and is left unannounced.
161
+
162
+ Args:
163
+ enable_interpreter: The raw tri-state caller intent (`--interpreter` →
164
+ `True`, `--no-interpreter` → `False`, unset → `None`).
165
+ sandbox_type: Sandbox backend identifier. Any falsy value (`None`, `""`)
166
+ or `"none"` is treated as local execution.
167
+ local_default: The local-mode default (`settings.enable_interpreter`);
168
+ gating on it keeps the advisory quiet for users who disabled the
169
+ interpreter in config.
170
+
171
+ Returns:
172
+ `True` when the advisory should be shown, otherwise `False`.
173
+ """
174
+ if enable_interpreter is not None:
175
+ return False
176
+ if not (sandbox_type and sandbox_type != "none"):
177
+ return False
178
+ return local_default
179
+
180
+
181
+ @dataclass(frozen=True)
182
+ class ServerConfig:
183
+ """Full configuration payload passed from the app to the server subprocess.
184
+
185
+ Serialized to/from `DEEPAGENTS_CODE_SERVER_*` environment variables so
186
+ that the server graph (which runs in a separate Python interpreter)
187
+ can reconstruct the app's intent without sharing memory.
188
+ """
189
+
190
+ model: str | None = None
191
+ """Model spec string (e.g. `'anthropic:claude-opus-4-7'`); `None` lets the
192
+ server pick its default."""
193
+
194
+ model_params: dict[str, Any] | None = None
195
+ """Extra kwargs forwarded to the chat model constructor (temperature,
196
+ max_tokens, etc.)."""
197
+
198
+ assistant_id: str = DEFAULT_ASSISTANT_ID
199
+ """Identifier of the agent graph to invoke on the server."""
200
+
201
+ system_prompt: str | None = None
202
+ """Override for the agent's system prompt; `None` uses the agent's default."""
203
+
204
+ auto_approve: bool = False
205
+ """Auto-approve every tool call without human-in-the-loop interrupts."""
206
+
207
+ interrupt_shell_only: bool = False
208
+ """Route only shell tool calls through HITL; validate others via middleware."""
209
+
210
+ shell_allow_list: list[str] | None = None
211
+ """Restrictive allow-list of shell commands; `None` disables the allow-list.
212
+
213
+ Must be non-empty when set.
214
+ """
215
+
216
+ interactive: bool = True
217
+ """Whether the agent runs in an interactive session (vs.
218
+ one-shot/non-interactive)."""
219
+
220
+ enable_shell: bool = True
221
+ """Enable the shell execution tool on the server."""
222
+
223
+ enable_ask_user: bool = False
224
+ """Enable the `ask_user` tool that lets the agent prompt the user mid-run."""
225
+
226
+ enable_memory: bool = True
227
+ """Enable the long-term memory subsystem."""
228
+
229
+ enable_skills: bool = True
230
+ """Enable the skills subsystem (SKILL.md loading and skill tools)."""
231
+
232
+ enable_interpreter: bool = False
233
+ """Enable `CodeInterpreterMiddleware` (`js_eval` REPL) on the main agent.
234
+
235
+ Always the resolved concrete value: `from_cli_args` collapses the tri-state
236
+ caller option via `_resolve_enable_interpreter` before constructing the
237
+ config, so the `bool | None` "defer to default" sentinel never reaches this
238
+ field. The `False` default here is only the bare-constructor/`from_env`
239
+ fallback; the user-facing default (on in local mode) lives in
240
+ `settings.enable_interpreter`.
241
+
242
+ Local-mode only; the server graph raises if a sandbox is configured and
243
+ this flag is `True`.
244
+ """
245
+
246
+ interpreter_ptc: str | list[str] | None = None
247
+ """Override for `settings.interpreter_ptc`.
248
+
249
+ `None` means "fall through to whatever `settings.interpreter_ptc` resolves
250
+ to from `~/.deepagents/config.toml`". A string is one of `"safe"`/`"all"`;
251
+ a list is an explicit allowlist of tool names that may also include the
252
+ `"safe"` preset (expanded at agent-build time); `"all"` is rejected inside
253
+ a list.
254
+ """
255
+
256
+ interpreter_ptc_acknowledge_unsafe: bool = False
257
+ """Mirror of `settings.interpreter_ptc_acknowledge_unsafe` — required when
258
+ `interpreter_ptc="all"` is paired with non-`auto_approve` mode.
259
+ """
260
+
261
+ rubric_model: str | None = None
262
+ """Grader model spec for `RubricMiddleware` (e.g. `'anthropic:...'`).
263
+
264
+ `None` reuses the main agent model.
265
+ """
266
+
267
+ rubric_max_iterations: int | None = None
268
+ """Explicit grader iterations per rubric attempt; `None` uses the SDK default."""
269
+
270
+ sandbox_type: str | None = None
271
+ """Sandbox backend identifier (e.g. `'daytona'`); `None` runs tools on the
272
+ host. `'none'` is normalized to `None` in `__post_init__`."""
273
+
274
+ sandbox_id: str | None = None
275
+ """Existing sandbox ID to attach to; `None` creates a fresh sandbox."""
276
+
277
+ sandbox_snapshot_name: str | None = None
278
+ """Sandbox snapshot (langsmith) or blueprint (runloop) name; must be `None`
279
+ when `sandbox_id` is set."""
280
+
281
+ sandbox_setup: str | None = None
282
+ """Absolute path to a setup script executed inside the sandbox on first attach."""
283
+
284
+ cwd: str | None = None
285
+ """User's original working directory, serialized as an absolute path."""
286
+
287
+ project_root: str | None = None
288
+ """Detected project root (e.g. nearest git/uv/npm boundary), or `None` when
289
+ outside a project."""
290
+
291
+ mcp_config_path: str | None = None
292
+ """Absolute path to the MCP server config file; `None` disables
293
+ MCP-from-config."""
294
+
295
+ no_mcp: bool = False
296
+ """Disable all MCP server connections regardless of other config."""
297
+
298
+ trust_project_mcp: bool | None = None
299
+ """Tri-state trust flag for project-scoped MCP servers: `True`/`False`/`None`
300
+ (prompt user)."""
301
+
302
+ def __post_init__(self) -> None:
303
+ """Normalize fields and validate invariants.
304
+
305
+ Raises:
306
+ TypeError: If `rubric_max_iterations` is a boolean.
307
+ ValueError: If `shell_allow_list` is an empty list or
308
+ `rubric_max_iterations` is non-positive.
309
+ """
310
+ if self.sandbox_type == "none":
311
+ object.__setattr__(self, "sandbox_type", None)
312
+ if self.shell_allow_list is not None and len(self.shell_allow_list) == 0:
313
+ msg = "shell_allow_list must be None or non-empty"
314
+ raise ValueError(msg)
315
+ if isinstance(self.rubric_max_iterations, bool):
316
+ msg = "rubric_max_iterations must be None or a positive integer"
317
+ raise TypeError(msg)
318
+ if self.rubric_max_iterations is not None and self.rubric_max_iterations <= 0:
319
+ msg = "rubric_max_iterations must be None or a positive integer"
320
+ raise ValueError(msg)
321
+
322
+ # ------------------------------------------------------------------
323
+ # Serialization
324
+ # ------------------------------------------------------------------
325
+
326
+ def to_env(self) -> dict[str, str | None]:
327
+ """Serialize this config to a `DEEPAGENTS_CODE_SERVER_*` env-var mapping.
328
+
329
+ `None` values signal that the variable should be *cleared* from the
330
+ environment (rather than set to an empty string), so callers can
331
+ iterate and set or clear each variable in `os.environ`.
332
+
333
+ Returns:
334
+ Dict mapping env-var suffixes (without the prefix) to their
335
+ string values or `None`.
336
+ """
337
+ return {
338
+ "MODEL": self.model,
339
+ "MODEL_PARAMS": (
340
+ json.dumps(self.model_params) if self.model_params is not None else None
341
+ ),
342
+ "ASSISTANT_ID": self.assistant_id,
343
+ "SYSTEM_PROMPT": self.system_prompt,
344
+ "AUTO_APPROVE": str(self.auto_approve).lower(),
345
+ "INTERRUPT_SHELL_ONLY": str(self.interrupt_shell_only).lower(),
346
+ "SHELL_ALLOW_LIST": (
347
+ ",".join(self.shell_allow_list)
348
+ if self.shell_allow_list is not None
349
+ else None
350
+ ),
351
+ "INTERACTIVE": str(self.interactive).lower(),
352
+ "ENABLE_SHELL": str(self.enable_shell).lower(),
353
+ "ENABLE_ASK_USER": str(self.enable_ask_user).lower(),
354
+ "ENABLE_MEMORY": str(self.enable_memory).lower(),
355
+ "ENABLE_SKILLS": str(self.enable_skills).lower(),
356
+ "ENABLE_INTERPRETER": str(self.enable_interpreter).lower(),
357
+ "INTERPRETER_PTC": (
358
+ json.dumps(self.interpreter_ptc)
359
+ if self.interpreter_ptc is not None
360
+ else None
361
+ ),
362
+ "INTERPRETER_PTC_ACKNOWLEDGE_UNSAFE": str(
363
+ self.interpreter_ptc_acknowledge_unsafe
364
+ ).lower(),
365
+ "RUBRIC_MODEL": self.rubric_model,
366
+ "RUBRIC_MAX_ITERATIONS": (
367
+ str(self.rubric_max_iterations)
368
+ if self.rubric_max_iterations is not None
369
+ else None
370
+ ),
371
+ "SANDBOX_TYPE": self.sandbox_type,
372
+ "SANDBOX_ID": self.sandbox_id,
373
+ "SANDBOX_SNAPSHOT_NAME": self.sandbox_snapshot_name,
374
+ "SANDBOX_SETUP": self.sandbox_setup,
375
+ "CWD": self.cwd,
376
+ "PROJECT_ROOT": self.project_root,
377
+ "MCP_CONFIG_PATH": self.mcp_config_path,
378
+ "NO_MCP": str(self.no_mcp).lower(),
379
+ "TRUST_PROJECT_MCP": (
380
+ str(self.trust_project_mcp).lower()
381
+ if self.trust_project_mcp is not None
382
+ else None
383
+ ),
384
+ }
385
+
386
+ @classmethod
387
+ def from_env(cls) -> ServerConfig:
388
+ """Reconstruct a `ServerConfig` from `DEEPAGENTS_CODE_SERVER_*` env vars.
389
+
390
+ This is the inverse of `to_env()` and is called inside the server
391
+ subprocess to recover the app's configuration.
392
+
393
+ Returns:
394
+ A `ServerConfig` populated from the environment.
395
+ """
396
+ return cls(
397
+ model=_read_env_str("MODEL"),
398
+ model_params=_read_env_json("MODEL_PARAMS"),
399
+ assistant_id=_read_env_str("ASSISTANT_ID") or DEFAULT_ASSISTANT_ID,
400
+ system_prompt=_read_env_str("SYSTEM_PROMPT"),
401
+ auto_approve=_read_env_bool("AUTO_APPROVE"),
402
+ interrupt_shell_only=_read_env_bool("INTERRUPT_SHELL_ONLY"),
403
+ shell_allow_list=(
404
+ [cmd.strip() for cmd in raw.split(",") if cmd.strip()]
405
+ if (raw := _read_env_str("SHELL_ALLOW_LIST"))
406
+ else None
407
+ )
408
+ or None,
409
+ interactive=_read_env_bool("INTERACTIVE", default=True),
410
+ enable_shell=_read_env_bool("ENABLE_SHELL", default=True),
411
+ enable_ask_user=_read_env_bool("ENABLE_ASK_USER"),
412
+ enable_memory=_read_env_bool("ENABLE_MEMORY", default=True),
413
+ enable_skills=_read_env_bool("ENABLE_SKILLS", default=True),
414
+ enable_interpreter=_read_env_bool("ENABLE_INTERPRETER"),
415
+ interpreter_ptc=_read_env_json("INTERPRETER_PTC"),
416
+ interpreter_ptc_acknowledge_unsafe=_read_env_bool(
417
+ "INTERPRETER_PTC_ACKNOWLEDGE_UNSAFE"
418
+ ),
419
+ rubric_model=_read_env_str("RUBRIC_MODEL") or None,
420
+ rubric_max_iterations=_read_env_int("RUBRIC_MAX_ITERATIONS", default=None),
421
+ sandbox_type=_read_env_str("SANDBOX_TYPE"),
422
+ sandbox_id=_read_env_str("SANDBOX_ID"),
423
+ sandbox_snapshot_name=_read_env_str("SANDBOX_SNAPSHOT_NAME") or None,
424
+ sandbox_setup=_read_env_str("SANDBOX_SETUP"),
425
+ cwd=_read_env_str("CWD"),
426
+ project_root=_read_env_str("PROJECT_ROOT"),
427
+ mcp_config_path=_read_env_str("MCP_CONFIG_PATH"),
428
+ no_mcp=_read_env_bool("NO_MCP"),
429
+ trust_project_mcp=_read_env_optional_bool("TRUST_PROJECT_MCP"),
430
+ )
431
+
432
+ # ------------------------------------------------------------------
433
+ # Factory
434
+ # ------------------------------------------------------------------
435
+
436
+ @classmethod
437
+ def from_cli_args(
438
+ cls,
439
+ *,
440
+ project_context: ProjectContext | None,
441
+ model_name: str | None,
442
+ model_params: dict[str, Any] | None,
443
+ assistant_id: str,
444
+ auto_approve: bool,
445
+ interrupt_shell_only: bool = False,
446
+ shell_allow_list: list[str] | None = None,
447
+ sandbox_type: str = "none",
448
+ sandbox_id: str | None,
449
+ sandbox_snapshot_name: str | None,
450
+ sandbox_setup: str | None,
451
+ enable_shell: bool,
452
+ enable_ask_user: bool,
453
+ enable_interpreter: bool | None = None,
454
+ interpreter_ptc: str | list[str] | None = None,
455
+ interpreter_ptc_acknowledge_unsafe: bool = False,
456
+ rubric_model: str | None = None,
457
+ rubric_max_iterations: int | None = None,
458
+ mcp_config_path: str | None,
459
+ no_mcp: bool,
460
+ trust_project_mcp: bool | None,
461
+ interactive: bool,
462
+ ) -> ServerConfig:
463
+ """Build a `ServerConfig` from parsed CLI arguments.
464
+
465
+ Handles path normalization (e.g. resolving relative MCP config paths
466
+ against the user's working directory) so that the raw serialized values
467
+ are always absolute and unambiguous.
468
+
469
+ Args:
470
+ project_context: Explicit user/project path context.
471
+ model_name: Model spec string.
472
+ model_params: Extra model kwargs.
473
+ assistant_id: Agent identifier.
474
+ auto_approve: Auto-approve all tools.
475
+ interrupt_shell_only: Validate shell commands via middleware instead
476
+ of HITL.
477
+ shell_allow_list: Restrictive shell allow-list to forward to the
478
+ server subprocess for `ShellAllowListMiddleware`.
479
+ sandbox_type: Sandbox type.
480
+ sandbox_id: Existing sandbox ID to reuse.
481
+ sandbox_snapshot_name: Snapshot (langsmith) or blueprint (runloop)
482
+ name to use or create.
483
+ sandbox_setup: Path to setup script for the sandbox.
484
+ enable_shell: Enable shell execution tools.
485
+ enable_ask_user: Enable ask_user tool.
486
+ enable_interpreter: Enable `CodeInterpreterMiddleware` on the main
487
+ agent. `None` uses the sandbox-aware default.
488
+ interpreter_ptc: Override for `settings.interpreter_ptc`.
489
+ interpreter_ptc_acknowledge_unsafe: Mirror of
490
+ `settings.interpreter_ptc_acknowledge_unsafe`.
491
+ rubric_model: Grader model spec; `None` reuses the main model.
492
+ rubric_max_iterations: Explicit grader iterations per rubric attempt;
493
+ `None` uses the SDK default.
494
+ mcp_config_path: Path to MCP config.
495
+ no_mcp: Disable MCP.
496
+ trust_project_mcp: Trust project MCP servers.
497
+ interactive: Whether the agent is interactive.
498
+
499
+ Returns:
500
+ A fully resolved `ServerConfig`.
501
+ """
502
+ normalized_mcp = _normalize_path(mcp_config_path, project_context, "MCP config")
503
+
504
+ resolved_enable_interpreter = _resolve_enable_interpreter(
505
+ enable_interpreter, sandbox_type
506
+ )
507
+
508
+ return cls(
509
+ model=model_name,
510
+ model_params=model_params,
511
+ assistant_id=assistant_id,
512
+ auto_approve=auto_approve,
513
+ interrupt_shell_only=interrupt_shell_only,
514
+ shell_allow_list=shell_allow_list,
515
+ interactive=interactive,
516
+ enable_shell=enable_shell,
517
+ enable_ask_user=enable_ask_user,
518
+ enable_interpreter=resolved_enable_interpreter,
519
+ interpreter_ptc=interpreter_ptc,
520
+ interpreter_ptc_acknowledge_unsafe=interpreter_ptc_acknowledge_unsafe,
521
+ rubric_model=rubric_model,
522
+ rubric_max_iterations=rubric_max_iterations,
523
+ sandbox_type=sandbox_type,
524
+ sandbox_id=sandbox_id,
525
+ sandbox_snapshot_name=sandbox_snapshot_name,
526
+ sandbox_setup=_normalize_path(
527
+ sandbox_setup, project_context, "sandbox setup"
528
+ ),
529
+ cwd=(
530
+ str(project_context.user_cwd) if project_context is not None else None
531
+ ),
532
+ project_root=(
533
+ str(project_context.project_root)
534
+ if project_context is not None
535
+ and project_context.project_root is not None
536
+ else None
537
+ ),
538
+ mcp_config_path=normalized_mcp,
539
+ no_mcp=no_mcp,
540
+ trust_project_mcp=trust_project_mcp,
541
+ )
542
+
543
+
544
+ def _normalize_path(
545
+ raw_path: str | None,
546
+ project_context: ProjectContext | None,
547
+ label: str,
548
+ ) -> str | None:
549
+ """Resolve a possibly-relative path to absolute.
550
+
551
+ The server subprocess runs in a different working directory, so relative
552
+ paths must be resolved against the user's original cwd before serialization.
553
+
554
+ Args:
555
+ raw_path: Path from CLI arguments (may be relative).
556
+ project_context: User/project context for path resolution.
557
+ label: Human-readable label for error messages (e.g. "MCP config").
558
+
559
+ Returns:
560
+ Absolute path string, or `None` when *raw_path* is `None` or empty.
561
+
562
+ Raises:
563
+ ValueError: If the path cannot be resolved.
564
+ """
565
+ if not raw_path:
566
+ return None
567
+ try:
568
+ if project_context is not None:
569
+ return str(project_context.resolve_user_path(raw_path))
570
+ return str(Path(raw_path).expanduser().resolve())
571
+ except OSError as exc:
572
+ msg = (
573
+ f"Could not resolve {label} path {raw_path!r}: {exc}. "
574
+ "Ensure the path exists and is accessible."
575
+ )
576
+ raise ValueError(msg) from exc