python-agent-harness 1.5.6.6__tar.gz → 1.5.6.7__tar.gz
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.
- {python_agent_harness-1.5.6.6/python_agent_harness.egg-info → python_agent_harness-1.5.6.7}/PKG-INFO +1 -1
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/pyproject.toml +1 -1
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/__init__.py +1 -1
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/models.py +17 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/token_estimator.py +4 -2
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/llm/client.py +15 -8
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/session/config.py +5 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tui/core.py +56 -10
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tui/input.py +31 -1
- python_agent_harness-1.5.6.7/python_agent_harness/tui/mouse.py +406 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tui/render.py +544 -24
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7/python_agent_harness.egg-info}/PKG-INFO +1 -1
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness.egg-info/SOURCES.txt +1 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/LICENSE +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/README.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/__main__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/agent.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/context_manager.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/planmode.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/subagent.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/tool_runner.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/entry/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/entry/cli.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/entry/controller.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/entry/headless.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/entry/restore.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/entry/server.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/entry/view.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/io/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/io/attachments.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/io/diffrender.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/io/persistence.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/io/text_filter.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/llm/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/mcp/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/mcp/client.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/mcp/config.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/mcp/manager.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/agent.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/agents/reviewer.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/build-switch.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/commands/explain.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/compact.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/core.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/initialize.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/plan-mode.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/plan.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/review.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/subagent.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/summary.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/task-completion-rules.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/prompts/title.md +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/session/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/session/commands.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/session/session.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/agent_tool.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/base.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/bash.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/diffapply.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/edit.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/edit_mac.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/filesystem.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/glob.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/glob_mac.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/grep.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/grep_mac.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/mcp.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/planexit.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/question.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/read.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/skill.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/todo.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tools/write.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tui/__init__.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tui/commands.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness.egg-info/dependency_links.txt +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness.egg-info/entry_points.txt +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness.egg-info/requires.txt +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness.egg-info/top_level.txt +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/setup.cfg +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/tests/test_invariants.py +0 -0
- {python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/tests/test_scenarios.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-agent-harness"
|
|
7
|
-
version = "1.5.6.
|
|
7
|
+
version = "1.5.6.7"
|
|
8
8
|
description = "A lightweight, hackable mini-OpenCode written in Python."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Huming Chen", email = "chenhuming@gmail.com"}
|
{python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/core/models.py
RENAMED
|
@@ -35,6 +35,23 @@ class AgentMode(enum.Enum):
|
|
|
35
35
|
PLAN = "plan"
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def extract_reasoning(obj: dict[str, Any]) -> str:
|
|
39
|
+
"""Reasoning text from a backend response object, or "".
|
|
40
|
+
|
|
41
|
+
Backends disagree on the field name: most (DeepSeek, GLM, ...) use
|
|
42
|
+
``reasoning_content``; Qwen uses ``reasoning``. This is the single
|
|
43
|
+
place that knows the variants — callers (the LLM client's streaming
|
|
44
|
+
and non-streaming parsers, token estimation) must not open-code the
|
|
45
|
+
keys. Non-string values (some backends send a list of parts) are
|
|
46
|
+
dropped, not coerced.
|
|
47
|
+
"""
|
|
48
|
+
for key in ("reasoning_content", "reasoning"):
|
|
49
|
+
value = obj.get(key)
|
|
50
|
+
if isinstance(value, str) and value:
|
|
51
|
+
return value
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
|
|
38
55
|
@dataclass
|
|
39
56
|
class TextPart:
|
|
40
57
|
"""A plain-text content part in a multimodal message."""
|
|
@@ -12,6 +12,7 @@ import os
|
|
|
12
12
|
import re
|
|
13
13
|
|
|
14
14
|
from ..session import config
|
|
15
|
+
from .models import extract_reasoning
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def is_cjk_char(c: str) -> bool:
|
|
@@ -128,8 +129,9 @@ def payload_text(system: object, messages: list[dict], tools: list[dict]) -> str
|
|
|
128
129
|
if isinstance(v, str):
|
|
129
130
|
buf.append(v)
|
|
130
131
|
break
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
# currently unreachable: Message.to_api() never emits reasoning (and strips it from content)
|
|
133
|
+
reasoning = extract_reasoning(msg)
|
|
134
|
+
if reasoning:
|
|
133
135
|
buf.append(reasoning)
|
|
134
136
|
for tc in msg.get("tool_calls") or []:
|
|
135
137
|
fn = tc.get("function", {}) if isinstance(tc, dict) else {}
|
{python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/llm/client.py
RENAMED
|
@@ -24,7 +24,15 @@ from typing import Any, Protocol, runtime_checkable
|
|
|
24
24
|
|
|
25
25
|
import httpx
|
|
26
26
|
|
|
27
|
-
from ..core.models import
|
|
27
|
+
from ..core.models import (
|
|
28
|
+
ImagePart,
|
|
29
|
+
Message,
|
|
30
|
+
TextPart,
|
|
31
|
+
ToolCall,
|
|
32
|
+
ToolSpec,
|
|
33
|
+
Usage,
|
|
34
|
+
extract_reasoning,
|
|
35
|
+
)
|
|
28
36
|
from ..session import config
|
|
29
37
|
|
|
30
38
|
|
|
@@ -875,11 +883,12 @@ class Client:
|
|
|
875
883
|
content_parts.append(delta["content"])
|
|
876
884
|
if on_delta:
|
|
877
885
|
on_delta(delta["content"])
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
886
|
+
reasoning = extract_reasoning(delta)
|
|
887
|
+
if reasoning:
|
|
888
|
+
content_parts.append(reasoning)
|
|
889
|
+
reasoning_parts.append(reasoning)
|
|
881
890
|
if on_delta:
|
|
882
|
-
on_delta(
|
|
891
|
+
on_delta(reasoning)
|
|
883
892
|
for tc in delta.get("tool_calls") or []:
|
|
884
893
|
self._absorb_tool_call(
|
|
885
894
|
tc_index, self._slot_for(tc_index, tc, 0), tc, on_tool_call
|
|
@@ -921,7 +930,7 @@ class Client:
|
|
|
921
930
|
tc_index: dict[int, dict[str, Any]] = {}
|
|
922
931
|
choice = (data.get("choices") or [{}])[0]
|
|
923
932
|
msg = choice.get("message") or {}
|
|
924
|
-
reasoning = msg
|
|
933
|
+
reasoning = extract_reasoning(msg)
|
|
925
934
|
content = msg.get("content") or ""
|
|
926
935
|
# some backends return content as a list of parts (multimodal);
|
|
927
936
|
# normalize to plain text like Message.text() does, so the
|
|
@@ -931,8 +940,6 @@ class Client:
|
|
|
931
940
|
p.get("text", "") if isinstance(p, dict) else (p if isinstance(p, str) else "")
|
|
932
941
|
for p in content
|
|
933
942
|
)
|
|
934
|
-
if not isinstance(reasoning, str):
|
|
935
|
-
reasoning = ""
|
|
936
943
|
# mirror the streaming order: reasoning first, then the answer
|
|
937
944
|
if reasoning:
|
|
938
945
|
reasoning_parts.append(reasoning)
|
{python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/session/config.py
RENAMED
|
@@ -203,6 +203,11 @@ SUBAGENT_EXCLUDED_TOOLS = ("Agent", "Question", "PlanExit", "TodoWrite")
|
|
|
203
203
|
TOOL_RESULT_PREVIEW_LINES = 5 # max lines of a tool result shown in the TUI
|
|
204
204
|
TOOL_RESULT_PREVIEW_CHARS = 500 # max chars of that preview (long single lines)
|
|
205
205
|
|
|
206
|
+
# Per-argument value cap for a tool call the user expanded: the full
|
|
207
|
+
# command/payload is shown, but a runaway (e.g. a 200KB argument) is
|
|
208
|
+
# still trimmed so one row cannot flood the panel.
|
|
209
|
+
TOOL_EXPANDED_ARG_CHARS = 4000
|
|
210
|
+
|
|
206
211
|
# ---- default agent prompts -----------------------------------------------------
|
|
207
212
|
# Ported system prompts (opencode-style) for the main agent and sub-agents,
|
|
208
213
|
# bundled with the package (prompts/agent.md, prompts/subagent.md).
|
{python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tui/core.py
RENAMED
|
@@ -32,6 +32,7 @@ from ..session import config
|
|
|
32
32
|
from ..session.session import Session
|
|
33
33
|
from .commands import CommandMixin
|
|
34
34
|
from .input import InputMixin, SlashCompleter, UiQuestion, _history_path, _make_prompt_session
|
|
35
|
+
from .mouse import MouseSession, apply_typed
|
|
35
36
|
from .render import RenderMixin, safe_markup
|
|
36
37
|
|
|
37
38
|
# Unexpected UI exceptions in a row (no successful input read in
|
|
@@ -81,6 +82,19 @@ class Tui(RenderMixin, InputMixin, CommandMixin):
|
|
|
81
82
|
# prompts/agents/ directory; refreshed on each /agent call so files
|
|
82
83
|
# added at runtime are picked up.
|
|
83
84
|
self._discovered_agents: dict[str, str] = {}
|
|
85
|
+
# Tool calls the user expanded by clicking their ▶ row: their
|
|
86
|
+
# panel rows carry every argument in full instead of the
|
|
87
|
+
# truncated one-line label.
|
|
88
|
+
self._expanded_tools: set[str] = set()
|
|
89
|
+
# Last rendered frame, so a mouse click can be traced back to
|
|
90
|
+
# the row it landed on.
|
|
91
|
+
self._last_frame: Any = None
|
|
92
|
+
# Mouse session of the run in progress (None outside a run): a
|
|
93
|
+
# click has to ask the terminal where the panel is on screen.
|
|
94
|
+
self._mouse: MouseSession | None = None
|
|
95
|
+
# Text typed while the mouse reader owned the terminal during a
|
|
96
|
+
# run: replayed into the next input prompt instead of dropped.
|
|
97
|
+
self._typed_during_run = ""
|
|
84
98
|
self.prompt_session = _make_prompt_session(
|
|
85
99
|
FileHistory(_history_path(str(self._controller.project_dir))),
|
|
86
100
|
SlashCompleter(lambda: str(self._controller.project_dir)),
|
|
@@ -377,6 +391,16 @@ class Tui(RenderMixin, InputMixin, CommandMixin):
|
|
|
377
391
|
self.console.print()
|
|
378
392
|
self._flush()
|
|
379
393
|
|
|
394
|
+
def _collect_typed(self, text: str) -> None:
|
|
395
|
+
"""Keep text typed while the mouse reader owned the terminal.
|
|
396
|
+
|
|
397
|
+
With mouse reporting on, the run owns stdin in cbreak mode, so
|
|
398
|
+
keystrokes would otherwise be read by the mouse reader and lost.
|
|
399
|
+
They are buffered here and offered as the next prompt's default
|
|
400
|
+
instead (see ``InputMixin._read_multiline``).
|
|
401
|
+
"""
|
|
402
|
+
self._typed_during_run = apply_typed(self._typed_during_run, text)
|
|
403
|
+
|
|
380
404
|
def _abandon_run(self) -> None:
|
|
381
405
|
"""Cancel the active run and release what it holds (UI side)."""
|
|
382
406
|
self._controller.cancel()
|
|
@@ -401,25 +425,47 @@ class Tui(RenderMixin, InputMixin, CommandMixin):
|
|
|
401
425
|
the moment new stream text arrives, so the text pushes the
|
|
402
426
|
scroll immediately instead of waiting for a fixed tick. The
|
|
403
427
|
short timeout keeps the spinner animating between data bursts.
|
|
428
|
+
|
|
429
|
+
While the panel is live, terminal mouse reporting is on
|
|
430
|
+
(``mouse.MouseSession``) so clicking a ``▶`` tool row expands or
|
|
431
|
+
collapses it. Reporting is switched off whenever the input
|
|
432
|
+
prompt needs the terminal (a pending question) and always on the
|
|
433
|
+
way out, so normal text selection and typing behave as usual
|
|
434
|
+
outside a run.
|
|
404
435
|
"""
|
|
436
|
+
mouse = MouseSession(
|
|
437
|
+
self.console,
|
|
438
|
+
self._on_mouse_click,
|
|
439
|
+
on_text=self._collect_typed,
|
|
440
|
+
)
|
|
441
|
+
self._mouse = mouse
|
|
405
442
|
with Live(
|
|
406
443
|
self._render_frame(),
|
|
407
444
|
console=self.console,
|
|
408
445
|
refresh_per_second=30,
|
|
409
446
|
screen=False,
|
|
410
447
|
) as live:
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
self.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
448
|
+
mouse.enable()
|
|
449
|
+
try:
|
|
450
|
+
while worker.is_alive():
|
|
451
|
+
if self.question is not None:
|
|
452
|
+
live.stop()
|
|
453
|
+
# prompt_toolkit owns stdin here: mouse reports
|
|
454
|
+
# would land in the answer buffer as garbage
|
|
455
|
+
mouse.disable()
|
|
456
|
+
self._ask_question_blocking()
|
|
457
|
+
mouse.enable()
|
|
458
|
+
live.start()
|
|
459
|
+
continue
|
|
460
|
+
self._data_event.wait(timeout=0.1)
|
|
461
|
+
self._data_event.clear()
|
|
462
|
+
live.update(self._render_frame())
|
|
463
|
+
self._flush()
|
|
419
464
|
live.update(self._render_frame())
|
|
420
465
|
self._flush()
|
|
421
|
-
|
|
422
|
-
|
|
466
|
+
finally:
|
|
467
|
+
mouse.disable()
|
|
468
|
+
self._mouse = None
|
|
423
469
|
# run finished: the last Live frame stays on screen, but the
|
|
424
470
|
# in-place redraws never reached the scrollback — print the
|
|
425
471
|
# full conversation so the user can scroll back through it
|
{python_agent_harness-1.5.6.6 → python_agent_harness-1.5.6.7}/python_agent_harness/tui/input.py
RENAMED
|
@@ -381,9 +381,14 @@ class InputMixin:
|
|
|
381
381
|
)
|
|
382
382
|
|
|
383
383
|
def _read_multiline(self) -> str | None:
|
|
384
|
+
# Text typed while a run held the terminal (mouse reporting on)
|
|
385
|
+
# is offered as the default buffer content rather than dropped.
|
|
386
|
+
default = self._pending_default()
|
|
384
387
|
try:
|
|
385
388
|
with _safe_patch_stdout():
|
|
386
|
-
text = self.prompt_session.prompt(
|
|
389
|
+
text = self.prompt_session.prompt(
|
|
390
|
+
self._input_prompt(), multiline=True, default=default
|
|
391
|
+
)
|
|
387
392
|
except EOFError:
|
|
388
393
|
# Ctrl-D: quit
|
|
389
394
|
return None
|
|
@@ -393,6 +398,31 @@ class InputMixin:
|
|
|
393
398
|
return ""
|
|
394
399
|
return text
|
|
395
400
|
|
|
401
|
+
def _pending_default(self) -> str:
|
|
402
|
+
"""Text typed during the last run, consumed once.
|
|
403
|
+
|
|
404
|
+
Line breaks typed during the run are kept between lines, but
|
|
405
|
+
stripped at the ends: pressing Enter while waiting (to "wake"
|
|
406
|
+
the terminal, or to submit) would otherwise seed the prompt
|
|
407
|
+
with blank lines, and leading whitespace would hide a
|
|
408
|
+
``/command`` from the slash dispatcher.
|
|
409
|
+
"""
|
|
410
|
+
typed = getattr(self, "_typed_during_run", "")
|
|
411
|
+
if typed:
|
|
412
|
+
self._typed_during_run = ""
|
|
413
|
+
# Leading blank lines never matter; leading spaces only go in
|
|
414
|
+
# front of a "/command" (they would hide it from dispatch), so
|
|
415
|
+
# pasted indented code keeps its first line's indentation. At
|
|
416
|
+
# the end only line breaks go -- a trailing space is part of a
|
|
417
|
+
# sentence the user goes on typing.
|
|
418
|
+
lines = typed.split("\n")
|
|
419
|
+
while lines and not lines[0].strip():
|
|
420
|
+
lines.pop(0)
|
|
421
|
+
if lines and lines[0].lstrip().startswith("/"):
|
|
422
|
+
lines[0] = lines[0].lstrip()
|
|
423
|
+
typed = "\n".join(lines).rstrip("\n")
|
|
424
|
+
return typed if typed.strip() else ""
|
|
425
|
+
|
|
396
426
|
def _ask_question_blocking(self) -> None:
|
|
397
427
|
"""Answer the pending question on the main thread.
|
|
398
428
|
|