fix(issue-30): review ad3d8206 - a prompt glyph inside a draft is draft text; the R4 regression test tells the defect apart
- input_region: in the prompt-line shape the input box starts only at a column-0 prompt (codex indents a draft's continuation lines), and the prompt and the (whole-line) placeholder are stripped from the box's first non-blank line only, so `› Explain this terminal prompt:\n ›` is not empty. - test_r4 runs with S=40 again: the defective code nudged ~30 s after a missed transition, which S=10 could not tell from the fixed ~10 s. Both new checks fail against the defective versions (stall_watch.py at47e4274-> 30.24 s < 40; input_region.py atbf0f5c1-> nudge sent) and pass now, each with its passing twin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bf0f5c1014
commit
9ec0569401
2 files changed
+62
-12
No files matched your search
@@ -11,12 +11,13 @@ def extract_input_region(pane_text: str, agent_name: str) -> str:
|
||||
1. an `input_block_prefix` -> the last contiguous block of lines starting
|
||||
with it, prefix removed, cut at the first `input_stop_pattern` line;
|
||||
2. two or more `input_rule_pattern` rules -> the lines between the last two;
|
||||
3. an `input_prompt` -> from the last line starting with the prompt to
|
||||
3. an `input_prompt` -> from the last line starting (column 0) with the prompt to
|
||||
the end of the pane, minus a last non-blank line matching
|
||||
`input_stop_pattern` (the status footer). Every other line below the
|
||||
prompt line is input: a multi-line draft is never cut to its first line.
|
||||
|
||||
Then `input_frame_chars`, the prompt and the placeholder are stripped. There
|
||||
Then `input_frame_chars` are stripped, and the prompt and the placeholder
|
||||
(whole-line) are stripped from the box's first non-blank line only. There
|
||||
is no whole-pane fallback: a pane with none of these shapes has no input
|
||||
region and yields a non-empty "unknown" result rather than a guess.
|
||||
Raises ValueError for agents without input area facts (like hermes' peers).
|
||||
@@ -52,7 +53,10 @@ def extract_input_region(pane_text: str, agent_name: str) -> str:
|
||||
region_lines = lines[rule_indices[-2] + 1:rule_indices[-1]]
|
||||
|
||||
if region_lines is None:
|
||||
starts = [i for i, line in enumerate(lines) if line.lstrip().startswith(prompt)]
|
||||
# The composer's prompt sits at column 0; a draft's continuation lines
|
||||
# are indented, so an indented prompt character is draft text (review
|
||||
# ad3d8206), never the start of the input box.
|
||||
starts = [i for i, line in enumerate(lines) if line.startswith(prompt)]
|
||||
if starts:
|
||||
region_lines = [line for line in lines[starts[-1]:]]
|
||||
while region_lines and not region_lines[-1].strip():
|
||||
@@ -68,11 +72,15 @@ def extract_input_region(pane_text: str, agent_name: str) -> str:
|
||||
if frame:
|
||||
region_lines = [line.strip().strip(frame) for line in region_lines]
|
||||
|
||||
region_text = "\n".join(region_lines)
|
||||
region_text = re.sub(r'^\s*' + re.escape(prompt) + r'\s*', '', region_text, flags=re.MULTILINE)
|
||||
# The prompt and the placeholder belong to the box's first line only: the
|
||||
# same characters further down are what the user typed.
|
||||
region_lines = list(region_lines)
|
||||
first = next((i for i, line in enumerate(region_lines) if line.strip()), None)
|
||||
if first is not None:
|
||||
head = re.sub(r'^\s*' + re.escape(prompt) + r'\s*', '', region_lines[first], count=1)
|
||||
placeholder = adapter.input_placeholder
|
||||
if placeholder and head.strip() == placeholder:
|
||||
head = ''
|
||||
region_lines[first] = head
|
||||
|
||||
placeholder = adapter.input_placeholder
|
||||
if placeholder:
|
||||
region_text = region_text.replace(placeholder, '')
|
||||
|
||||
return region_text.strip()
|
||||
return "\n".join(region_lines).strip()
|
||||
@@ -676,7 +676,7 @@ def test_r4_a_missed_transition_restarts_the_idle_window(mam_sandbox, mock_herdr
|
||||
wait mock. process-info retains its measured nested result shape.
|
||||
"""
|
||||
reg = mam_sandbox / ".mam" / "jobs"
|
||||
env = _env(mam_sandbox, MAM_STALL_IDLE_S=10)
|
||||
env = _env(mam_sandbox, MAM_STALL_IDLE_S=40)
|
||||
jid = _job(env, reg)
|
||||
agent = _proc("exec sleep 300")
|
||||
observed = mam_sandbox / "observed.json"
|
||||
@@ -729,7 +729,10 @@ def test_r4_a_missed_transition_restarts_the_idle_window(mam_sandbox, mock_herdr
|
||||
w.terminate()
|
||||
w.communicate(timeout=10)
|
||||
idle_time = times['prompted'] - times.get('changed', times['started'])
|
||||
assert idle_time >= 10, {'continuous_idle_seconds': idle_time, 'threshold': 10}
|
||||
# S must exceed the 30 s re-judgement interval: with the old code the nudge came
|
||||
# ~30 s after the missed transition, which only fails the assertion when S > 30
|
||||
# (review ad3d8206; S=10 passed on the defective code too).
|
||||
assert idle_time >= 40, {'continuous_idle_seconds': idle_time, 'threshold': 40}
|
||||
finally:
|
||||
if w is not None and w.poll() is None:
|
||||
w.terminate()
|
||||
@@ -745,3 +748,42 @@ def test_r1_the_codex_extractor_keeps_every_line_under_the_prompt():
|
||||
assert extract_input_region(draft, "codex") == "draft on the second input line"
|
||||
no_footer = "\n".join(idle.splitlines()[:-1]).replace("› Ask Codex to do anything", "›\n a · b · c")
|
||||
assert extract_input_region(no_footer, "codex") != "", "only the last line may be the footer"
|
||||
|
||||
|
||||
|
||||
# Review ad3d8206 (planner-reviewer-codex-01): a prompt glyph inside the draft
|
||||
# is draft text, not the start of the input box (the reviewer's reproduction).
|
||||
@pytest.mark.parametrize("draft", [False, True])
|
||||
def test_r1b_a_draft_containing_the_prompt_glyph_is_not_empty(mam_sandbox, mock_herdr, draft):
|
||||
reg = mam_sandbox / ".mam" / "jobs"
|
||||
env = _env(mam_sandbox, MAM_STALL_IDLE_S=1)
|
||||
jid = _job(env, reg)
|
||||
agent = _proc("exec sleep 300")
|
||||
w = None
|
||||
try:
|
||||
pane = _pane("codex")
|
||||
if draft:
|
||||
pane = pane.replace("› Ask Codex to do anything", "› Explain this terminal prompt:\n ›")
|
||||
_agent(mock_herdr, "reviewer-codex-t", "codex", agent.pid, "idle", pane)
|
||||
w = _watch(env, reg, jid, "reviewer-codex-t", "codex")
|
||||
if draft:
|
||||
time.sleep(5)
|
||||
else:
|
||||
assert _until(lambda: _prompts(mock_herdr))
|
||||
prompts = _prompts(mock_herdr)
|
||||
_finish(env, reg, jid, w)
|
||||
assert len(prompts) == (0 if draft else 1), prompts
|
||||
finally:
|
||||
if w is not None and w.poll() is None:
|
||||
w.terminate()
|
||||
w.communicate(timeout=10)
|
||||
_kill(agent)
|
||||
|
||||
|
||||
def test_r1b_the_extractor_strips_the_prompt_from_the_first_line_only():
|
||||
from lib_py.agents.input_region import extract_input_region
|
||||
idle = _pane("codex")
|
||||
for draft in ("› Explain this terminal prompt:\n ›", "›\n ›", "› Ask Codex to do anything\n more"):
|
||||
assert extract_input_region(idle.replace("› Ask Codex to do anything", draft), "codex") != "", draft
|
||||
typed = re.sub(r"^❯\s*$", "❯ x\n ❯", _pane("claude"), count=1, flags=re.M)
|
||||
assert typed != _pane("claude") and extract_input_region(typed, "claude") != ""
|
||||
Reference in new issue
Block a user