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,329 @@
1
+ """Session-end summary writer.
2
+
3
+ Records why a dcode session ended (`completed` / `interrupted` / `error`) and
4
+ how long it ran, then emits the result to three sinks on shutdown:
5
+
6
+ 1. `stderr` — a human-readable panel printed after all UI teardown.
7
+ 2. `~/.deepagents/.state/session_end/<thread_id>.txt` — one file per session
8
+ (last-write-wins) for post-mortem inspection.
9
+ 3. `~/.deepagents/session_end_log.jsonl` — one JSON line appended per session
10
+ for audit/aggregation.
11
+
12
+ Design constraints:
13
+ - **Minimal-invasive**: this module is self-contained. It hooks into shutdown
14
+ via `atexit` + `sys.excepthook`, so mainline code touches only
15
+ `install(...)`, `set_thread_id(...)`, `mark_reason(...)`, and
16
+ `mark_signal(...)` — small entry points that don't collide with upstream
17
+ evolution of `app.py` or `client/non_interactive.py`.
18
+ - **Idempotent**: `_finalize()` may fire multiple times; a lock + `_finalized`
19
+ flag makes only the first invocation write anything.
20
+ - **Never raises**: any I/O or formatting failure is logged at debug level and
21
+ swallowed. Failing to write the summary must never mask the real exit code
22
+ or crash the shutdown path.
23
+ - **Zero heavy imports at import time**: pulls `format_duration` lazily inside
24
+ `_finalize` so this module stays cheap for the `-v` fast path.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import atexit
30
+ import json
31
+ import logging
32
+ import os
33
+ import signal
34
+ import sys
35
+ import threading
36
+ import time
37
+ from pathlib import Path
38
+ from typing import TYPE_CHECKING
39
+
40
+ if TYPE_CHECKING:
41
+ from types import TracebackType
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ REASON_COMPLETED = "completed"
47
+ """Normal exit — `/exit`, Ctrl-D, end of script, `sys.exit(0)`, etc."""
48
+
49
+ REASON_INTERRUPTED = "interrupted"
50
+ """User or OS terminated the process — Ctrl-C, SIGTERM, SIGHUP, SIGQUIT."""
51
+
52
+ REASON_ERROR = "error"
53
+ """An unhandled exception propagated out of the program."""
54
+
55
+
56
+ _state_lock = threading.Lock()
57
+ """Guards all mutable module-level state so mark/finalize races are safe."""
58
+
59
+ _installed = False
60
+ """True after `install()` has run so double-install is a no-op."""
61
+
62
+ _finalized = False
63
+ """True after `_finalize()` has emitted output so re-entry is a no-op."""
64
+
65
+ _start_monotonic: float | None = None
66
+ """`time.monotonic()` at install time; used to compute wall-clock duration."""
67
+
68
+ _thread_id: str = ""
69
+ """Session/thread id, populated by `set_thread_id` when known."""
70
+
71
+ _reason: str = REASON_COMPLETED
72
+ """Current best guess at the exit reason; upgraded by `mark_reason`."""
73
+
74
+ _reason_detail: str = ""
75
+ """Optional short human-readable detail (e.g. `SIGINT`, `RuntimeError: foo`)."""
76
+
77
+
78
+ # Upgrade order — higher wins. This lets late-arriving signals overwrite an
79
+ # earlier `completed`, but a first-seen `error` isn't clobbered by a later
80
+ # atexit-triggered `completed`.
81
+ _REASON_PRIORITY = {
82
+ REASON_COMPLETED: 0,
83
+ REASON_INTERRUPTED: 1,
84
+ REASON_ERROR: 2,
85
+ }
86
+
87
+
88
+ _SIGNAL_NAMES: dict[int, str] = {}
89
+ for _name in ("SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"):
90
+ _sig = getattr(signal, _name, None)
91
+ if _sig is not None:
92
+ _SIGNAL_NAMES[int(_sig)] = _name
93
+
94
+
95
+ def install(*, thread_id: str = "") -> None:
96
+ """Register shutdown hooks and record the session start time.
97
+
98
+ Safe to call more than once; subsequent calls are no-ops except that a
99
+ non-empty `thread_id` will fill an empty slot.
100
+
101
+ Args:
102
+ thread_id: Best-known session/thread identifier at install time. May
103
+ be updated later via `set_thread_id` once the client learns the
104
+ real id.
105
+ """
106
+ global _installed, _start_monotonic
107
+
108
+ with _state_lock:
109
+ if not _installed:
110
+ _start_monotonic = time.monotonic()
111
+ _installed = True
112
+ # atexit fires for normal termination *and* for `sys.exit()`, and
113
+ # runs after Textual/Rich have torn down, so our stderr print is
114
+ # not swallowed by an alt-screen. It does NOT fire for `os._exit`,
115
+ # `kill -9`, or a fatal signal that bypasses Python — none of
116
+ # which can be caught anywhere in userspace anyway.
117
+ atexit.register(_finalize)
118
+ _install_excepthook()
119
+
120
+ if thread_id and not _thread_id:
121
+ _set_thread_id_locked(thread_id)
122
+
123
+
124
+ def _install_excepthook() -> None:
125
+ """Chain a classification wrapper on top of the existing `sys.excepthook`."""
126
+ prev_hook = sys.excepthook
127
+
128
+ def _hook(
129
+ exc_type: type[BaseException],
130
+ exc: BaseException,
131
+ tb: TracebackType | None,
132
+ ) -> None:
133
+ # KeyboardInterrupt reaches excepthook when it escapes the main frame;
134
+ # classify it as `interrupted`, not `error`.
135
+ if issubclass(exc_type, KeyboardInterrupt):
136
+ mark_reason(REASON_INTERRUPTED, "KeyboardInterrupt")
137
+ elif issubclass(exc_type, SystemExit):
138
+ # SystemExit is a normal exit path; don't upgrade to error.
139
+ pass
140
+ else:
141
+ detail = f"{exc_type.__name__}: {exc}".strip()
142
+ if len(detail) > 500: # noqa: PLR2004
143
+ detail = detail[:497] + "..."
144
+ mark_reason(REASON_ERROR, detail)
145
+ prev_hook(exc_type, exc, tb)
146
+
147
+ sys.excepthook = _hook
148
+
149
+
150
+ def set_thread_id(thread_id: str) -> None:
151
+ """Record the session/thread id once the client learns it."""
152
+ if not thread_id:
153
+ return
154
+ with _state_lock:
155
+ _set_thread_id_locked(thread_id)
156
+
157
+
158
+ def _set_thread_id_locked(thread_id: str) -> None:
159
+ """Thread-id setter that assumes `_state_lock` is held."""
160
+ global _thread_id
161
+ _thread_id = thread_id
162
+
163
+
164
+ def mark_reason(reason: str, detail: str = "") -> None:
165
+ """Upgrade the recorded exit reason if `reason` has higher priority.
166
+
167
+ Args:
168
+ reason: One of `REASON_COMPLETED` / `REASON_INTERRUPTED` /
169
+ `REASON_ERROR`. Unknown values are ignored.
170
+ detail: Short human-readable qualifier (e.g. `"SIGINT"`,
171
+ `"RuntimeError: foo"`).
172
+ """
173
+ if reason not in _REASON_PRIORITY:
174
+ return
175
+ global _reason, _reason_detail
176
+ with _state_lock:
177
+ if _REASON_PRIORITY[reason] >= _REASON_PRIORITY[_reason]:
178
+ _reason = reason
179
+ if detail:
180
+ _reason_detail = detail
181
+
182
+
183
+ def mark_signal(signum: int) -> None:
184
+ """Record that a terminating signal was received.
185
+
186
+ Called from `main.py`'s signal handler right before it raises
187
+ `SystemExit`. `SystemExit` itself won't route through `sys.excepthook`, so
188
+ this is the only chance to attach a signal-derived detail.
189
+ """
190
+ name = _SIGNAL_NAMES.get(signum, f"signal {signum}")
191
+ mark_reason(REASON_INTERRUPTED, name)
192
+
193
+
194
+ def _summary_dir() -> Path:
195
+ """`~/.deepagents/.state/session_end/`, created lazily and idempotently."""
196
+ from deepagents_code.model_config import DEFAULT_STATE_DIR
197
+
198
+ path = DEFAULT_STATE_DIR / "session_end"
199
+ path.mkdir(parents=True, exist_ok=True)
200
+ return path
201
+
202
+
203
+ def _log_path() -> Path:
204
+ """`~/.deepagents/session_end_log.jsonl`, parent created if missing."""
205
+ from deepagents_code.model_config import DEFAULT_CONFIG_DIR
206
+
207
+ DEFAULT_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
208
+ return DEFAULT_CONFIG_DIR / "session_end_log.jsonl"
209
+
210
+
211
+ def _format_duration(seconds: float) -> str:
212
+ """Format a duration; try `formatting.format_duration`, fall back locally."""
213
+ try:
214
+ from deepagents_code.formatting import format_duration
215
+
216
+ return format_duration(seconds)
217
+ except Exception:
218
+ total = int(seconds)
219
+ h, rem = divmod(total, 3600)
220
+ m, s = divmod(rem, 60)
221
+ if h:
222
+ return f"{h}h{m:02d}m{s:02d}s"
223
+ if m:
224
+ return f"{m}m{s:02d}s"
225
+ return f"{s}s"
226
+
227
+
228
+ def _sanitize_filename(name: str) -> str:
229
+ """Strip path separators and other unsafe characters from a thread id."""
230
+ safe = "".join(c if c.isalnum() or c in "-_." else "_" for c in name)
231
+ return safe or "unknown"
232
+
233
+
234
+ def _print_stderr_panel(
235
+ reason_line: str,
236
+ duration_pretty: str,
237
+ thread_id: str,
238
+ ) -> None:
239
+ """Print the human-facing summary to stderr as a plain-text panel."""
240
+ log_hint = "~/.deepagents/session_end_log.jsonl"
241
+ lines = [
242
+ "",
243
+ "─────────── Session ended ───────────",
244
+ f"Reason: {reason_line}",
245
+ f"Duration: {duration_pretty}",
246
+ f"Thread: {thread_id}",
247
+ f"Log: {log_hint}",
248
+ "──────────────────────────────────────",
249
+ "",
250
+ ]
251
+ sys.stderr.write("\n".join(lines))
252
+ sys.stderr.flush()
253
+
254
+
255
+ def _finalize() -> None:
256
+ """Emit the session-end summary to all three sinks. Idempotent."""
257
+ global _finalized
258
+
259
+ with _state_lock:
260
+ if _finalized or not _installed:
261
+ return
262
+ _finalized = True
263
+ reason = _reason
264
+ detail = _reason_detail
265
+ thread_id = _thread_id or "<unknown>"
266
+ started = _start_monotonic
267
+
268
+ duration = time.monotonic() - started if started is not None else 0.0
269
+ duration_pretty = _format_duration(duration)
270
+ reason_line = reason if not detail else f"{reason} ({detail})"
271
+
272
+ try:
273
+ _print_stderr_panel(reason_line, duration_pretty, thread_id)
274
+ except Exception:
275
+ logger.debug("session_end_summary stderr print failed", exc_info=True)
276
+
277
+ try:
278
+ safe_name = _sanitize_filename(thread_id)
279
+ text_path = _summary_dir() / f"{safe_name}.txt"
280
+ text_path.write_text(
281
+ "Session ended\n"
282
+ f"Reason: {reason_line}\n"
283
+ f"Duration: {duration_pretty} ({duration:.3f}s)\n"
284
+ f"Thread: {thread_id}\n"
285
+ f"PID: {os.getpid()}\n",
286
+ encoding="utf-8",
287
+ )
288
+ except Exception:
289
+ logger.debug("session_end_summary per-thread write failed", exc_info=True)
290
+
291
+ try:
292
+ record = {
293
+ "ts": time.time(),
294
+ "thread_id": thread_id,
295
+ "pid": os.getpid(),
296
+ "reason": reason,
297
+ "reason_detail": detail,
298
+ "duration_seconds": round(duration, 3),
299
+ "duration_pretty": duration_pretty,
300
+ }
301
+ # `a` + `write(line + "\n")` in a single call is atomic on POSIX for
302
+ # writes below PIPE_BUF; the JSON line is well under that limit.
303
+ with _log_path().open("a", encoding="utf-8") as fp:
304
+ fp.write(json.dumps(record, ensure_ascii=False) + "\n")
305
+ except Exception:
306
+ logger.debug("session_end_summary JSONL append failed", exc_info=True)
307
+
308
+
309
+ def _reset_for_tests() -> None:
310
+ """Reset all module state. Test-only; not part of the public API.
311
+
312
+ Also unregisters the `atexit` handler installed by `install()` so pytest
313
+ exit does not print a stray summary panel from a prior test's install.
314
+ Tests that want the hooks re-armed should call `install()` again on a
315
+ fresh clock.
316
+ """
317
+ global _installed, _finalized, _start_monotonic, _thread_id, _reason
318
+ global _reason_detail
319
+ try:
320
+ atexit.unregister(_finalize)
321
+ except Exception:
322
+ pass
323
+ with _state_lock:
324
+ _installed = False
325
+ _finalized = False
326
+ _start_monotonic = None
327
+ _thread_id = ""
328
+ _reason = REASON_COMPLETED
329
+ _reason_detail = ""