fix(issue-30-33): review 5ec8ef33 - a multi-line codex draft is not an empty box, blocked notices reach the loop while the job runs, and both clocks are labelled on every close path

- R1 (P1): without rules the input region runs from the last prompt line to
  the end of the pane; only a last line matching the adapter's footer pattern
  (codex: `  <model> · ~/<cwd> · …`) is dropped, so a draft on a second line
  keeps the nudge withheld.
- R2: while the synchronous submit runs, a relay thread finds the job it
  registers (same session, created since the submit) and prints its
  stalled:/stall_watch: notices; before, they appeared only after the job.
- R3: the broker-unavailable disk wait that runs into the deadline is the wall
  clock: wall_clock_timeout (caller rc stays 3).
- R4: a state_change_seq change seen right before the judgement restarts the
  idle window (decision 9: S seconds of continuous idle).

Regression tests are the reviewer's reproductions, each with its passing
twin; the wrapper-level #30 tests now use this environment's broker instead
of the #18 harness's public default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-09-25 14:01:53 +00:00
1 parent 47e42744e7
commit bf0f5c1014
8 files changed
+269 -12

No files matched your search

@@ -51,6 +51,12 @@ class CodexAgentAdapter(BaseAgentAdapter):
def input_placeholder(self) -> Optional[str]:
return 'Ask Codex to do anything'
@property
def input_stop_pattern(self) -> Optional[str]:
# The status footer under the input line: ` <model> <effort> · <cwd> · …`
# (the ` · ~/` or ` · /` of the cwd keeps a draft line with dots from matching)
return r'^\s+\S.*\s\u00b7\s[~/]'
@property
def resident_children(self) -> tuple:
# Kept alive while idle (measured 2026-09-25).
+3 -1
View File
@@ -164,7 +164,9 @@ class BaseAgentAdapter:
@property
def input_stop_pattern(self) -> Optional[str]:
"""Regex of the status line that ends the input area inside such a block (#30)."""
"""Regex of the status line that ends the input area (#30): inside a
block, the first matching line; below a prompt line (no rules), only
the last non-blank line of the pane."""
return None
@property
+15 -4
View File
@@ -11,7 +11,10 @@ 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` -> the last line starting with the prompt.
3. an `input_prompt` -> from the last line starting 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
is no whole-pane fallback: a pane with none of these shapes has no input
@@ -49,9 +52,17 @@ 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 = [line for line in lines if line.lstrip().startswith(prompt)]
# No input line at all: report the whole tail as "not empty" (unknown).
region_lines = starts[-1:] if starts else [pane_text or '?']
starts = [i for i, line in enumerate(lines) if line.lstrip().startswith(prompt)]
if starts:
region_lines = [line for line in lines[starts[-1]:]]
while region_lines and not region_lines[-1].strip():
region_lines.pop()
if (len(region_lines) > 1 and adapter.input_stop_pattern
and re.search(adapter.input_stop_pattern, region_lines[-1])):
region_lines.pop()
else:
# No input line at all: report the whole tail as "not empty" (unknown).
region_lines = [pane_text or '?']
frame = adapter.input_frame_chars
if frame: