Skip to content

Commit 24db186

Browse files
authored
Update commands.py
1 parent a5cbecb commit 24db186

1 file changed

Lines changed: 6 additions & 100 deletions

File tree

‎python_agent_harness/tui/commands.py‎

Lines changed: 6 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@
1818
from rich.live import Live
1919

2020
from .. import config
21-
from ..attachments import image_placeholder, parse_at_references, reattach_images
21+
from ..attachments import image_placeholder, parse_at_references
2222
from ..commands import find_command
2323
from ..models import ImagePart, Message, TextPart
2424
from ..persistence import (
2525
SessionPersistence,
2626
escape_role_headers,
27-
split_role_header,
27+
find_session_by_title,
28+
parse_saved_body,
2829
title_from_filename,
29-
unescape_role_header,
3030
)
3131
from ..prompts import RESERVED_AGENT_NAME, discover_agents
3232
from .input import _history_path
@@ -632,7 +632,7 @@ def _run_sessions(self) -> None:
632632
model = meta.get("python-agent-harness--model", "?")
633633
project = meta.get("python-agent-harness--project-dir", "?")
634634
body = SessionPersistence.strip_metadata(text)
635-
n_msgs = len(self._parse_saved_body(body))
635+
n_msgs = len(parse_saved_body(body))
636636
self.console.print(
637637
f" {basename:50s} model={model:20s} project={project} {n_msgs} messages"
638638
)
@@ -655,7 +655,7 @@ def _run_restore(self, arg: str) -> None:
655655
else:
656656
# Try title-based matching: find sessions whose filename
657657
# contains the argument as a case-insensitive substring
658-
path = self._find_session_by_title(arg)
658+
path = find_session_by_title(arg)
659659
if not path:
660660
self.console.print(
661661
"[yellow]no session found "
@@ -675,7 +675,7 @@ def _run_restore(self, arg: str) -> None:
675675
meta = SessionPersistence.parse_metadata(text)
676676
body = SessionPersistence.strip_metadata(text)
677677
# Rebuild conversation history from the saved markdown format
678-
messages = self._parse_saved_body(body)
678+
messages = parse_saved_body(body)
679679
# Round timestamps are persisted in the metadata block; restore
680680
# them so the dump separators keep their HH:MM:SS times.
681681
round_times = meta.get("python-agent-harness--round-times")
@@ -785,97 +785,3 @@ def _switch_input_history(self, project_dir: str) -> None:
785785
# reset() cancels the pending history-load task and repopulates
786786
# the working lines from the new history on the next render
787787
buffer.reset()
788-
789-
@staticmethod
790-
def _parse_saved_body(body: str) -> list[Message]:
791-
"""Parse a saved session body back into Message objects.
792-
793-
The save format is markdown with **role**: content blocks
794-
separated by blank lines.
795-
796-
``tool`` and ``system`` blocks are dropped: the saved markdown
797-
does not keep ``tool_call_id``/``name`` (assistant tool calls are
798-
flattened to plain text), so a restored ``role="tool"`` message
799-
would form an API-invalid payload (a tool message with no
800-
preceding assistant ``tool_calls``). A restored ``system``
801-
message would duplicate the live system prompt the client
802-
prepends on every request. The following assistant reply
803-
already summarizes the results, so dropping them loses no
804-
essential context.
805-
806-
Body lines that merely look like a block header are escaped by
807-
the renderer (see `escape_role_headers`) and unescaped here, so
808-
a message quoting this format no longer splits into extra
809-
messages. Sessions saved before escaping existed can still
810-
split — that ambiguity is in the file, not in this parser.
811-
"""
812-
messages: list[Message] = []
813-
current_role: str | None = None
814-
current_lines: list[str] = []
815-
816-
def _flush(role: str, lines: list[str]) -> None:
817-
content = "\n".join(lines).strip()
818-
if not content:
819-
return
820-
# A leading image-attachment placeholder (written on save)
821-
# is re-attached when the file still exists, so the restored
822-
# message is multimodal again; otherwise it stays as text.
823-
new_content, parts = reattach_images(content)
824-
if parts:
825-
remainder = new_content.split("\n", 1)
826-
rest_text = remainder[1].strip() if len(remainder) > 1 else ""
827-
content_parts: list = []
828-
if rest_text:
829-
content_parts.append(TextPart(text=rest_text))
830-
content_parts.extend(parts)
831-
messages.append(Message(role=role, content=content_parts))
832-
else:
833-
messages.append(Message(role=role, content=content))
834-
835-
for line in body.splitlines():
836-
# Check for a role header: **user**: ... or **assistant**: ...
837-
header = split_role_header(line)
838-
if header is not None:
839-
role, rest = header
840-
# Save the previous block (tool blocks lose their
841-
# tool_call_id/name; system blocks would duplicate the
842-
# live system prompt the client prepends per request)
843-
if current_role is not None and current_role not in ("tool", "system"):
844-
_flush(current_role, current_lines)
845-
current_role = role
846-
current_lines = [unescape_role_header(rest)]
847-
continue
848-
current_lines.append(unescape_role_header(line))
849-
850-
# Don't forget the last block (tool/system blocks dropped, see above)
851-
if current_role is not None and current_role not in ("tool", "system"):
852-
_flush(current_role, current_lines)
853-
854-
return messages
855-
856-
@staticmethod
857-
def _find_session_by_title(query: str) -> str | None:
858-
"""Find a session file by title substring (case-insensitive).
859-
860-
Matches against the full filename, the filename without .md,
861-
and the derived title. Returns the most recent match, or None.
862-
"""
863-
query_lower = query.lower()
864-
# Strip .md from query if present, for cleaner substring matching
865-
query_stem = query_lower[:-3] if query_lower.endswith(".md") else query_lower
866-
files = SessionPersistence.list_sessions() # already sorted by mtime desc
867-
for f in files:
868-
basename = os.path.basename(f)
869-
basename_lower = basename.lower()
870-
# Exact basename match (with or without .md)
871-
if basename_lower == query_lower or basename_lower == query_lower + ".md":
872-
return f
873-
# Substring match against filename (minus .md)
874-
name_part = basename[:-3] if basename.endswith(".md") else basename
875-
if query_stem in name_part.lower():
876-
return f
877-
# Match against derived title (dashes → spaces)
878-
title = title_from_filename(f)
879-
if title and query_stem in title.lower():
880-
return f
881-
return None

0 commit comments

Comments
 (0)