deepagents-code 0.1.87__py3-none-any.whl → 0.2.0__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.
- deepagents_code/_ask_user_types.py +1 -1
- deepagents_code/_build_info.py +1 -1
- deepagents_code/_dep_floor_check.py +3 -3
- deepagents_code/_git.py +12 -6
- deepagents_code/_repository_bounds.py +1 -1
- deepagents_code/_session_stats.py +1 -1
- deepagents_code/_tool_stream.py +1 -1
- deepagents_code/_version.py +1 -1
- deepagents_code/agent.py +3 -0
- deepagents_code/app.py +111 -37
- deepagents_code/approval_mode.py +1 -1
- deepagents_code/auto_mode.py +4 -4
- deepagents_code/btw.py +2 -2
- deepagents_code/btw_api.py +2 -2
- deepagents_code/built_in_skills/deepagents-thread-inspector/scripts/inspect_sessions.py +3 -11
- deepagents_code/client/launch/server.py +1 -1
- deepagents_code/client/launch/server_manager.py +1 -1
- deepagents_code/client/non_interactive.py +1 -1
- deepagents_code/client/remote_client.py +3 -3
- deepagents_code/config.py +7 -7
- deepagents_code/config_manifest.py +11 -7
- deepagents_code/configurable_model.py +1 -1
- deepagents_code/configuration/paths.py +4 -1
- deepagents_code/configuration/providers.py +3 -3
- deepagents_code/configuration/resolver.py +1 -1
- deepagents_code/cost_tracking.py +1 -1
- deepagents_code/doctor.py +1 -1
- deepagents_code/event_bus.py +1 -1
- deepagents_code/extensions/discovery.py +1 -1
- deepagents_code/extensions/loader.py +1 -1
- deepagents_code/extensions/runtime.py +1 -1
- deepagents_code/extras_info.py +12 -12
- deepagents_code/file_ops.py +3 -3
- deepagents_code/formatting.py +1 -1
- deepagents_code/goal_rubric.py +1 -1
- deepagents_code/hooks/migration.py +2 -2
- deepagents_code/hooks/runner.py +2 -2
- deepagents_code/hooks/transcript.py +2 -2
- deepagents_code/hooks/trust.py +3 -3
- deepagents_code/input.py +7 -5
- deepagents_code/integrations/sandbox_factory.py +2 -2
- deepagents_code/iterm_cursor_guide.py +1 -1
- deepagents_code/main.py +153 -84
- deepagents_code/managed_tools.py +1 -1
- deepagents_code/mcp_auth.py +1 -1
- deepagents_code/mcp_tools.py +2 -2
- deepagents_code/media_utils.py +3 -2
- deepagents_code/memory_guard.py +4 -4
- deepagents_code/model_api.py +3 -3
- deepagents_code/model_config.py +23 -23
- deepagents_code/model_retry.py +2 -2
- deepagents_code/offload.py +5 -5
- deepagents_code/offload_middleware.py +1 -1
- deepagents_code/plugins/adapters/mcp.py +1 -1
- deepagents_code/plugins/adapters/skills.py +1 -1
- deepagents_code/plugins/adapters/skills_middleware.py +2 -2
- deepagents_code/plugins/manifest.py +1 -1
- deepagents_code/plugins/marketplace.py +4 -0
- deepagents_code/sessions.py +4 -4
- deepagents_code/skills/commands.py +1 -1
- deepagents_code/skills/load.py +1 -1
- deepagents_code/skills/trust.py +4 -4
- deepagents_code/terminal_escape.py +1 -1
- deepagents_code/terminal_title.py +1 -1
- deepagents_code/theme.py +1 -1
- deepagents_code/tool_catalog.py +2 -2
- deepagents_code/tool_display.py +6 -4
- deepagents_code/tui/key_hints.py +4 -1
- deepagents_code/tui/modals/plugin_manager/content.py +7 -2
- deepagents_code/tui/modals/plugin_manager/models.py +3 -2
- deepagents_code/tui/modals/plugin_manager/state.py +1 -1
- deepagents_code/tui/textual_adapter.py +2 -2
- deepagents_code/tui/widgets/_links.py +2 -2
- deepagents_code/tui/widgets/autocomplete.py +1 -1
- deepagents_code/tui/widgets/debug_console.py +1 -1
- deepagents_code/tui/widgets/history.py +1 -1
- deepagents_code/tui/widgets/messages.py +5 -5
- deepagents_code/tui/widgets/model_selector.py +2 -2
- deepagents_code/tui/widgets/startup_tip.py +1 -6
- deepagents_code/tui/widgets/status.py +1 -1
- deepagents_code/tui/widgets/subagent_panel.py +6 -6
- deepagents_code/tui/widgets/thread_selector.py +3 -3
- deepagents_code/tui/widgets/tool_widgets.py +1 -1
- deepagents_code/tui/widgets/welcome.py +2 -2
- deepagents_code/update_check.py +642 -158
- {deepagents_code-0.1.87.dist-info → deepagents_code-0.2.0.dist-info}/METADATA +30 -32
- {deepagents_code-0.1.87.dist-info → deepagents_code-0.2.0.dist-info}/RECORD +91 -91
- {deepagents_code-0.1.87.data → deepagents_code-0.2.0.data}/data/deepagents_code/default_agent_prompt.md +0 -0
- {deepagents_code-0.1.87.dist-info → deepagents_code-0.2.0.dist-info}/WHEEL +0 -0
- {deepagents_code-0.1.87.dist-info → deepagents_code-0.2.0.dist-info}/entry_points.txt +0 -0
- {deepagents_code-0.1.87.dist-info → deepagents_code-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -109,7 +109,7 @@ def decode_multi_select_answer(raw: str) -> list[str] | None:
|
|
|
109
109
|
"""
|
|
110
110
|
try:
|
|
111
111
|
decoded = json.loads(raw)
|
|
112
|
-
except
|
|
112
|
+
except json.JSONDecodeError, RecursionError:
|
|
113
113
|
return None
|
|
114
114
|
if not isinstance(decoded, list) or not all(
|
|
115
115
|
isinstance(value, str) for value in decoded
|
deepagents_code/_build_info.py
CHANGED
|
@@ -96,7 +96,7 @@ def _load_cli_requirements() -> list[str] | None:
|
|
|
96
96
|
try:
|
|
97
97
|
data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
|
|
98
98
|
entries = data["project"]["dependencies"]
|
|
99
|
-
except
|
|
99
|
+
except OSError, KeyError, TypeError, tomllib.TOMLDecodeError:
|
|
100
100
|
logger.debug(
|
|
101
101
|
"Could not read dependency floors from the source checkout",
|
|
102
102
|
exc_info=True,
|
|
@@ -330,7 +330,7 @@ def _workspace_editable_paths() -> list[Path]:
|
|
|
330
330
|
try:
|
|
331
331
|
data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
|
|
332
332
|
sources = data["tool"]["uv"]["sources"]
|
|
333
|
-
except
|
|
333
|
+
except OSError, KeyError, TypeError, tomllib.TOMLDecodeError:
|
|
334
334
|
logger.debug(
|
|
335
335
|
"Could not read workspace sources from the source checkout",
|
|
336
336
|
exc_info=True,
|
|
@@ -565,7 +565,7 @@ def _read_dismissed_fingerprints(path: Path) -> dict[str, str]:
|
|
|
565
565
|
"""
|
|
566
566
|
try:
|
|
567
567
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
568
|
-
except
|
|
568
|
+
except OSError, ValueError:
|
|
569
569
|
return {}
|
|
570
570
|
if not isinstance(data, dict):
|
|
571
571
|
return {}
|
deepagents_code/_git.py
CHANGED
|
@@ -173,7 +173,7 @@ def _read_single_git_metadata_line(path: Path) -> str | None:
|
|
|
173
173
|
return None
|
|
174
174
|
try:
|
|
175
175
|
raw = path.read_text(encoding="utf-8")
|
|
176
|
-
except
|
|
176
|
+
except OSError, UnicodeError:
|
|
177
177
|
logger.debug("Failed to read Git metadata from %s", path, exc_info=True)
|
|
178
178
|
return None
|
|
179
179
|
|
|
@@ -204,7 +204,7 @@ def _resolve_git_metadata_path(path: Path, *, relative_to: Path) -> Path | None:
|
|
|
204
204
|
candidate = relative_to / candidate
|
|
205
205
|
try:
|
|
206
206
|
return candidate.resolve(strict=True)
|
|
207
|
-
except
|
|
207
|
+
except OSError, RuntimeError, ValueError:
|
|
208
208
|
logger.debug("Failed to resolve Git metadata path from %s", path, exc_info=True)
|
|
209
209
|
return None
|
|
210
210
|
|
|
@@ -227,7 +227,7 @@ def _normalize_git_metadata_path(path: Path, *, relative_to: Path) -> Path | Non
|
|
|
227
227
|
candidate = relative_to / candidate
|
|
228
228
|
try:
|
|
229
229
|
return Path(os.path.abspath(candidate)) # noqa: PTH100 # do not follow links
|
|
230
|
-
except
|
|
230
|
+
except OSError, RuntimeError, ValueError:
|
|
231
231
|
logger.debug(
|
|
232
232
|
"Failed to normalize Git metadata path from %s", path, exc_info=True
|
|
233
233
|
)
|
|
@@ -254,7 +254,7 @@ def _parse_trusted_git_dir_pointer(git_entry: Path) -> Path | None:
|
|
|
254
254
|
candidate = git_entry.parent / candidate
|
|
255
255
|
try:
|
|
256
256
|
git_dir = candidate.resolve(strict=True)
|
|
257
|
-
except
|
|
257
|
+
except OSError, RuntimeError, ValueError:
|
|
258
258
|
logger.debug(
|
|
259
259
|
"Failed to resolve trusted gitdir pointer %s", git_entry, exc_info=True
|
|
260
260
|
)
|
|
@@ -328,7 +328,7 @@ def find_git_common_dir(path: str | Path) -> Path | None:
|
|
|
328
328
|
backlink = _normalize_git_metadata_path(git_dir / "gitdir", relative_to=git_dir)
|
|
329
329
|
if backlink is None or backlink != git_entry:
|
|
330
330
|
return None
|
|
331
|
-
except
|
|
331
|
+
except OSError, RuntimeError, ValueError:
|
|
332
332
|
logger.debug(
|
|
333
333
|
"Failed to validate Git common directory for %s", path, exc_info=True
|
|
334
334
|
)
|
|
@@ -377,7 +377,7 @@ def _submodule_git_dir(root: Path, parent_config_dir: Path) -> Path | None:
|
|
|
377
377
|
if not candidate.is_absolute():
|
|
378
378
|
candidate = git_dir / candidate
|
|
379
379
|
return git_dir if candidate.resolve(strict=True) == root else None
|
|
380
|
-
except
|
|
380
|
+
except OSError, RuntimeError, ValueError:
|
|
381
381
|
logger.debug("Failed to validate submodule Git metadata")
|
|
382
382
|
return None
|
|
383
383
|
|
|
@@ -451,6 +451,9 @@ def read_git_branch_via_subprocess(path: str | Path) -> str:
|
|
|
451
451
|
["git", "rev-parse", "--abbrev-ref", "HEAD"], # noqa: S607
|
|
452
452
|
capture_output=True,
|
|
453
453
|
text=True,
|
|
454
|
+
# Branch names are UTF-8, not in the Windows ANSI code page.
|
|
455
|
+
encoding="utf-8",
|
|
456
|
+
errors="replace",
|
|
454
457
|
timeout=2,
|
|
455
458
|
check=False,
|
|
456
459
|
cwd=path,
|
|
@@ -653,6 +656,9 @@ def read_git_remote_url_via_subprocess(path: str | Path) -> str:
|
|
|
653
656
|
["git", "config", "--get", "remote.origin.url"], # noqa: S607
|
|
654
657
|
capture_output=True,
|
|
655
658
|
text=True,
|
|
659
|
+
# Local-path remotes are UTF-8, not in the Windows ANSI code page.
|
|
660
|
+
encoding="utf-8",
|
|
661
|
+
errors="replace",
|
|
656
662
|
timeout=2,
|
|
657
663
|
check=False,
|
|
658
664
|
cwd=path,
|
|
@@ -432,7 +432,7 @@ class RepositoryBounds:
|
|
|
432
432
|
body, separator, notes = content.partition("\n\n")
|
|
433
433
|
try:
|
|
434
434
|
paths = ast.literal_eval(body)
|
|
435
|
-
except
|
|
435
|
+
except SyntaxError, ValueError:
|
|
436
436
|
return content
|
|
437
437
|
if not isinstance(paths, list) or not all(
|
|
438
438
|
isinstance(path, str) for path in paths
|
|
@@ -1311,7 +1311,7 @@ def record_model_usage_event(
|
|
|
1311
1311
|
"model_provider": provider,
|
|
1312
1312
|
},
|
|
1313
1313
|
)
|
|
1314
|
-
except
|
|
1314
|
+
except TypeError, ValueError:
|
|
1315
1315
|
logger.debug("Rejected malformed nested model usage", exc_info=True)
|
|
1316
1316
|
return None
|
|
1317
1317
|
return record_message_usage(
|
deepagents_code/_tool_stream.py
CHANGED
|
@@ -711,7 +711,7 @@ class ToolCallBuffer:
|
|
|
711
711
|
joined = self._materialize_args()
|
|
712
712
|
try:
|
|
713
713
|
parsed = json.loads(joined)
|
|
714
|
-
except
|
|
714
|
+
except json.JSONDecodeError, RecursionError:
|
|
715
715
|
# `RecursionError` is caught alongside the decode error because
|
|
716
716
|
# pathologically nested model output can make `json.loads` exceed
|
|
717
717
|
# the interpreter recursion limit, and that is one malformed call to
|
deepagents_code/_version.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Keep the `x-release-please-version` annotation — release-please uses it to
|
|
4
4
|
# bump `__version__` in sync with `pyproject.toml` on every release PR.
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.2.0" # x-release-please-version
|
|
6
6
|
|
|
7
7
|
DOCS_URL = "https://docs.langchain.com/oss/python/deepagents/code"
|
|
8
8
|
"""URL for `deepagents-code` documentation."""
|
deepagents_code/agent.py
CHANGED
|
@@ -1160,6 +1160,7 @@ def load_async_subagents(config_path: Path | None = None) -> list[AsyncSubAgent]
|
|
|
1160
1160
|
description = "Research agent"
|
|
1161
1161
|
url = "https://my-deployment.langsmith.dev"
|
|
1162
1162
|
graph_id = "agent"
|
|
1163
|
+
webhook = "https://example.com/webhooks/subagent-finished"
|
|
1163
1164
|
```
|
|
1164
1165
|
|
|
1165
1166
|
Args:
|
|
@@ -1225,6 +1226,8 @@ def load_async_subagents(config_path: Path | None = None) -> list[AsyncSubAgent]
|
|
|
1225
1226
|
}
|
|
1226
1227
|
if "url" in spec and isinstance(spec["url"], str):
|
|
1227
1228
|
agent["url"] = spec["url"]
|
|
1229
|
+
if "webhook" in spec and isinstance(spec["webhook"], str):
|
|
1230
|
+
agent["webhook"] = spec["webhook"]
|
|
1228
1231
|
if "headers" in spec and isinstance(spec["headers"], dict):
|
|
1229
1232
|
agent["headers"] = spec["headers"]
|
|
1230
1233
|
agents.append(agent)
|
deepagents_code/app.py
CHANGED
|
@@ -514,7 +514,7 @@ def _coerce_session_cost_usd(value: object) -> float:
|
|
|
514
514
|
return 0.0
|
|
515
515
|
try:
|
|
516
516
|
cost_usd = float(value)
|
|
517
|
-
except
|
|
517
|
+
except OverflowError, ValueError:
|
|
518
518
|
logger.warning("Discarding a persisted session cost that is out of range.")
|
|
519
519
|
return 0.0
|
|
520
520
|
if not math.isfinite(cost_usd) or cost_usd < 0:
|
|
@@ -558,7 +558,7 @@ def _coerce_provisional_cost_delta_usd(value: object) -> float | None:
|
|
|
558
558
|
return None
|
|
559
559
|
try:
|
|
560
560
|
delta_usd = float(value)
|
|
561
|
-
except
|
|
561
|
+
except OverflowError, ValueError:
|
|
562
562
|
logger.warning("Discarding a provisional cost delta that is out of range.")
|
|
563
563
|
return None
|
|
564
564
|
if not math.isfinite(delta_usd):
|
|
@@ -5642,7 +5642,7 @@ class DeepAgentsApp(App):
|
|
|
5642
5642
|
root = await asyncio.to_thread(project_root_for, Path(self._cwd))
|
|
5643
5643
|
config_path = project_hooks_path(root)
|
|
5644
5644
|
has_project_hooks = await asyncio.to_thread(config_path.is_file)
|
|
5645
|
-
except
|
|
5645
|
+
except OSError, ValueError:
|
|
5646
5646
|
logger.warning(
|
|
5647
5647
|
"Could not inspect project hooks after cwd switch",
|
|
5648
5648
|
exc_info=True,
|
|
@@ -6687,7 +6687,7 @@ class DeepAgentsApp(App):
|
|
|
6687
6687
|
|
|
6688
6688
|
try:
|
|
6689
6689
|
messages = self.query_one("#messages", Container)
|
|
6690
|
-
except
|
|
6690
|
+
except NoMatches, ScreenStackError:
|
|
6691
6691
|
return
|
|
6692
6692
|
if not messages.is_attached:
|
|
6693
6693
|
return
|
|
@@ -7692,17 +7692,30 @@ class DeepAgentsApp(App):
|
|
|
7692
7692
|
await self._mount_message(
|
|
7693
7693
|
AppMessage(f"Update log: {format_log_follow_command(log_path)}"),
|
|
7694
7694
|
)
|
|
7695
|
+
|
|
7696
|
+
notices: list[str] = []
|
|
7697
|
+
deferred: list[bool] = []
|
|
7698
|
+
unverified: list[bool] = []
|
|
7699
|
+
|
|
7700
|
+
async def show_upgrade_notice(message: str) -> None:
|
|
7701
|
+
notices.append(message)
|
|
7702
|
+
await self._mount_message(AppMessage(message))
|
|
7703
|
+
|
|
7704
|
+
# A Python migration rebuilds the environment this session imports
|
|
7705
|
+
# from, so the TUI defers it to `dcode update` from the shell.
|
|
7695
7706
|
success, output, installed = await perform_upgrade(
|
|
7707
|
+
on_notice=show_upgrade_notice,
|
|
7696
7708
|
log_path=log_path,
|
|
7697
7709
|
include_prereleases=include_prereleases,
|
|
7698
7710
|
target_version=latest,
|
|
7711
|
+
allow_python_migration=False,
|
|
7712
|
+
on_migration_deferred=lambda: deferred.append(True),
|
|
7713
|
+
on_unverified_install=lambda: unverified.append(True),
|
|
7699
7714
|
)
|
|
7700
7715
|
if success:
|
|
7701
7716
|
self._update_available = (False, None)
|
|
7702
|
-
#
|
|
7703
|
-
#
|
|
7704
|
-
# (a release published while the session sat open). Report what
|
|
7705
|
-
# actually landed on disk rather than the stale check result.
|
|
7717
|
+
# uv installs are pinned and verified, and other methods report
|
|
7718
|
+
# the target, so this matches `latest`; the fallback is defensive.
|
|
7706
7719
|
upgraded_to = installed or latest
|
|
7707
7720
|
# uv may have installed the upgraded shim into a directory that
|
|
7708
7721
|
# isn't first on the user's PATH (e.g. a leftover pre-uv
|
|
@@ -7729,6 +7742,24 @@ class DeepAgentsApp(App):
|
|
|
7729
7742
|
await self._mount_message(
|
|
7730
7743
|
ErrorMessage(format_shadowed_dcode_warning(shadow)),
|
|
7731
7744
|
)
|
|
7745
|
+
elif unverified:
|
|
7746
|
+
# This session lazily imports modules that uv may have replaced.
|
|
7747
|
+
await self._mount_message(
|
|
7748
|
+
ErrorMessage(
|
|
7749
|
+
"Update could not be verified; see above. Quit and relaunch "
|
|
7750
|
+
"dcode before continuing."
|
|
7751
|
+
),
|
|
7752
|
+
)
|
|
7753
|
+
elif deferred:
|
|
7754
|
+
# Nothing ran, so there is no failure or log to point at.
|
|
7755
|
+
await self._mount_message(AppMessage("Update deferred; see above."))
|
|
7756
|
+
elif notices:
|
|
7757
|
+
# The notice already carries its own recovery steps; a generic
|
|
7758
|
+
# command here would contradict them.
|
|
7759
|
+
log_line = f"\nLog: {log_path}" if log_path is not None else ""
|
|
7760
|
+
await self._mount_message(
|
|
7761
|
+
AppMessage(f"Update failed; see the message above.{log_line}"),
|
|
7762
|
+
)
|
|
7732
7763
|
else:
|
|
7733
7764
|
cmd = upgrade_command(
|
|
7734
7765
|
include_prereleases=upgrade_include_prereleases,
|
|
@@ -8850,7 +8881,7 @@ class DeepAgentsApp(App):
|
|
|
8850
8881
|
# composes its own rack, and modals must not retune the main one.
|
|
8851
8882
|
screen = chrome.screen
|
|
8852
8883
|
rack = screen.get_child_by_id("textual-toastrack")
|
|
8853
|
-
except
|
|
8884
|
+
except NoMatches, NoScreen:
|
|
8854
8885
|
return
|
|
8855
8886
|
region = chrome.region
|
|
8856
8887
|
if not region:
|
|
@@ -10826,7 +10857,7 @@ class DeepAgentsApp(App):
|
|
|
10826
10857
|
"""
|
|
10827
10858
|
try:
|
|
10828
10859
|
messages = self.query_one("#messages", Container)
|
|
10829
|
-
except
|
|
10860
|
+
except NoMatches, ScreenStackError:
|
|
10830
10861
|
logger.debug(
|
|
10831
10862
|
"Messages container unavailable; skipping transient status %r",
|
|
10832
10863
|
content,
|
|
@@ -11474,7 +11505,7 @@ class DeepAgentsApp(App):
|
|
|
11474
11505
|
"""
|
|
11475
11506
|
try:
|
|
11476
11507
|
await widget.remove()
|
|
11477
|
-
except
|
|
11508
|
+
except AttributeError, RuntimeError:
|
|
11478
11509
|
logger.debug(
|
|
11479
11510
|
"Failed to remove %s widget during %s",
|
|
11480
11511
|
prompt_name,
|
|
@@ -14027,7 +14058,7 @@ class DeepAgentsApp(App):
|
|
|
14027
14058
|
proc.kill()
|
|
14028
14059
|
with suppress(ProcessLookupError, OSError):
|
|
14029
14060
|
await proc.wait()
|
|
14030
|
-
except
|
|
14061
|
+
except ProcessLookupError, OSError:
|
|
14031
14062
|
pass
|
|
14032
14063
|
|
|
14033
14064
|
async def _open_url_command(self, command: str, cmd: str) -> None:
|
|
@@ -18253,7 +18284,7 @@ class DeepAgentsApp(App):
|
|
|
18253
18284
|
|
|
18254
18285
|
clear_caches()
|
|
18255
18286
|
self._sync_status_model()
|
|
18256
|
-
except
|
|
18287
|
+
except OSError, ValueError:
|
|
18257
18288
|
logger.exception("Failed to reload configuration")
|
|
18258
18289
|
await self._mount_message(
|
|
18259
18290
|
AppMessage(
|
|
@@ -18614,7 +18645,9 @@ class DeepAgentsApp(App):
|
|
|
18614
18645
|
_, conversation = await self._get_context_usage_counts()
|
|
18615
18646
|
return conversation
|
|
18616
18647
|
|
|
18617
|
-
async def _handle_server_offload(
|
|
18648
|
+
async def _handle_server_offload(
|
|
18649
|
+
self, config: RunnableConfig, *, automatic: bool = False
|
|
18650
|
+
) -> None:
|
|
18618
18651
|
"""Request and render the built-in server-owned offload operation."""
|
|
18619
18652
|
from deepagents_code.hooks.client_lifecycle import ClientHookStopError
|
|
18620
18653
|
|
|
@@ -18641,6 +18674,8 @@ class DeepAgentsApp(App):
|
|
|
18641
18674
|
await self._sync_session_cost_from_checkpoint()
|
|
18642
18675
|
|
|
18643
18676
|
status = result.get("status")
|
|
18677
|
+
if automatic and status in {"empty", "noop"}:
|
|
18678
|
+
return
|
|
18644
18679
|
if status == "empty":
|
|
18645
18680
|
await self._mount_message(
|
|
18646
18681
|
AppMessage("Nothing to offload — start a conversation first")
|
|
@@ -18829,7 +18864,7 @@ class DeepAgentsApp(App):
|
|
|
18829
18864
|
"Compacting idle context before cache expiry.",
|
|
18830
18865
|
markup=False,
|
|
18831
18866
|
)
|
|
18832
|
-
await self._handle_offload(reserved=True)
|
|
18867
|
+
await self._handle_offload(reserved=True, automatic=idle_window is not None)
|
|
18833
18868
|
finally:
|
|
18834
18869
|
self._offload_task_started = False
|
|
18835
18870
|
self._offload_worker = None
|
|
@@ -18837,16 +18872,19 @@ class DeepAgentsApp(App):
|
|
|
18837
18872
|
if not self._startup_sequence_running:
|
|
18838
18873
|
await self._process_next_from_queue()
|
|
18839
18874
|
|
|
18840
|
-
async def _handle_offload(
|
|
18875
|
+
async def _handle_offload(
|
|
18876
|
+
self, *, reserved: bool = False, automatic: bool = False
|
|
18877
|
+
) -> None:
|
|
18841
18878
|
"""Run `/offload`, always releasing its busy-state reservation.
|
|
18842
18879
|
|
|
18843
18880
|
Args:
|
|
18844
18881
|
reserved: Whether the caller already reserved the turn synchronously.
|
|
18882
|
+
automatic: Suppress no-op feedback for idle cache maintenance.
|
|
18845
18883
|
"""
|
|
18846
18884
|
if not reserved:
|
|
18847
18885
|
self._set_agent_running(True)
|
|
18848
18886
|
try:
|
|
18849
|
-
await self._offload_impl()
|
|
18887
|
+
await self._offload_impl(automatic=automatic)
|
|
18850
18888
|
finally:
|
|
18851
18889
|
self._set_agent_running(False)
|
|
18852
18890
|
try:
|
|
@@ -18854,7 +18892,7 @@ class DeepAgentsApp(App):
|
|
|
18854
18892
|
except Exception: # best-effort spinner cleanup
|
|
18855
18893
|
logger.exception("Failed to dismiss spinner after offload")
|
|
18856
18894
|
|
|
18857
|
-
async def _offload_impl(self) -> None:
|
|
18895
|
+
async def _offload_impl(self, *, automatic: bool = False) -> None:
|
|
18858
18896
|
"""Offload older messages through the dcode server operation."""
|
|
18859
18897
|
if not self._agent or not self._lc_thread_id:
|
|
18860
18898
|
await self._mount_message(
|
|
@@ -18870,7 +18908,7 @@ class DeepAgentsApp(App):
|
|
|
18870
18908
|
return
|
|
18871
18909
|
|
|
18872
18910
|
config: RunnableConfig = {"configurable": {"thread_id": self._lc_thread_id}}
|
|
18873
|
-
await self._handle_server_offload(config)
|
|
18911
|
+
await self._handle_server_offload(config, automatic=automatic)
|
|
18874
18912
|
|
|
18875
18913
|
def _start_plugin_auto_update(self) -> None:
|
|
18876
18914
|
"""Start the plugin auto-update worker."""
|
|
@@ -20985,7 +21023,7 @@ class DeepAgentsApp(App):
|
|
|
20985
21023
|
try:
|
|
20986
21024
|
resumable = await thread_exists(previous_thread_id)
|
|
20987
21025
|
owner = await get_thread_agent(previous_thread_id) if resumable else None
|
|
20988
|
-
except
|
|
21026
|
+
except sqlite3.Error, OSError:
|
|
20989
21027
|
# Keep store failures visible because they are not routine suppression.
|
|
20990
21028
|
logger.info(
|
|
20991
21029
|
"Could not check whether previous thread %s is resumable",
|
|
@@ -22465,18 +22503,18 @@ class DeepAgentsApp(App):
|
|
|
22465
22503
|
if self._pending_approval_widget:
|
|
22466
22504
|
try:
|
|
22467
22505
|
self._pending_approval_widget.action_select_reject()
|
|
22468
|
-
except
|
|
22506
|
+
except AttributeError, RuntimeError:
|
|
22469
22507
|
logger.exception("force-clear: failed to reject pending approval")
|
|
22470
22508
|
if self._pending_ask_user_widget:
|
|
22471
22509
|
try:
|
|
22472
22510
|
self._pending_ask_user_widget.action_cancel()
|
|
22473
|
-
except
|
|
22511
|
+
except AttributeError, RuntimeError:
|
|
22474
22512
|
logger.exception("force-clear: failed to cancel pending ask-user")
|
|
22475
22513
|
self._cancel_goal_proposal_worker()
|
|
22476
22514
|
if self._pending_goal_review_widget:
|
|
22477
22515
|
try:
|
|
22478
22516
|
self._pending_goal_review_widget.action_cancel()
|
|
22479
|
-
except
|
|
22517
|
+
except AttributeError, RuntimeError:
|
|
22480
22518
|
logger.exception("force-clear: failed to cancel pending goal review")
|
|
22481
22519
|
if self._shell_running and self._shell_worker:
|
|
22482
22520
|
self._shell_worker.cancel()
|
|
@@ -23476,14 +23514,14 @@ class DeepAgentsApp(App):
|
|
|
23476
23514
|
asyncio.shield(worker.wait()),
|
|
23477
23515
|
timeout=_GRACEFUL_EXIT_WAIT_SECONDS,
|
|
23478
23516
|
)
|
|
23479
|
-
except
|
|
23517
|
+
except asyncio.CancelledError, WorkerCancelled:
|
|
23480
23518
|
# Expected: exit() cancelled the worker above, so
|
|
23481
23519
|
# its cancellation handler ran to completion.
|
|
23482
23520
|
logger.debug(
|
|
23483
23521
|
"Agent worker cancelled cleanly before app exit",
|
|
23484
23522
|
exc_info=True,
|
|
23485
23523
|
)
|
|
23486
|
-
except
|
|
23524
|
+
except TimeoutError, WorkerFailed:
|
|
23487
23525
|
# The worker did not finish within the window, so
|
|
23488
23526
|
# the in-flight run's server-side trace may be
|
|
23489
23527
|
# incomplete. Surface above debug so the loss isn't
|
|
@@ -26025,7 +26063,7 @@ class DeepAgentsApp(App):
|
|
|
26025
26063
|
if self._pending_ask_user_widget is not None:
|
|
26026
26064
|
try:
|
|
26027
26065
|
self._pending_ask_user_widget.action_cancel()
|
|
26028
|
-
except
|
|
26066
|
+
except AttributeError, RuntimeError:
|
|
26029
26067
|
logger.debug(
|
|
26030
26068
|
"Failed to cancel pending ask-user during agent swap",
|
|
26031
26069
|
exc_info=True,
|
|
@@ -26424,7 +26462,7 @@ class DeepAgentsApp(App):
|
|
|
26424
26462
|
cli_version="0.1.0",
|
|
26425
26463
|
release_age=" (released 2 days ago)",
|
|
26426
26464
|
installed_age="",
|
|
26427
|
-
upgrade_cmd="uv tool
|
|
26465
|
+
upgrade_cmd="uv tool install -U deepagents-code",
|
|
26428
26466
|
)
|
|
26429
26467
|
self._notice_registry.add(update_notification)
|
|
26430
26468
|
self._update_modal_pending.set()
|
|
@@ -27261,10 +27299,25 @@ class DeepAgentsApp(App):
|
|
|
27261
27299
|
show_toast=not progress_modal_visible,
|
|
27262
27300
|
)
|
|
27263
27301
|
return
|
|
27302
|
+
|
|
27303
|
+
notices: list[str] = []
|
|
27304
|
+
deferred: list[bool] = []
|
|
27305
|
+
unverified: list[bool] = []
|
|
27306
|
+
|
|
27307
|
+
def show_upgrade_notice(message: str) -> None:
|
|
27308
|
+
notices.append(message)
|
|
27309
|
+
screen.append_line(message)
|
|
27310
|
+
self.notify(message, timeout=15, markup=False)
|
|
27311
|
+
|
|
27312
|
+
# Same live-session constraint as `/update`.
|
|
27264
27313
|
success, output, installed = await perform_upgrade(
|
|
27265
27314
|
progress=screen.append_line,
|
|
27315
|
+
on_notice=show_upgrade_notice,
|
|
27266
27316
|
log_path=log_path,
|
|
27267
27317
|
target_version=payload.latest,
|
|
27318
|
+
allow_python_migration=False,
|
|
27319
|
+
on_migration_deferred=lambda: deferred.append(True),
|
|
27320
|
+
on_unverified_install=lambda: unverified.append(True),
|
|
27268
27321
|
)
|
|
27269
27322
|
if success:
|
|
27270
27323
|
self._notice_registry.remove(entry.key)
|
|
@@ -27289,11 +27342,9 @@ class DeepAgentsApp(App):
|
|
|
27289
27342
|
markup=False,
|
|
27290
27343
|
)
|
|
27291
27344
|
return
|
|
27292
|
-
#
|
|
27293
|
-
#
|
|
27294
|
-
#
|
|
27295
|
-
# the notice sat open). Report what actually landed on
|
|
27296
|
-
# disk rather than the stale payload version.
|
|
27345
|
+
# uv installs are pinned and verified, and other
|
|
27346
|
+
# methods report the target, so this matches the
|
|
27347
|
+
# payload; the fallback is defensive.
|
|
27297
27348
|
upgraded_to = installed or payload.latest
|
|
27298
27349
|
# After `mark_success`: the modal stays un-dismissable
|
|
27299
27350
|
# until it is marked done, and this worker runs with
|
|
@@ -27311,12 +27362,35 @@ class DeepAgentsApp(App):
|
|
|
27311
27362
|
markup=False,
|
|
27312
27363
|
)
|
|
27313
27364
|
return
|
|
27365
|
+
self._notice_registry.remove(entry.key)
|
|
27366
|
+
if deferred:
|
|
27367
|
+
logger.info(
|
|
27368
|
+
"Deferred the Python migration for v%s", payload.latest
|
|
27369
|
+
)
|
|
27370
|
+
screen.mark_warning("Update deferred. See details.")
|
|
27371
|
+
return
|
|
27314
27372
|
logger.warning(
|
|
27315
27373
|
"Auto-upgrade failed for v%s. Output:\n%s",
|
|
27316
27374
|
payload.latest,
|
|
27317
27375
|
output,
|
|
27318
27376
|
)
|
|
27319
|
-
|
|
27377
|
+
if unverified:
|
|
27378
|
+
# This session lazily imports modules that uv may have
|
|
27379
|
+
# replaced.
|
|
27380
|
+
restart = (
|
|
27381
|
+
"Update could not be verified. Quit and relaunch dcode "
|
|
27382
|
+
"before continuing."
|
|
27383
|
+
)
|
|
27384
|
+
screen.mark_warning(restart)
|
|
27385
|
+
self.notify(
|
|
27386
|
+
restart, severity="warning", timeout=20, markup=False
|
|
27387
|
+
)
|
|
27388
|
+
return
|
|
27389
|
+
if notices:
|
|
27390
|
+
# The notice toast and modal line already carry the
|
|
27391
|
+
# notice's own recovery steps.
|
|
27392
|
+
screen.mark_warning("Update did not complete. See details.")
|
|
27393
|
+
return
|
|
27320
27394
|
screen.mark_failure(cmd)
|
|
27321
27395
|
snippet = _truncate(output, limit=160) if output else ""
|
|
27322
27396
|
message = f"Auto-update failed. Run manually: {cmd}"
|
|
@@ -27859,7 +27933,7 @@ class DeepAgentsApp(App):
|
|
|
27859
27933
|
"""
|
|
27860
27934
|
try:
|
|
27861
27935
|
banner = self.query_one("#welcome-banner", WelcomeBanner)
|
|
27862
|
-
except
|
|
27936
|
+
except NoMatches, ScreenStackError:
|
|
27863
27937
|
logger.debug("Welcome banner not mounted during MCP count refresh")
|
|
27864
27938
|
return
|
|
27865
27939
|
banner.set_connected(
|
|
@@ -30121,7 +30195,7 @@ class DeepAgentsApp(App):
|
|
|
30121
30195
|
|
|
30122
30196
|
msg = f"No previous threads for '{active_agent}' to resume."
|
|
30123
30197
|
await self._mount_message(AppMessage(msg))
|
|
30124
|
-
except
|
|
30198
|
+
except sqlite3.Error, OSError:
|
|
30125
30199
|
# Expected thread-store failures: log for a debug session and tell
|
|
30126
30200
|
# the user to retry. Mirrors the launch-time resolver's handling.
|
|
30127
30201
|
logger.warning(
|
|
@@ -30963,7 +31037,7 @@ class DeepAgentsApp(App):
|
|
|
30963
31037
|
credentials.preview_reload_from_environment,
|
|
30964
31038
|
start_path=cwd,
|
|
30965
31039
|
)
|
|
30966
|
-
except
|
|
31040
|
+
except OSError, ValueError:
|
|
30967
31041
|
# Environmental failures (unreadable dotenv, malformed values) are
|
|
30968
31042
|
# expected and non-fatal for a best-effort preview. Programming
|
|
30969
31043
|
# errors (KeyError/TypeError/ImportError) are left to propagate so a
|
|
@@ -32183,7 +32257,7 @@ class DeepAgentsApp(App):
|
|
|
32183
32257
|
self._retry_status_widget = None
|
|
32184
32258
|
try:
|
|
32185
32259
|
messages = self.query_one("#messages", Container)
|
|
32186
|
-
except
|
|
32260
|
+
except NoMatches, ScreenStackError:
|
|
32187
32261
|
messages = None
|
|
32188
32262
|
if messages is not None and messages.is_attached:
|
|
32189
32263
|
new_widget = AppMessage(
|
deepagents_code/approval_mode.py
CHANGED
|
@@ -408,7 +408,7 @@ def _load_approval_state(path: Path) -> dict[str, object]:
|
|
|
408
408
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
409
409
|
except FileNotFoundError:
|
|
410
410
|
return {}
|
|
411
|
-
except
|
|
411
|
+
except OSError, UnicodeDecodeError, json.JSONDecodeError:
|
|
412
412
|
logger.warning(
|
|
413
413
|
"Ignoring unreadable or corrupt approval state at %s; callers fail "
|
|
414
414
|
"closed and the file will be overwritten on the next save",
|
deepagents_code/auto_mode.py
CHANGED
|
@@ -548,7 +548,7 @@ def _validate_temp_artifact(value: object) -> AutoTempArtifact | None:
|
|
|
548
548
|
return None
|
|
549
549
|
try:
|
|
550
550
|
file_path = Path(cast("str", raw_file_path))
|
|
551
|
-
except
|
|
551
|
+
except OSError, TypeError, ValueError:
|
|
552
552
|
return None
|
|
553
553
|
if not file_path.is_absolute() or not file_path.name.startswith(
|
|
554
554
|
_TEMP_ARTIFACT_PREFIX
|
|
@@ -794,7 +794,7 @@ def _known_credential_values(
|
|
|
794
794
|
and len(value) >= _MIN_SECRET_LENGTH
|
|
795
795
|
):
|
|
796
796
|
values.add(value)
|
|
797
|
-
except
|
|
797
|
+
except OSError, RuntimeError, TypeError, ValueError:
|
|
798
798
|
logger.debug("Could not load stored credential values for Auto redaction")
|
|
799
799
|
return tuple(sorted(values, key=len, reverse=True))
|
|
800
800
|
|
|
@@ -2033,7 +2033,7 @@ def _resolve_path(root: Path, raw: object) -> Path | None:
|
|
|
2033
2033
|
if not candidate.is_absolute():
|
|
2034
2034
|
candidate = root / candidate
|
|
2035
2035
|
return candidate.resolve(strict=False)
|
|
2036
|
-
except
|
|
2036
|
+
except OSError, RuntimeError, ValueError:
|
|
2037
2037
|
return None
|
|
2038
2038
|
|
|
2039
2039
|
|
|
@@ -2662,7 +2662,7 @@ class AutoModeHITLMiddleware(HumanInTheLoopMiddleware[AutoModeState, Any, Any]):
|
|
|
2662
2662
|
if not targets_managed_artifact:
|
|
2663
2663
|
try:
|
|
2664
2664
|
candidate_stat = candidate.stat()
|
|
2665
|
-
except
|
|
2665
|
+
except OSError, ValueError:
|
|
2666
2666
|
pass
|
|
2667
2667
|
else:
|
|
2668
2668
|
targets_managed_artifact = any(
|
deepagents_code/btw.py
CHANGED
|
@@ -186,7 +186,7 @@ async def _open_text_stream(
|
|
|
186
186
|
messages: list[BaseMessage],
|
|
187
187
|
config: RunnableConfig,
|
|
188
188
|
settings: Mapping[str, Any],
|
|
189
|
-
) -> tuple[AsyncGenerator[AIMessageChunk
|
|
189
|
+
) -> tuple[AsyncGenerator[AIMessageChunk], str]:
|
|
190
190
|
"""Open one attempt through its first visible fragment, closing failures.
|
|
191
191
|
|
|
192
192
|
Returns:
|
|
@@ -194,7 +194,7 @@ async def _open_text_stream(
|
|
|
194
194
|
"""
|
|
195
195
|
# BaseChatModel.astream is an async generator, annotated as an iterator.
|
|
196
196
|
stream = cast(
|
|
197
|
-
"AsyncGenerator[AIMessageChunk
|
|
197
|
+
"AsyncGenerator[AIMessageChunk]",
|
|
198
198
|
model.astream(messages, config=config, **settings),
|
|
199
199
|
)
|
|
200
200
|
try:
|
deepagents_code/btw_api.py
CHANGED
|
@@ -133,7 +133,7 @@ class _BtwStreamingResponse(Response):
|
|
|
133
133
|
await emit("error", {"detail": "Side question timed out. Try again."})
|
|
134
134
|
except ClientDisconnect:
|
|
135
135
|
raise # A closed transport cannot receive an error event.
|
|
136
|
-
except
|
|
136
|
+
except Exception, SystemExit:
|
|
137
137
|
logger.exception("Side question failed")
|
|
138
138
|
await emit(
|
|
139
139
|
"error",
|
|
@@ -230,7 +230,7 @@ async def btw(request: Request) -> Response:
|
|
|
230
230
|
return JSONResponse(
|
|
231
231
|
{"detail": "Side question timed out. Try again."}, status_code=504
|
|
232
232
|
)
|
|
233
|
-
except
|
|
233
|
+
except Exception, SystemExit:
|
|
234
234
|
logger.exception("Side question failed")
|
|
235
235
|
return JSONResponse(
|
|
236
236
|
{"detail": "Side question failed on the server; see the server log."},
|