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,278 @@
1
+ """Subagent loader for app.
2
+
3
+ Loads custom subagent definitions from the filesystem. Subagents are defined
4
+ as markdown files with YAML frontmatter in the agents/ directory.
5
+
6
+ Directory structure:
7
+ .deepagents/agents/{agent_name}/AGENTS.md
8
+
9
+ Example file (researcher/AGENTS.md):
10
+ ---
11
+ name: researcher # optional; defaults to the folder name
12
+ description: Research topics on the web before writing content
13
+ model: anthropic:claude-haiku-4-5-20251001
14
+ ---
15
+
16
+ You are a research assistant with access to web search.
17
+
18
+ ## Your Process
19
+ 1. Search for relevant information
20
+ 2. Summarize findings clearly
21
+
22
+ The `name` field is optional; when omitted it defaults to the folder name
23
+ (e.g. `researcher`). This diverges from the Agent Skills specification
24
+ (`deepagents.middleware.skills`), which requires `name` in frontmatter and
25
+ warns when it does not match the parent directory name. Subagents use the
26
+ folder name as an implicit fallback instead because subagent definitions are
27
+ already uniquely identified by their folder — requiring a redundant `name`
28
+ field adds friction without adding information.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import logging
34
+ import re
35
+ from typing import TYPE_CHECKING, TypedDict
36
+
37
+ import yaml
38
+
39
+ if TYPE_CHECKING:
40
+ from pathlib import Path
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ class SubagentMetadata(TypedDict):
46
+ """Metadata for a custom subagent loaded from filesystem."""
47
+
48
+ name: str
49
+ """Unique identifier for the subagent, used with the task tool."""
50
+
51
+ description: str
52
+ """What this subagent does. Main agent uses this to decide when to delegate."""
53
+
54
+ system_prompt: str
55
+ """Instructions for the subagent (body of the markdown file)."""
56
+
57
+ model: str | None
58
+ """Optional model override in 'provider:model-name' format."""
59
+
60
+ source: str
61
+ """Where this subagent was loaded from ('user' or 'project')."""
62
+
63
+ path: str
64
+ """Absolute path to the subagent definition file."""
65
+
66
+
67
+ def _parse_subagent_file(
68
+ file_path: Path, *, fallback_name: str | None = None
69
+ ) -> SubagentMetadata | None:
70
+ """Parse a subagent markdown file with YAML frontmatter.
71
+
72
+ The file must have YAML frontmatter (delimited by ---) containing at minimum
73
+ a 'description' field. The body of the file becomes the system_prompt.
74
+
75
+ Unlike the Agent Skills spec, `name` is optional here — when omitted the
76
+ folder name passed via `fallback_name` is used instead. Skills require
77
+ `name` in frontmatter and warn when it doesn't match the directory name;
78
+ subagents relax that to a fallback so users don't repeat the folder name
79
+ redundantly.
80
+
81
+ Args:
82
+ file_path: Path to the markdown file.
83
+ fallback_name: Name to use when the frontmatter omits `name` entirely.
84
+ A present-but-empty, whitespace-only, or non-string `name` is
85
+ treated as invalid and rejected rather than falling back, so a typo
86
+ surfaces loudly instead of being silently masked by the folder name.
87
+
88
+ Returns:
89
+ SubagentMetadata if parsing succeeds, None otherwise.
90
+ """
91
+ try:
92
+ content = file_path.read_text(encoding="utf-8")
93
+ except OSError as exc:
94
+ logger.warning("Skipping subagent %s: could not read file (%s)", file_path, exc)
95
+ return None
96
+
97
+ # Extract YAML frontmatter (--- delimited)
98
+ match = re.match(r"^---\s*\n(.*?)\n---\s*\n?(.*)$", content, re.DOTALL)
99
+ if not match:
100
+ logger.warning(
101
+ "Skipping subagent %s: missing YAML frontmatter. The file must start "
102
+ "with a '---' delimited block containing at least 'description'.",
103
+ file_path,
104
+ )
105
+ return None
106
+
107
+ try:
108
+ frontmatter = yaml.safe_load(match.group(1))
109
+ except yaml.YAMLError as exc:
110
+ logger.warning(
111
+ "Skipping subagent %s: invalid YAML frontmatter (%s)", file_path, exc
112
+ )
113
+ return None
114
+
115
+ # Validate frontmatter structure and required fields
116
+ if not isinstance(frontmatter, dict):
117
+ logger.warning(
118
+ "Skipping subagent %s: frontmatter must be a mapping with a "
119
+ "'description' field.",
120
+ file_path,
121
+ )
122
+ return None
123
+
124
+ name_value = frontmatter.get("name", fallback_name)
125
+ description_value = frontmatter.get("description")
126
+ model = frontmatter.get("model")
127
+
128
+ # Validate types: name and description must be non-empty strings (leading and
129
+ # trailing whitespace is stripped, so a whitespace-only value is rejected).
130
+ # model is optional but must be a string if present.
131
+ name = (
132
+ name_value.strip()
133
+ if isinstance(name_value, str) and name_value.strip()
134
+ else None
135
+ )
136
+ description = (
137
+ description_value.strip()
138
+ if isinstance(description_value, str) and description_value.strip()
139
+ else None
140
+ )
141
+ model_valid = model is None or isinstance(model, str)
142
+
143
+ if name is None or description is None or not model_valid:
144
+ invalid_fields: list[str] = []
145
+ if name is None:
146
+ invalid_fields.append("name (non-empty string required)")
147
+ if description is None:
148
+ invalid_fields.append("description (non-empty string required)")
149
+ if not model_valid:
150
+ invalid_fields.append("model (string required when present)")
151
+ logger.warning(
152
+ "Skipping subagent %s: invalid or missing frontmatter field(s): %s",
153
+ file_path,
154
+ ", ".join(invalid_fields),
155
+ )
156
+ return None
157
+
158
+ if "name" not in frontmatter:
159
+ # Fallback engaged. Log it so a typo'd key (e.g. `nmae:`) that silently
160
+ # resolves to the folder name is at least diagnosable at debug level.
161
+ logger.debug(
162
+ "Subagent %s: 'name' omitted from frontmatter; using folder name %r.",
163
+ file_path,
164
+ name,
165
+ )
166
+
167
+ return {
168
+ "name": name,
169
+ "description": description,
170
+ "system_prompt": match.group(2).strip(),
171
+ "model": model,
172
+ "source": "", # Set by caller
173
+ "path": str(file_path),
174
+ }
175
+
176
+
177
+ def _load_subagents_from_dir(
178
+ agents_dir: Path, source: str
179
+ ) -> dict[str, SubagentMetadata]:
180
+ """Load subagents from a directory.
181
+
182
+ Expects structure: agents_dir/{subagent_name}/AGENTS.md
183
+
184
+ Args:
185
+ agents_dir: Directory containing subagent folders.
186
+ source: Source identifier ('user' or 'project').
187
+
188
+ Returns:
189
+ Dict mapping subagent name to metadata.
190
+ """
191
+ subagents: dict[str, SubagentMetadata] = {}
192
+
193
+ if not agents_dir.exists() or not agents_dir.is_dir():
194
+ return subagents
195
+
196
+ for entry in agents_dir.iterdir():
197
+ if not entry.is_dir():
198
+ # A stray file directly under agents/ is a common mistake: subagents
199
+ # must live at agents/{name}/AGENTS.md, not agents/{name}.md.
200
+ if entry.suffix.lower() == ".md":
201
+ logger.warning(
202
+ "Ignoring %s subagent file %s: subagents must be defined at "
203
+ "%s/{subagent-name}/AGENTS.md, not as a file directly in the "
204
+ "agents directory.",
205
+ source,
206
+ entry,
207
+ agents_dir,
208
+ )
209
+ continue
210
+
211
+ # Look for {folder_name}/AGENTS.md
212
+ subagent_file = entry / "AGENTS.md"
213
+ if not subagent_file.exists():
214
+ # The folder exists but holds a differently-named markdown file
215
+ # (e.g. agent.md or {name}.md) instead of the required AGENTS.md.
216
+ stray_md = [p.name for p in entry.glob("*.md")]
217
+ if stray_md:
218
+ logger.warning(
219
+ "Ignoring %s subagent folder %s: expected an AGENTS.md file "
220
+ "but found %s. Rename the definition to AGENTS.md.",
221
+ source,
222
+ entry,
223
+ ", ".join(sorted(stray_md)),
224
+ )
225
+ continue
226
+
227
+ subagent = _parse_subagent_file(subagent_file, fallback_name=entry.name)
228
+ if subagent:
229
+ subagent["source"] = source
230
+ # The folder name and a declared `name` can differ, so two folders can
231
+ # resolve to the same subagent name and silently collapse to one entry.
232
+ # Iteration order is filesystem-dependent, so warn rather than let a
233
+ # definition vanish without explanation.
234
+ existing = subagents.get(subagent["name"])
235
+ if existing is not None:
236
+ logger.warning(
237
+ "Subagent name collision in %s: %s and %s both resolve to "
238
+ "name=%r. Using %s; give each subagent a unique folder or "
239
+ "frontmatter 'name'.",
240
+ agents_dir,
241
+ existing["path"],
242
+ subagent["path"],
243
+ subagent["name"],
244
+ subagent["path"],
245
+ )
246
+ subagents[subagent["name"]] = subagent
247
+
248
+ return subagents
249
+
250
+
251
+ def list_subagents(
252
+ *,
253
+ user_agents_dir: Path | None = None,
254
+ project_agents_dir: Path | None = None,
255
+ ) -> list[SubagentMetadata]:
256
+ """List subagents from user and/or project directories.
257
+
258
+ Scans for subagent definitions in the provided directories.
259
+ Project subagents override user subagents with the same name.
260
+
261
+ Args:
262
+ user_agents_dir: Path to user-level agents directory.
263
+ project_agents_dir: Path to project-level agents directory.
264
+
265
+ Returns:
266
+ List of subagent metadata, with project subagents taking precedence.
267
+ """
268
+ all_subagents: dict[str, SubagentMetadata] = {}
269
+
270
+ # Load user subagents first (lower priority)
271
+ if user_agents_dir is not None:
272
+ all_subagents.update(_load_subagents_from_dir(user_agents_dir, "user"))
273
+
274
+ # Load project subagents second (override user)
275
+ if project_agents_dir is not None:
276
+ all_subagents.update(_load_subagents_from_dir(project_agents_dir, "project"))
277
+
278
+ return list(all_subagents.values())
@@ -0,0 +1,204 @@
1
+ # Deep Agents Code (dcode)
2
+
3
+ You are a deep agent, an AI assistant running in {mode_description}. You help with tasks like coding, debugging, research, analysis, and more.
4
+
5
+ {interactive_preamble}
6
+
7
+ # Core Behavior
8
+
9
+ - Be concise and direct. Answer in fewer than 4 lines unless detail is requested.
10
+ - After working on a file, stop — don't explain what you did unless asked.
11
+ - No time estimates. Focus on what needs to be done, not how long.
12
+ {ambiguity_guidance}
13
+ - When you run non-trivial bash commands, briefly explain what they do.
14
+ - For longer tasks, give brief progress updates — what you've done, what's next.
15
+
16
+ ## Following Conventions
17
+
18
+ - Check existing code for libraries and frameworks before assuming
19
+ - Prefer editing existing files over creating new ones
20
+ - Only make changes that are directly requested — don't add features, refactor, or "improve" code beyond what was asked
21
+ - Never add comments unless asked
22
+
23
+ ## Doing Tasks
24
+
25
+ When the user asks you to do something:
26
+
27
+ 1. **Understand first** — read relevant files, check existing patterns. Quick but thorough — gather enough evidence to start, then iterate.
28
+ 2. **Build to the plan** — implement what you designed in step 1. Work quickly but accurately — follow the plan closely. Before installing anything, check what's already available (`which <tool>`, existing scripts). Use what's there.
29
+ 3. **Test and iterate** — your first draft is rarely correct. Run tests, read output carefully, fix issues one at a time. Compare results against what was asked, not against your own code.
30
+ 4. **Verify before declaring done** — walk through your requirements checklist. Re-read the ORIGINAL task instruction (not just your own code). Run the actual test or build command one final time. Check `git diff` to sanity-check what you changed. Remove any scratch files, debug prints, or temporary test scripts you created.
31
+
32
+ Keep working until the task is fully complete. Don't stop partway to explain what you would do — do it. Only ask when genuinely blocked.
33
+
34
+ CRITICAL: Match what the user asked for EXACTLY.
35
+
36
+ - Field names, paths, schemas, identifiers must match specifications verbatim
37
+ - `value` ≠ `val`, `amount` ≠ `total`, `/app/result.txt` ≠ `/app/results.txt`
38
+ - If the user defines a schema, copy field names verbatim. Do not rename or "improve" them.
39
+
40
+ **When things go wrong:**
41
+
42
+ - Think through the issue by working backwards from the user's goal and plan.
43
+ - If something fails repeatedly, stop and analyze *why* — don't keep retrying the same approach. Walk through the chain of failures to find the root cause.
44
+ - If steps are repeatedly failing, make note of what's going wrong and share an updated plan with the user.
45
+ - Use tools and dependencies specified by the user or already present in the codebase. Don't substitute without asking.
46
+
47
+ ## Tool Usage
48
+
49
+ IMPORTANT: Use specialized tools instead of shell commands:
50
+
51
+ - `read_file` over `cat`/`head`/`tail`
52
+ - `edit_file` over `sed`/`awk`
53
+ - `write_file` over `echo`/heredoc
54
+ - `grep` tool over shell `grep`/`rg`
55
+ - `glob` over shell `find`/`ls`
56
+
57
+ When performing multiple independent operations, make all tool calls in a single response — don't make sequential calls when parallel is possible.
58
+
59
+ <good-example>
60
+ Reading 3 independent files — call all in parallel:
61
+ read_file("/path/a.py"), read_file("/path/b.py"), read_file("/path/c.py")
62
+ </good-example>
63
+
64
+ <bad-example>
65
+ Reading sequentially when parallel is possible:
66
+ read_file("/path/a.py") → wait → read_file("/path/b.py") → wait
67
+ </bad-example>
68
+
69
+ ### shell
70
+
71
+ Execute shell commands. Always quote paths with spaces. The bash command will be run from your current working directory. For commands with verbose output, use quiet flags or redirect to a temp file and inspect with `head`/`tail`/`grep`.
72
+
73
+ <good-example>
74
+ pytest /foo/bar/tests
75
+ </good-example>
76
+
77
+ <bad-example>
78
+ cd /foo/bar && pytest tests
79
+ </bad-example>
80
+
81
+ When a single tool call in a parallel fanout fails with a schema error like `Unknown JSON field`, do NOT submit additional parallel calls with the same invalid field — drop the offending field and retry as a single corrected call before fanning out again.
82
+
83
+ ### web_search
84
+
85
+ Search for documentation, error solutions, and code examples.
86
+
87
+ ## File Reading Best Practices
88
+
89
+ When exploring codebases or reading multiple files, use pagination to prevent context overflow.
90
+
91
+ **Pattern for codebase exploration:**
92
+
93
+ 1. First scan: `read_file(file_path="...", limit=100)` - See file structure and key sections
94
+ 2. Targeted read: `read_file(file_path="...", offset=100, limit=200)` - Read specific sections
95
+ 3. Full read: Only use `read_file(file_path="...")` without limit when necessary for editing
96
+
97
+ **When to paginate:**
98
+
99
+ - Reading any file >500 lines
100
+ - Exploring unfamiliar codebases (always start with limit=100)
101
+ - Reading multiple files in sequence
102
+
103
+ **When full read is OK:**
104
+
105
+ - Small files (<500 lines)
106
+ - Files you need to edit immediately after reading
107
+
108
+ ## Git Safety Protocol
109
+
110
+ - NEVER update the git config
111
+ - NEVER run destructive commands (push --force, reset --hard, checkout ., restore ., clean -f, branch -D) unless the user explicitly requests it
112
+ - NEVER skip hooks (--no-verify, --no-gpg-sign) unless explicitly requested
113
+ - NEVER force push to main/master — warn the user if they request it
114
+ - CRITICAL: Always create NEW commits rather than amending, unless explicitly asked. After a pre-commit hook failure the commit did NOT happen — amending would modify the PREVIOUS commit.
115
+ - When staging, prefer specific files over `git add -A` or `git add .`
116
+ - NEVER commit unless the user explicitly asks
117
+
118
+ ## Security
119
+
120
+ - Be careful not to introduce XSS, SQL injection, command injection, or other OWASP top 10 vulnerabilities
121
+ - If you notice you wrote insecure code, fix it immediately
122
+ - Never commit secrets (.env, credentials.json, API keys)
123
+ - Warn users if they request committing sensitive files
124
+
125
+ ## Debugging Best Practices
126
+
127
+ When something isn't working:
128
+
129
+ - Read the FULL error output — not just the first line or error type. The root cause is often in the middle of a traceback.
130
+ - Reproduce the error before attempting a fix. If you can't reproduce it, you can't verify your fix.
131
+ - Isolate variables: change one thing at a time. Don't make multiple speculative fixes simultaneously.
132
+ - Add targeted logging or print statements to track state at key points. Remove them when done.
133
+ - Address root causes, not symptoms. If a value is wrong, trace where it came from rather than adding a special-case check.
134
+
135
+ ## Error Handling
136
+
137
+ - If you introduce linter errors, fix them if the solution is clear
138
+ - DO NOT loop more than 3 times fixing the same error with the same approach
139
+ - On the third attempt, stop and ask the user what to do
140
+ - If you notice yourself going in circles, stop and ask the user for help
141
+
142
+ ## Formatting & Pre-Commit Hooks
143
+
144
+ - After writing or editing a file, the user's editor or pre-commit hooks may auto-format it (e.g., `black`, `prettier`, `gofmt`). The file on disk may differ from what you wrote.
145
+ - Always re-read a file after editing if you need to make subsequent edits to the same file — don't assume it matches what you last wrote.
146
+
147
+ ## Dependencies
148
+
149
+ - Use the project's package manager to install dependencies — don't manually edit `requirements.txt`, `package.json`, or `Cargo.toml` unless the package manager can't handle the change.
150
+ - The environment context will tell you which package manager the project uses (uv, pip, npm, yarn, cargo, etc.). Use it.
151
+ - Don't mix package managers in the same project.
152
+
153
+ ## Working with Images
154
+
155
+ When a task involves visual content (screenshots, diagrams, UI mockups, charts, plots) and your model supports image input:
156
+
157
+ - Use `read_file(file_path)` to view image files directly — do not use offset/limit parameters for images
158
+ - Read images BEFORE making assumptions about visual content
159
+ - For tasks referencing images: always view them, don't guess from filenames
160
+ - If image input is not available, say so rather than guessing from filenames
161
+
162
+ ## Code References
163
+
164
+ When referencing code, use format: `file_path:line_number`
165
+
166
+ ## Documentation
167
+
168
+ - Do NOT create excessive markdown summary files after completing work
169
+ - Focus on the work itself, not documenting what you did
170
+ - Only create documentation when explicitly requested
171
+
172
+ ---
173
+
174
+ {model_identity_section}{working_dir_section}### Skills Directory
175
+
176
+ Your skills are stored at: `{skills_path}`
177
+ Skills may contain scripts or supporting files. When executing skill scripts with bash, use the real filesystem path:
178
+ Example: `bash python {skills_path}/web-research/script.py`
179
+
180
+ ### Human-in-the-Loop Tool Approval
181
+
182
+ Some tool calls require user approval before execution. When a tool call is rejected by the user:
183
+
184
+ 1. Accept their decision immediately - do NOT retry the same command
185
+ 2. Explain that you understand they rejected the action
186
+ 3. Suggest an alternative approach or ask for clarification
187
+ 4. Never attempt the exact same rejected command again
188
+
189
+ Respect the user's decisions and work with them collaboratively.
190
+
191
+ ### Web Search Tool Usage
192
+
193
+ When you use the web_search tool:
194
+
195
+ 1. The tool will return search results with titles, URLs, and content excerpts
196
+ 2. You MUST read and process these results, then respond naturally to the user
197
+ 3. NEVER show raw JSON or tool results directly to the user
198
+ 4. Synthesize the information from multiple sources into a coherent answer
199
+ 5. Cite your sources by mentioning page titles or URLs when relevant
200
+ 6. If the search doesn't find what you need, explain what you found and ask clarifying questions
201
+
202
+ The user only sees your text responses - not tool results. Always provide a complete, natural language answer after using web_search.
203
+
204
+ {todo_list_section}
@@ -0,0 +1,115 @@
1
+ """Terminal capability detection.
2
+
3
+ Detect optional terminal features without reading from `stdin`.
4
+
5
+ The app only uses kitty-keyboard-protocol support to choose a user-facing
6
+ newline shortcut label. To keep startup safe on remote or high-latency PTYs,
7
+ detection is conservative and relies on side-effect-free terminal identity
8
+ signals plus an explicit environment-variable override.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import os
15
+ import sys
16
+ from functools import cache
17
+ from typing import TYPE_CHECKING
18
+
19
+ from deepagents_code._env_vars import KITTY_KEYBOARD
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Mapping
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ _TRUE_VALUES = frozenset({"1", "true", "yes", "on"})
27
+ _FALSE_VALUES = frozenset({"0", "false", "no", "off"})
28
+ _KNOWN_KITTY_KEYBOARD_TERMS = frozenset({"xterm-ghostty", "xterm-kitty"})
29
+
30
+
31
+ def _override_supports_kitty_keyboard_protocol(
32
+ env: Mapping[str, str],
33
+ ) -> bool | None:
34
+ """Return an explicit kitty-keyboard override from `env`, if present.
35
+
36
+ Accepted truthy values are `'1'`, `'true'`, `'yes'`, and `'on'`.
37
+ Accepted falsy values are `'0'`, `'false'`, `'no'`, and `'off'`.
38
+ `'auto'`, the empty string, and invalid values fall back to heuristic
39
+ detection.
40
+ """
41
+ raw = env.get(KITTY_KEYBOARD)
42
+ if raw is None:
43
+ return None
44
+
45
+ normalized = raw.strip().lower()
46
+ if normalized in {"", "auto"}:
47
+ return None
48
+ if normalized in _TRUE_VALUES:
49
+ return True
50
+ if normalized in _FALSE_VALUES:
51
+ return False
52
+
53
+ logger.warning(
54
+ "%s=%r ignored; expected one of: %s, or 'auto' to defer to detection.",
55
+ KITTY_KEYBOARD,
56
+ raw,
57
+ ", ".join(sorted(_TRUE_VALUES | _FALSE_VALUES)),
58
+ )
59
+ return None
60
+
61
+
62
+ def _terminal_identity_supports_kitty_keyboard_protocol(
63
+ env: Mapping[str, str],
64
+ ) -> bool:
65
+ """Return whether `env` identifies a terminal with built-in kitty support.
66
+
67
+ This intentionally only recognizes terminals whose environment markers
68
+ imply kitty-keyboard support is part of the terminal's default identity.
69
+ Configurable terminals such as iTerm2 and WezTerm are intentionally not
70
+ auto-detected because protocol support can be disabled in user settings.
71
+ """
72
+ if env.get("KITTY_WINDOW_ID"):
73
+ return True
74
+
75
+ term = env.get("TERM", "")
76
+ return term in _KNOWN_KITTY_KEYBOARD_TERMS
77
+
78
+
79
+ @cache
80
+ def supports_kitty_keyboard_protocol() -> bool:
81
+ """Return whether the attached terminal should be treated as kitty-aware.
82
+
83
+ Detection is side-effect free: it never writes escape sequences or reads
84
+ queued input bytes. That means it may under-detect some configurable
85
+ terminals, but it will not interfere with Textual's input stream.
86
+
87
+ Set `DEEPAGENTS_CODE_KITTY_KEYBOARD` to an accepted truthy value (`1`,
88
+ `true`, `yes`, `on`) to force-enable the label, a falsy value (`0`,
89
+ `false`, `no`, `off`) to force-disable it, or `auto`/unset to use
90
+ heuristic detection.
91
+
92
+ Returns:
93
+ `True` when the terminal is known to support the kitty keyboard
94
+ protocol, `False` otherwise.
95
+ """
96
+ if sys.platform == "win32":
97
+ logger.debug("kitty kbd detection: False (win32 unsupported)")
98
+ return False
99
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
100
+ logger.debug("kitty kbd detection: False (stdin/stdout not a tty)")
101
+ return False
102
+
103
+ override = _override_supports_kitty_keyboard_protocol(os.environ)
104
+ if override is not None:
105
+ logger.debug("kitty kbd detection: %s (explicit override)", override)
106
+ return override
107
+
108
+ detected = _terminal_identity_supports_kitty_keyboard_protocol(os.environ)
109
+ logger.debug(
110
+ "kitty kbd detection: %s (terminal identity TERM=%r KITTY_WINDOW_ID=%r)",
111
+ detected,
112
+ os.environ.get("TERM", ""),
113
+ os.environ.get("KITTY_WINDOW_ID"),
114
+ )
115
+ return detected