core/tools+cli/display: name the trash in the delete confirm

The box asked "delete path 'x'", which reads as final even now that it is not.
It now says "move path 'x' to the system trash" — and "delete path 'x'
permanently" when the path really is destroyed: an explicit `permanent=true`, a
host that turned `[trash] enabled` off, or a scratch path under the OS temporary
directory. The classifier resolves the path to answer that, and falls back to
the trash wording when path policy will refuse the call anyway.

The pretty line follows: a delete that landed in the trash prints `(to trash)`
instead of `(done)`, so a session's transcript still shows which deletes could
be undone.
This commit is contained in:
2026-10-21 20:23:54 +08:00
parent 17813281d8
commit 2369592473
4 changed files with 46 additions and 3 deletions
+2
View File
@@ -550,6 +550,8 @@ def _mutator_detail(name: str, result: str) -> str:
if match:
chars, _path = match.groups()
return _pretty_segments((f"({chars} chars)", None))
if name == "delete_path" and "system trash" in result:
return _pretty_segments(("(to trash)", "green"))
return _pretty_segments(("(done)", "green"))
+19 -2
View File
@@ -6,7 +6,8 @@ from typing import TYPE_CHECKING, cast
from plyngent.agent.tools import DangerReason
from plyngent.tools.command_scan import dash_c_code, escalators_in_chain, is_interpreter, unwrap_command
from plyngent.tools.workspace import AccessMode, WorkspaceError, resolve_path
from plyngent.tools.trash import under_system_temp
from plyngent.tools.workspace import AccessMode, WorkspaceError, get_trash_enabled, resolve_path
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
@@ -132,7 +133,23 @@ def _delete_path_reason(args: Mapping[str, object]) -> str | None:
path = args.get("path")
recursive = bool(args.get("recursive", False))
extra = " recursively" if recursive else ""
return f"delete path {path!r}{extra}"
if _delete_is_final(args):
return f"delete path {path!r}{extra} permanently"
return f"move path {path!r}{extra} to the system trash"
def _delete_is_final(args: Mapping[str, object]) -> bool:
"""True when the delete destroys the path instead of trashing it."""
if bool(args.get("permanent", False)):
return True
try:
if not get_trash_enabled():
return True
target = resolve_path(str(args.get("path", "")), required=AccessMode.WRITE)
except WorkspaceError:
# Path policy will refuse the call; word the confirm as the default.
return False
return under_system_temp(target)
def _run_argv_reason(args: Mapping[str, object]) -> DangerReason | None: