Response Parse
Opening Explanation
This stage exists to turn the LLM’s raw reply into something the agent can safely act on. The model does not return ready-to-run commands in a guaranteed shape. It may mix prose with structured output, cut off partway through, or mark a task as done in the wrong way. Response Parse is the boundary that says: “Did we get a valid action plan, a completion signal, or feedback we need to send back?” It sits right after the LLM call and before any user handoff or command handling. Without this stage, later parts of the pipeline would have to guess what the model meant, and the agent would be much more brittle.
Main Flow
Terminus2._handle_llm_interaction()(takes one LLM reply and turns it into agent-ready data) sends the reply text into the selected parser.The parser format is chosen earlier at construction time: JSON or XML. That choice matters because the agent wants a strict contract for how commands, analysis, plan, warnings, and completion are expressed.
TerminusJSONPlainParser.parse_response()orTerminusXMLPlainParser.parse_response()(read the model reply and extract the structured pieces) tries to recover a usable result even when the reply is messy.This is why the parser is more than a decoder. It also repairs common model failures:
- JSON parser: incomplete JSON, mixed prose plus JSON
- XML parser: missing closing tags, truncated output The reason is simple: the LLM is useful but not perfectly reliable, and the agent still needs a stable next step.
- The parser returns a
ParseResult: commands, task-complete flag, error, warning, analysis, and plan.This is the core responsibility of the stage. It converts one blob of model text into a small set of decisions the rest of the system can trust.
_handle_llm_interaction()then compresses parse problems into a singlefeedbackstring, using markers likeERROR: ...andWARNINGS: ....This exists so later stages do not need to understand every parser detail. They only need one clear message to send back into the loop if the model output was malformed or questionable.
- Any parsed command is wrapped as a
Command(...), with its duration capped at 60 seconds.The point is not just formatting. It normalizes the LLM’s intent into the agent’s own command object, and it applies a safety limit so one bad duration value does not create an overly long terminal action.
- At the end of this stage, the agent knows one of a few clean outcomes:
- here are valid commands
- the task is complete
- the reply had issues, and here is feedback to give the model That clean split is why this stage exists. It turns ambiguous model output into controlled agent behavior.
State Flow
- writes: 无 — this stage is not described as writing any explicit register from the provided skeleton
- reads: 无 — no explicit register use is identified in the provided stage/register list
- clears: 无 — no explicit register clearing is identified in the provided stage/register list
- triggers downstream:
stage-4.6 Pending Handoff Prompt → User Step (or Split)— after the LLM reply has been parsed into commands / completion / feedback, downstream can decide whether to hand off, ask the user, or split based on that normalized result
Pipeline Hand-Off
Upstream, this stage receives the raw text reply from stage 4.3, the LLM Query. It produces a normalized parse result: safe command objects, a task-complete signal, and a single feedback string for parse problems, which downstream stages use to decide whether to continue, ask for correction, or move toward user-facing handoff.
Terminus2._handle_llm_interactionterminus_2.py:1196–1225 ↗
Parse LLM reply into commands and feedback
Stage context: This region is the parse-and-normalize part of
Terminus2._handle_llm_interaction. It runs after the method has anllm_responseobject and turnsllm_response.contentinto structured outputs for the rest of the method's return tuple.
What this code does
This code asks self._parser to parse llm_response.content, then reshapes the parser result into the method's return values. It builds one feedback string from result.error and result.warning, logs parser warnings through self.logger.debug, converts each parsed command into a Command, and clamps each command duration to at most 60 seconds. It returns the converted commands, completion flag, feedback text, analysis, plan, and the original llm_response object.
Interface · params / IO
(self, llm_response)
- params:
self:?— Owns_parserandloggerused during parse handling;llm_response:?— Response object whosecontentis parsed and which is returned unchanged as the sixth tuple element - reads:
self._parser,self.logger,llm_response.content,result.error,result.warning,result.commands,result.is_task_complete,result.analysis,result.plan - returns: A 6-tuple: (
list[Command]built fromresult.commands,result.is_task_complete, synthesizedfeedbackstring,result.analysis,result.plan, the samellm_responseobject passed in) - effects: Calls
self.logger.debug(...)whenresult.warningis truthy
Execution flow
- Call
self._parser.parse_response(llm_response.content)and store the structured parse output inresult. - Build
feedbackfrom parser diagnostics: start empty, prependERROR: ...whenresult.errorexists, append\nWARNINGS: ...only when bothresult.errorandresult.warningexist, otherwise useWARNINGS: ...alone when there is no error. - If
result.warningis truthy, emitself.logger.debug(f"Parser warnings: {result.warning}"). - Iterate over
result.commands, wrap eachparsed_cmdasCommand(keystrokes=parsed_cmd.keystrokes, duration_sec=min(parsed_cmd.duration, 60)), and collect them incommands. - Return
commands,result.is_task_complete,feedback,result.analysis,result.plan, and the originalllm_responseobject.
Source
result = self._parser.parse_response(llm_response.content)
feedback = ""
if result.error:
feedback += f"ERROR: {result.error}"
if result.warning:
feedback += f"\nWARNINGS: {result.warning}"
elif result.warning:
feedback += f"WARNINGS: {result.warning}"
if result.warning:
self.logger.debug(f"Parser warnings: {result.warning}")
commands = []
for parsed_cmd in result.commands:
commands.append(
Command(
keystrokes=parsed_cmd.keystrokes,
duration_sec=min(parsed_cmd.duration, 60),
)
)
return (
commands,
result.is_task_complete,
feedback,
result.analysis,
result.plan,
llm_response,
)
Non-obvious design decisions
- The feedback builder gives
result.errorpriority by placingERROR: ...first and only adding warnings as a second line when both fields are present. That choice preserves both diagnostics without collapsing them into one label. - The code logs only
result.warning, notresult.error. The implementation treats warnings as something to surface throughself.logger.debugwhile leaving errors only in the returnedfeedbackstring. - Command conversion applies
min(parsed_cmd.duration, 60)at construction time. This enforces a hard upper bound onduration_seceven if the parser returned a larger duration.
Relations
- Callers:
Terminus2._handle_llm_interaction - Core callees:
self._parser.parse_response;self.logger.debug;Command - Config / state sources:
self._parser;self.logger - Results to: Returns parser-derived values to the enclosing
Terminus2._handle_llm_interactioncall result; Exposes the originalllm_responseunchanged as the sixth return element
TerminusJSONPlainParser.parse_responseterminus_json_plain_parser.py:29–62 ↗
Parse response with retry-on-autofix
Stage context: This method turns one raw
responsestring into aParseResult. It first asks_try_parse_response(...)for a direct parse, then only enters its fallback path whenresult.erroris non-empty. The fallback path retries parsing against corrected text candidates from_get_auto_fixes()and returns the first corrected parse whosecorrected_result.erroris exactly"".
What this code does
parse_response parses the input response and returns a ParseResult. It reads result.error from the initial parse result to decide whether to try auto-fixes, and on a successful corrected parse it reads corrected_result.error and corrected_result.warning, then assigns corrected_result.warning to the value returned by _combine_warnings(...). It does not write any self state.
Interface · params / IO
(self, response: str) -> ParseResult
- params:
self:?— parser instance that supplies parse and auto-fix helpers;response:str— full response text to parse - reads:
self._try_parse_response,self._get_auto_fixes,self._combine_warnings - returns: A
ParseResult: either the initialresult, or the firstcorrected_resultwhosecorrected_result.erroris""after an auto-fix attempt. - effects: Assigns
corrected_result.warningon the successful auto-fix path
Execution flow
- Call
self._try_parse_response(response)and store the returnedParseResultinresult. - Check
result.error; if it is non-empty, iterate through(fix_name, fix_function)pairs fromself._get_auto_fixes(). - For each fix, call
fix_function(response, result.error)to getcorrected_responseandwas_fixed, and skip reparsing whenwas_fixedis false. - When
was_fixedis true, callself._try_parse_response(corrected_response)and store thatParseResultincorrected_result. - Accept the corrected parse only when
corrected_result.error == ""; then buildauto_warning, combine it withcorrected_result.warningviaself._combine_warnings(...), assign the combined value back tocorrected_result.warning, and returncorrected_result. - If no attempted fix produces a
corrected_resultwith an empty error string, return the originalresult.
Source
def parse_response(self, response: str) -> ParseResult:
"""
Parse a terminus JSON plain response and extract commands.
Args:
response: The full LLM response string
Returns:
ParseResult with commands, completion status, errors and warnings
"""
# Try normal parsing first
result = self._try_parse_response(response)
if result.error:
# Try auto-fixes in order until one works
for fix_name, fix_function in self._get_auto_fixes():
corrected_response, was_fixed = fix_function(response, result.error)
if was_fixed:
corrected_result = self._try_parse_response(corrected_response)
if corrected_result.error == "":
# Success! Add auto-correction warning
auto_warning = (
f"AUTO-CORRECTED: {fix_name} - "
"please fix this in future responses"
)
corrected_result.warning = self._combine_warnings(
auto_warning, corrected_result.warning
)
return corrected_result
# Return original result if no fix worked
return result
Non-obvious design decisions
- It gates the fallback path on
result.errorinstead of always running fixes. That keeps the normal path simple and avoids changing already-parseable input. - It tests correction success with the exact condition
corrected_result.error == "". That makes acceptance depend on the parser's own error field rather than on whether a fix function reportedwas_fixed. - It returns on the first successful fix from
_get_auto_fixes(). This gives the fix list an ordered priority and avoids merging or comparing multiple corrected parses. - It preserves the original
resultwhen no fix yields an emptycorrected_result.error. That keeps the original parser outcome, including its original error, instead of replacing it with a later failed retry.
Relations
- Callers: External code that needs to parse a raw response string into a
ParseResult; Owning parser users that invokeparse_response(response)on this parser instance - Core callees: self._try_parse_response; self._get_auto_fixes; fix_function(response, result.error); self._combine_warnings
- Config / state sources: self._get_auto_fixes
- Results to: The immediate caller of
parse_response; Code that consumes the returnedParseResult - Related siblings: Related to
Terminus2._handle_llm_interaction, which consumes a parser result and reshapes it for its own return values
TerminusJSONPlainParser._try_parse_responseterminus_json_plain_parser.py:64–163 ↗
JSON response parser and ParseResult builder
Stage context: This helper does the first strict parse attempt inside stage 4.4's response-parse path.
TerminusJSONPlainParser.parse_responsecalls it before deciding whether to apply auto-correction, andTerminus2._handle_llm_interactionlater consumes the returnedParseResult. Compared with its siblingparse_response, this method focuses on extraction, decoding, validation, and command conversion for one raw response string.
What this code does
_try_parse_response turns one LLM response string into a ParseResult. It extracts a JSON fragment, decodes it, checks the expected schema, normalizes task_complete, and converts the commands payload through _parse_commands. It returns structured errors and warnings instead of raising, and it does not mutate any instance state.
Interface · params / IO
(self, response: str) -> ParseResult
- params:
response:str— full LLM response text to extract JSON from and parse - returns: A
ParseResult(commands, is_task_complete, error, warning, analysis, plan)built from the parsed JSON, or a failure result with empty commands and diagnostic text.
Execution flow
- It starts a local
warningslist, then calls_extract_json_content(response)to split outjson_contentand collect format warnings about extra text before or after the JSON. - If
_extract_json_contentfinds nojson_content, it returns a failureParseResultwitherrorset to"No valid JSON found in response"and the accumulated warnings joined into the bullet-list warning string. - It tries
json.loads(json_content). Onjson.JSONDecodeError, it returns a failureParseResultwhoseerrorincludes the exception text plus either the fulljson_contentor a 100-character preview, depending on length. - It calls
_validate_json_structure(parsed_data, json_content, warnings). If that helper returnsvalidation_error, it stops and returns a failureParseResult, again preserving any warnings already accumulated. - It reads
parsed_data.get("task_complete", False)intois_completeand coerces string forms like"true","1", and"yes"to booleans. It also pullsanalysisandplanfromparsed_datawith empty-string defaults. - It reads
commands_data = parsed_data.get("commands", [])and passes it to_parse_commands(commands_data, warnings). If_parse_commandsreportsparse_error, the method either downgrades that problem into an added warning and returnsis_task_complete=Truewith no commands whenis_completeis already true, or returns the parse error as a hard failure when completion is false. - If command parsing succeeds, it returns a success
ParseResultwith the parsedcommands, the normalizedis_completeflag, noerror, the joined warning text, and the extractedanalysisandplan.
Source
def _try_parse_response(self, response: str) -> ParseResult:
"""
Try to parse a terminus JSON plain response.
Args:
response: The full LLM response string
Returns:
ParseResult with commands, completion status, errors and warnings
"""
warnings = []
# Check for extra text before/after JSON
json_content, extra_text_warnings = self._extract_json_content(response)
warnings.extend(extra_text_warnings)
if not json_content:
return ParseResult(
[],
False,
"No valid JSON found in response",
"- " + "\n- ".join(warnings) if warnings else "",
"",
"",
)
# Parse JSON
try:
parsed_data = json.loads(json_content)
except json.JSONDecodeError as e:
# Add debug info
error_msg = f"Invalid JSON: {str(e)}"
if len(json_content) < 200:
error_msg += f" | Content: {repr(json_content)}"
else:
error_msg += f" | Content preview: {repr(json_content[:100])}..."
return ParseResult(
[],
False,
error_msg,
"- " + "\n- ".join(warnings) if warnings else "",
"",
"",
)
# Validate structure
validation_error = self._validate_json_structure(
parsed_data, json_content, warnings
)
if validation_error:
return ParseResult(
[],
False,
validation_error,
"- " + "\n- ".join(warnings) if warnings else "",
"",
"",
)
# Check if task is complete
is_complete = parsed_data.get("task_complete", False)
if isinstance(is_complete, str):
is_complete = is_complete.lower() in ("true", "1", "yes")
# Extract analysis and plan for reasoning content
analysis = parsed_data.get("analysis", "")
plan = parsed_data.get("plan", "")
# Parse commands
commands_data = parsed_data.get("commands", [])
commands, parse_error = self._parse_commands(commands_data, warnings)
if parse_error:
# If task is complete, parse errors are just warnings
if is_complete:
warnings.append(parse_error)
return ParseResult(
[],
True,
"",
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
return ParseResult(
[],
False,
parse_error,
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
return ParseResult(
commands,
is_complete,
"",
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
Non-obvious design decisions
- It separates hard failures from recoverable format issues by collecting
warningsalongside normal parsing. This lets_extract_json_content(...)and_parse_commands(...)report extra text or non-fatal command issues without discarding the whole response. - It embeds
json_contentor a preview in theJSONDecodeErrorpath. That choice favors diagnosis of malformed model output; the size check (len(json_content) < 200) limits how much bad content the error string carries. - It coerces string
task_completevalues to booleans because model output may not honor the expected JSON boolean type exactly. Without this branch, values like"true"would pass schema extraction but drive the completion logic incorrectly. - It treats
parse_errorfrom_parse_commandsas a warning whenis_completeis already true. That trade-off prefers honoring an explicit completion signal over rejecting the whole response for unusable commands that no longer matter.
Relations
- Callers:
TerminusJSONPlainParser.parse_response - Core callees:
self._extract_json_content;json.loads;self._validate_json_structure;self._parse_commands;ParseResultconstructor - Config / state sources:
responseargument; parsed JSON fieldstask_complete,analysis,plan, andcommands; warnings accumulated from_extract_json_contentand_parse_commands - Results to:
TerminusJSONPlainParser.parse_response, which may retry with auto-correction and combine warnings;Terminus2._handle_llm_interaction, which mapserrorandwarninginto one feedback string and wraps parsed commands intoCommandobjects - Related siblings:
TerminusJSONPlainParser.parse_responsewraps this method with auto-fix retries and warning combination;Terminus2._handle_llm_interactionconsumes this method'sParseResultshape during the same stage
TerminusJSONPlainParser._extract_json_contentterminus_json_plain_parser.py:165–212 ↗
Extract first balanced JSON object from reply text
Stage context: This helper lives under the stage's response-parsing code and handles one narrow task: isolate a JSON object from a raw
responsestring. The body shows no instance-state access and no downstream work beyond returning extracted text plus warning strings. It is a local utility for the parser methods in this class.
What this code does
_extract_json_content scans response and returns the first top-level {...} segment whose braces balance outside quoted strings. It also reports whether trimmed text exists before or after that segment. On failure, it returns an empty string and exactly ["No valid JSON object found"]. It does not mutate self or any external state.
Interface · params / IO
(self, response: str) -> tuple[str, List[str]]
- params:
self:?— parser instance; unused in this body;response:str— raw text to scan for one complete top-level JSON object - returns: A
(json_text, warnings)tuple. On success,json_textisresponse[json_start:json_end]andwarningsmay include"Extra text detected before JSON object"ifresponse[:json_start].strip()is non-empty and/or"Extra text detected after JSON object"ifresponse[json_end:].strip()is non-empty. On failure, it returns""and exactly["No valid JSON object found"]when eitherjson_start == -1orjson_end == -1after the scan.
Execution flow
- Initialize local scan state:
warnings,json_start,json_end,brace_count,in_string, andescape_next. - Walk
responsewithenumerate(response). Ifescape_nextwas set by the prior character, clear it andcontinueimmediately, so the current character does not reach quote or brace handling. - When the current
charis"\\", setescape_next = Trueandcontinue, so that backslash itself does not affectin_stringor brace counting on this iteration. - When the current
charis"and the code has not already continued, togglein_stringandcontinue, so quote characters only change string state and do not count as structure. - Only while
not in_string, treat{and}as JSON structure: the first{seen atbrace_count == 0setsjson_start; every{incrementsbrace_count; every}decrements it; when a}bringsbrace_countback to0andjson_start != -1, setjson_end = i + 1and stop scanning withbreak. - After the loop, fail if either boundary was never found by returning
""and["No valid JSON object found"]. - On success, compute
before_text = response[:json_start].strip()andafter_text = response[json_end:].strip(), append the exact warning strings for any non-empty surrounding text, and return the sliced JSON text pluswarnings.
Source
def _extract_json_content(self, response: str) -> tuple[str, List[str]]:
"""Extract JSON content from response, handling extra text."""
warnings = []
# Try to find JSON object boundaries
json_start = -1
json_end = -1
brace_count = 0
in_string = False
escape_next = False
for i, char in enumerate(response):
if escape_next:
escape_next = False
continue
if char == "\\":
escape_next = True
continue
if char == '"' and not escape_next:
in_string = not in_string
continue
if not in_string:
if char == "{":
if brace_count == 0:
json_start = i
brace_count += 1
elif char == "}":
brace_count -= 1
if brace_count == 0 and json_start != -1:
json_end = i + 1
break
if json_start == -1 or json_end == -1:
return "", ["No valid JSON object found"]
# Check for extra text
before_text = response[:json_start].strip()
after_text = response[json_end:].strip()
if before_text:
warnings.append("Extra text detected before JSON object")
if after_text:
warnings.append("Extra text detected after JSON object")
return response[json_start:json_end], warnings
Non-obvious design decisions
- The scan tracks
in_stringandescape_nextseparately, so braces only count when they appear outside quoted text. This choice makesbrace_countreflect top-level structure instead of literal characters inside JSON strings. - The code uses immediate
continueafter both the prior-escape case and the backslash case. That ordering ensures an escaped quote does not togglein_string, and a backslash character does not also enter the quote or brace branches in the same iteration. - It stops at the first complete object by
breaking as soon asbrace_countreturns to zero afterjson_startwas set. That favors a single extracted object over trying to parse multiple JSON regions from oneresponse. - It treats surrounding prose as warnings, not failure, by checking
before_textandafter_textonly after a valid object boundary pair exists. By contrast, missing boundaries produce the single hard failure message"No valid JSON object found".
Relations
- Callers:
TerminusJSONPlainParsermethods in this class that need a JSON substring from raw text - Core callees:
enumerate(response)for ordered character scanning;response[:json_start].strip()to detect leading non-JSON text;response[json_end:].strip()to detect trailing non-JSON text - Config / state sources: none; the body reads no
self._*attributes or external registers - Results to: Returns
json_textfor later JSON decoding by its caller; Returns warning strings describing surrounding non-JSON text or total extraction failure - Related siblings:
TerminusJSONPlainParser._try_parse_responseperforms the later decode and schema checks after extraction;TerminusJSONPlainParser.parse_responsewraps lower-level parse attempts and warning combination
TerminusJSONPlainParser._validate_json_structureterminus_json_plain_parser.py:214–249 ↗
Validate decoded JSON response schema
Stage context: This helper is part of the stage-4.4 response-parse path.
TerminusJSONPlainParser._try_parse_responsecalls it after_extract_json_contenthas found a JSON fragment andjson.loads(...)has decoded it. In the JSON parser sibling flow, this method handles schema-level checks before_try_parse_responsenormalizestask_completeand passescommandsto_parse_commands.
What this code does
_validate_json_structure checks whether decoded data matches the parser's expected top-level JSON shape. It inspects data, json_content, and the parser configuration in self.required_fields, returns a non-empty error string for hard failures, and returns "" when validation can continue. It also appends non-fatal issues to the caller-provided warnings list and delegates field-order checks to _check_field_order.
Interface · params / IO
(self, data: dict, json_content: str, warnings: List[str]) -> str
- params:
data:dict— decoded JSON payload to validate;json_content:str— raw JSON text, used for field-order checks;warnings:List[str]— mutable warning accumulator shared with the caller - reads:
self.required_fields - returns: A validation error string for hard schema failures, or
""if the structure is acceptable enough to continue parsing. - effects: appends warning messages to
warnings; callsself._check_field_order(data, json_content, warnings)
Execution flow
- It first rejects any non-dictionary
dataand returns"Response must be a JSON object"immediately. - It checks every name in
self.required_fields, collects missing ones intomissing_fields, and returns a single"Missing required fields: ..."error if any are absent. - It treats
analysisandplanas soft checks: ifdata.get("analysis", "")ordata.get("plan", "")is not astr, it appends a warning instead of failing. - It reads
commands = data.get("commands", [])and hard-fails with"Field 'commands' must be an array"if that value is not alist. - It invokes
self._check_field_order(data, json_content, warnings)so field-order issues become warnings tied to the original JSON text. - It validates
task_completeonly whendata.get("task_complete")is notNone; if present and not aboolorstr, it appends a warning, then returns""to signal that hard validation passed.
Source
def _validate_json_structure(
self, data: dict, json_content: str, warnings: List[str]
) -> str:
"""Validate the JSON structure has required fields."""
if not isinstance(data, dict):
return "Response must be a JSON object"
# Check for required fields
missing_fields = []
for field in self.required_fields:
if field not in data:
missing_fields.append(field)
if missing_fields:
return f"Missing required fields: {', '.join(missing_fields)}"
# Validate field types
if not isinstance(data.get("analysis", ""), str):
warnings.append("Field 'analysis' should be a string")
if not isinstance(data.get("plan", ""), str):
warnings.append("Field 'plan' should be a string")
commands = data.get("commands", [])
if not isinstance(commands, list):
return "Field 'commands' must be an array"
# Check for correct order of fields (analysis, plan, commands)
self._check_field_order(data, json_content, warnings)
# Validate task_complete if present
task_complete = data.get("task_complete")
if task_complete is not None and not isinstance(task_complete, (bool, str)):
warnings.append("Field 'task_complete' should be a boolean or string")
return ""
Non-obvious design decisions
- It splits schema problems into hard errors and warnings. The code returns immediately for top-level shape, missing required fields, and non-list
commands, but only appends warnings foranalysis,plan, andtask_completetype mismatches. That choice lets_try_parse_responsekeep useful command data when descriptive fields are malformed. - It accepts
task_completeas eitherboolorstrviaisinstance(task_complete, (bool, str)). This is broader than strict JSON-schema typing, but it matches the parser pipeline where_try_parse_responselater normalizes that field instead of rejecting the whole response. - It guards the
task_completetype check withtask_complete is not None. That preserves omission as acceptable while still flagging present-but-wrong values, avoiding a warning for missing optional data. - It validates field order through
_check_field_order(data, json_content, warnings)using both parseddataand rawjson_content. That separates semantic validation from presentation-order checks, and it keeps ordering issues non-fatal.
Relations
- Callers:
TerminusJSONPlainParser._try_parse_response - Core callees:
TerminusJSONPlainParser._check_field_order - Config / state sources:
self.required_fields - Results to: returned error string is consumed by
TerminusJSONPlainParser._try_parse_response; mutatedwarningslist flows into theParseResult.warningassembly inTerminusJSONPlainParser._try_parse_responseandTerminusJSONPlainParser.parse_response - Related siblings:
TerminusJSONPlainParser.parse_responseretries parsing and combines warnings around this validation step;TerminusJSONPlainParser._try_parse_responseperforms decode, calls this validator, then normalizestask_completeand parsescommands;TerminusJSONPlainParser._extract_json_contentprovides thejson_contentstring that this function uses for field-order checks;Terminus2._handle_llm_interactionlater turns parsererrorandwarningoutputs into user-facingfeedback
TerminusJSONPlainParser._check_field_orderterminus_json_plain_parser.py:352–393 ↗
JSON required-field presentation order checker
Stage context: This helper performs one narrow validation on the raw
responsetext: it checks whether the keysanalysis,plan, andcommandsappear in that textual order. The provided sibling synopsis forTerminusJSONPlainParser._validate_json_structurestates that it delegates field-order checks to_check_field_order, so this function contributes a soft warning during JSON structure validation rather than producing parse data itself.
What this code does
_check_field_order inspects response for quoted key occurrences of analysis, plan, and commands, then compares their observed text order against the fixed expected_order. It uses warnings as its real output: it may append at most one human-readable warning, and only when at least two of those target fields are found and their observed order differs from expected_present. It does not return a value and does not mutate any self attributes.
Interface · params / IO
(self, data: dict, response: str, warnings: List[str]) -> None
- params:
self:?— parser instance; present but not read for anyself.state here;data:dict— parsed JSON object; accepted by signature but unused in this body;response:str— original response text searched for key positions withre.search(...);warnings:List[str]— mutable warning sink; may receive at most one appended message if order is wrong - returns: Returns
None; its real product is a possible single appended warning inwarnings. - effects: May call
warnings.append(...)once, but only if at least two target fields are found andactual_order != expected_present.
Execution flow
- It fixes the target sequence in
expected_order = ["analysis", "plan", "commands"]and initializespositions = {}. - For each field in
expected_order, it builds the regexpattern = f'"({field})"\\s*:', searchesresponsewithre.search(pattern, response), and recordsmatch.start()inpositions[field]when found. - It stops early with
returnwhenlen(positions) < 2, because there are not enough discovered fields to compare order. - It builds
present_fieldsby iterating throughexpected_orderagain and collecting(field, positions[field])only for fields that were found. - It sorts
present_fieldsby the recorded position to deriveactual_order, and separately derivesexpected_presentby filteringexpected_orderdown to the fields present inpositions. - If
actual_order != expected_present, it formats both orders with" → ".join(...)and appends one warning string towarnings.
Source
def _check_field_order(
self, data: dict, response: str, warnings: List[str]
) -> None:
"""Check if fields appear in the correct order: analysis, plan, commands."""
# Expected order for required fields
expected_order = ["analysis", "plan", "commands"]
# Find positions of each field in the original response
positions = {}
for field in expected_order:
# Look for the field name in quotes
pattern = f'"({field})"\\s*:'
match = re.search(pattern, response)
if match:
positions[field] = match.start()
# Check if we have at least 2 fields to compare order
if len(positions) < 2:
return
# Get fields that are present, in the order they appear
present_fields = []
for field in expected_order:
if field in positions:
present_fields.append((field, positions[field]))
# Sort by position to get actual order
actual_order = [
field for field, pos in sorted(present_fields, key=lambda x: x[1])
]
# Get expected order for present fields only
expected_present = [f for f in expected_order if f in positions]
# Compare orders
if actual_order != expected_present:
actual_str = " → ".join(actual_order)
expected_str = " → ".join(expected_present)
warnings.append(
f"Fields appear in wrong order. Found: {actual_str}, "
f"expected: {expected_str}"
)
Non-obvious design decisions
- It checks order against the raw
responsetext viare.search(...)andmatch.start()instead of relying ondata. That choice preserves the original presentation order, which a parseddictparameter does not expose in this function. - It compares only the subset in
expected_presentrather than requiring all three keys. That lets it warn about misordering among whichever target fields were actually found, while skipping cases where fewer than two positions exist. - It records the issue through
warnings.append(...)instead of raising or returning an error value. The code treats wrong field order as a soft formatting problem, not as a hard failure.
Relations
- Callers:
TerminusJSONPlainParser._validate_json_structuredelegates field-order checks here according to the provided sibling synopsis - Core callees:
re.searchto locate each quoted field name inresponse;sorted(..., key=lambda x: x[1])to order found fields by position;warnings.append(...)to report one mismatch message;" → ".join(...)to format found and expected orders - Config / state sources: Local constant
expected_orderdefines the only accepted sequence:analysis,plan,commands; Argumentresponsesupplies the raw text searched for field positions; Argumentwarningssupplies the mutable destination for any soft validation message; Argumentdatais part of the interface but unused in this implementation - Results to:
warningslist passed by the caller; Caller-visibleNonereturn after optional warning append - Related siblings:
TerminusJSONPlainParser._validate_json_structure;TerminusJSONPlainParser._try_parse_response;TerminusJSONPlainParser.parse_response
TerminusJSONPlainParser._parse_commandsterminus_json_plain_parser.py:251–303 ↗
Validate command objects into parsed command records
Stage context: This helper sits inside the stage-4.4 response-parsing path.
TerminusJSONPlainParser._try_parse_responsecalls it after JSON decoding and top-level schema checks to turn thecommandspayload intoParsedCommandobjects. It complements_validate_json_structureby handling per-command validation, and its warnings flow back throughparse_responseand thenTerminus2._handle_llm_interactioninto the stage feedback string.
What this code does
_parse_commands inspects commands_data, which should be a list of command dictionaries, and converts each valid entry into a ParsedCommand. It returns a pair (commands, error_message), where any hard validation failure returns [] plus a non-empty error string, and successful parsing returns the built command list plus "". It also appends non-fatal notices into the caller-supplied warnings list for missing or invalid duration, unknown fields, and missing trailing newlines before later commands. The function does not read or write any self state.
Interface · params / IO
(self, commands_data: List[dict], warnings: List[str]) -> tuple[List[ParsedCommand], str]
- params:
self:?— parser instance; unused for state access in this method;commands_data:List[dict]— decoded JSONcommandspayload to validate and convert;warnings:List[str]— caller-owned warning accumulator that this method appends to - returns: A
(commands, error_message)tuple. On success,commandsis aList[ParsedCommand]anderror_messageis""; on hard validation failure,commandsis[]anderror_messageexplains the first fatal issue found. - effects: appends human-readable warning strings to the passed-in
warningslist
Execution flow
- Initialize an empty
commandslist, then walkcommands_datawithenumerateso each message can nameCommand {i + 1}. - For each
cmd_data, reject non-dict entries immediately with([], f"Command {i + 1} must be an object"), then require akeystrokeskey and requirecmd_data["keystrokes"]to be a string. - Read optional
duration: if the key is missing, append a warning and use1.0; if present but not anintorfloat, append a warning and also fall back to1.0. - Compute
unknown_fieldsasset(cmd_data.keys()) - {"keystrokes", "duration"}and append a warning when extra keys are present. - If this is not the last command and
keystrokesdoes not end with"\n", append a warning about line concatenation risk across adjacent commands. - Create
ParsedCommand(keystrokes=keystrokes, duration=float(duration)), append it tocommands, and after the loop return(commands, "").
Source
def _parse_commands(
self, commands_data: List[dict], warnings: List[str]
) -> tuple[List[ParsedCommand], str]:
"""Parse commands array into ParsedCommand objects."""
commands = []
for i, cmd_data in enumerate(commands_data):
if not isinstance(cmd_data, dict):
return [], f"Command {i + 1} must be an object"
# Check for required keystrokes field
if "keystrokes" not in cmd_data:
return [], f"Command {i + 1} missing required 'keystrokes' field"
keystrokes = cmd_data["keystrokes"]
if not isinstance(keystrokes, str):
return [], f"Command {i + 1} 'keystrokes' must be a string"
# Parse optional fields with defaults
if "duration" in cmd_data:
duration = cmd_data["duration"]
if not isinstance(duration, (int, float)):
warnings.append(
f"Command {i + 1}: Invalid duration value, using default 1.0"
)
duration = 1.0
else:
warnings.append(
f"Command {i + 1}: Missing duration field, using default 1.0"
)
duration = 1.0
# Check for unknown fields
known_fields = {"keystrokes", "duration"}
unknown_fields = set(cmd_data.keys()) - known_fields
if unknown_fields:
warnings.append(
f"Command {i + 1}: Unknown fields: {', '.join(unknown_fields)}"
)
# Check for newline at end of keystrokes if followed by another command
if i < len(commands_data) - 1 and not keystrokes.endswith("\n"):
warnings.append(
f"Command {i + 1} should end with newline when followed "
"by another command. Otherwise the two commands will be "
"concatenated together on the same line."
)
commands.append(
ParsedCommand(keystrokes=keystrokes, duration=float(duration))
)
return commands, ""
Non-obvious design decisions
- It treats
keystrokesas a hard requirement but treatsdurationas recoverable. The branches around"keystrokes"andisinstance(keystrokes, str)abort immediately, while bad or missingdurationonly adds towarningsand falls back to1.0. That choice preserves executable command text when timing metadata is weak. - It stops at the first fatal command error instead of collecting every structural problem. The early
return [], ...branches keep downstream code from receiving a partially parsed command list, at the cost of less comprehensive error reporting in one pass. - It warns about unknown keys instead of rejecting them. The
unknown_fieldscheck makes the parser strict enough to surface schema drift but permissive enough to keep useful commands. - It checks for a trailing newline only when another command follows. The
i < len(commands_data) - 1guard reflects the real hazard: separate command payloads can concatenate on one shell line, so the parser reports this as advisory formatting risk rather than invalid syntax.
Relations
- Callers:
TerminusJSONPlainParser._try_parse_response - Core callees:
enumerateovercommands_data;isinstancefor dict/string/number validation;set(cmd_data.keys())to detect unknown fields;ParsedCommand(...)constructor;float(duration)coercion - Config / state sources:
commands_dataargument supplies the decoded command objects;warningsargument supplies the mutable warning sink shared with parser siblings - Results to: returned
commandspopulateParseResult.commandsinTerminusJSONPlainParser._try_parse_response; returned error string becomes the parse error path inTerminusJSONPlainParser._try_parse_response; appended warnings are carried throughTerminusJSONPlainParser.parse_response; those warnings are later folded intofeedbackbyTerminus2._handle_llm_interaction - Related siblings:
TerminusJSONPlainParser._try_parse_responsecalls this after_validate_json_structurepasses;TerminusJSONPlainParser.parse_responsemay combine this method's warnings with auto-correction warnings;Terminus2._handle_llm_interactionconverts successfulParsedCommandresults intoCommandobjects with duration clamping
TerminusJSONPlainParser._fix_mixed_contentterminus_json_plain_parser.py:330–343 ↗
Mixed-content JSON candidate extractor
Stage context: This helper belongs to the stage's parser-side recovery toolkit. Within this entry's own code, it only searches a
responsestring for brace-delimited candidates and tests them as JSON; it does not inspect parser configuration or mutate parser state.
What this code does
_fix_mixed_content scans response for substrings that look like JSON objects and returns the first candidate that json.loads accepts. It takes error as a parameter but this body never reads it. On success it returns (match, True); if no candidate parses, it returns the original response unchanged with False. It reads no self attributes and writes no state.
Interface · params / IO
(self, response: str, error: str) -> tuple[str, bool]
- params:
self:?— parser instance; unused by this method body;response:str— source text to search for brace-delimited JSON-like substrings;error:str— unused input parameter - returns: A tuple
(text, ok)wheretextis either the first candidate substring accepted byjson.loadsor the originalresponse, andokisTrueon successful extraction andFalseotherwise.
Execution flow
- Build
json_pattern = r"\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}"and callre.findall(json_pattern, response, re.DOTALL)to collect brace-delimited, object-like substrings fromresponse. - Iterate through each
matchfrommatchesand calljson.loads(match)to validate whether that candidate is real JSON. - If
json.loads(match)succeeds for a candidate, return that exactmatchwithTrueimmediately. - If
json.loads(match)raisesjson.JSONDecodeError, ignore that candidate and continue to the next one; the function does not handle other exception types here. - If no candidate survives validation, return the original
responsewithFalse.
Source
def _fix_mixed_content(self, response: str, error: str) -> tuple[str, bool]:
"""Extract JSON from response with mixed content."""
# Look for JSON-like patterns
json_pattern = r"\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}"
matches = re.findall(json_pattern, response, re.DOTALL)
for match in matches:
try:
json.loads(match)
return match, True
except json.JSONDecodeError:
continue
return response, False
Non-obvious design decisions
- It separates candidate extraction from acceptance: the regex only finds object-like text, and
json.loadsmakes the final decision. That avoids trusting the pattern to recognize valid JSON on its own. - The pattern targets brace-delimited substrings, not arbitrary JSON values. This keeps the search focused on object-shaped fragments, but it also means the method is only an approximation for nested structures.
- The
exceptblock catches onlyjson.JSONDecodeErrorand then continues scanning. This choice deliberately treats malformed candidates as normal misses instead of failing the whole recovery attempt.
Relations
- Callers: Unknown from provided code; this entry does not show its caller
- Core callees:
re.findall;json.loads - Config / state sources:
responseargument;errorargument (present but unused) - Results to: Its direct caller, via the returned
(text, ok)tuple
TerminusXMLPlainParser.parse_responseterminus_xml_plain_parser.py:28–60 ↗
Stage context:
What this code does
Source
def parse_response(self, response: str) -> ParseResult:
"""
Parse a terminus XML plain response and extract commands.
Args:
response: The full LLM response string
Returns:
ParseResult with commands, completion status, errors and warnings
"""
# Try normal parsing first
result = self._try_parse_response(response)
if result.error:
# Try auto-fixes in order until one works
for fix_name, fix_function in self._get_auto_fixes():
corrected_response, was_fixed = fix_function(response, result.error)
if was_fixed:
corrected_result = self._try_parse_response(corrected_response)
if corrected_result.error == "":
# Success! Add auto-correction warning
auto_warning = (
f"AUTO-CORRECTED: {fix_name} - "
"please fix this in future responses"
)
corrected_result.warning = self._combine_warnings(
auto_warning, corrected_result.warning
)
return corrected_result
# Return original result if no fix worked
return result
Non-obvious design decisions
Relations
TerminusXMLPlainParser._try_parse_responseterminus_xml_plain_parser.py:62–169 ↗
Parse one XML reply into ParseResult
Stage context: This function is the XML parser's core stage-4.4 worker for turning one LLM reply string into the normalized
ParseResultthat later response-handling code consumes.TerminusXMLPlainParser.parse_responsecalls it before any higher-level feedback mapping happens. In the XML path, it plays the same role thatTerminusJSONPlainParser._try_parse_responseplays for JSON, but it works through XML-specific helpers for wrapper extraction, section discovery, completion detection, and command parsing.
What this code does
_try_parse_response consumes a full XML-formatted response string and returns a ParseResult(commands, is_task_complete, error, warning, analysis, plan). It validates the outer <response> wrapper, extracts section content, detects completion through is_complete, and parses <commands> content into command objects through _parse_xml_commands. It accumulates human-readable warnings in warnings and returns parse failures as error strings instead of raising. It does not mutate any instance state.
Interface · params / IO
(self, response: str) -> ParseResult
- params:
response:str— full LLM reply to parse as Terminus plain XML - returns: a
ParseResultcontaining parsed commands, completion flag, parse error text, warning text, and extractedanalysis/plansection text
Execution flow
- It starts a local
warningslist, then calls_check_extra_text(response, warnings)so any text outside the expected<response>wrapper becomes non-fatal warning output. - It extracts the wrapper body with
_extract_response_content(response); if that returns a falsey value, it stops immediately and returns aParseResultwitherrorset to"No <response> tag found", no commands,Falsecompletion, and any accumulated warnings joined into the bullet-list string format. - It computes
is_completefrom_check_task_complete(response_content), then calls_extract_sections(response_content, warnings)to gather named section payloads and section-related warnings. - It pulls
analysisandplanfromsectionswith default"", then inspectscommands_content = sections.get("commands", "")to decide whether commands are present, empty, or missing. - If
commands_contentis empty but the key"commands"exists insections, it treats that as an empty commands section: whenis_completeis false it appends a specific waiting-related warning, and then returns success-with-no-commands using the currentis_complete,analysis, andplanvalues. - If the
"commands"section is missing entirely, it accepts that only whenis_completeis true; otherwise it returns a hard parse error"Missing <commands> section"with no commands. - When command content exists, it delegates to
_parse_xml_commands(commands_content, warnings); if that returnsparse_error, the function downgrades that error into an added warning whenis_completeis true, but returns it as theerrorfield whenis_completeis false. - If command parsing succeeds, it returns a
ParseResultwith the parsedcommands, the previously computedis_complete, an emptyerror, the joined warning string, and the extractedanalysisandplantext.
Source
def _try_parse_response(self, response: str) -> ParseResult:
"""
Try to parse a terminus XML plain response.
Args:
response: The full LLM response string
Returns:
ParseResult with commands, completion status, errors and warnings
"""
warnings = []
# Check for extra text before/after <response> tags
self._check_extra_text(response, warnings)
# Extract <response> content
response_content = self._extract_response_content(response)
if not response_content:
return ParseResult(
[],
False,
"No <response> tag found",
"- " + "\n- ".join(warnings) if warnings else "",
"",
"",
)
# Check if task is complete first
is_complete = self._check_task_complete(response_content)
# Check for required sections and extract content
sections = self._extract_sections(response_content, warnings)
# Extract analysis and plan for reasoning content
analysis = sections.get("analysis", "")
plan = sections.get("plan", "")
# Extract commands section
commands_content = sections.get("commands", "")
if not commands_content:
if "commands" in sections:
# Commands section exists but is empty
if not is_complete:
warnings.append(
"Commands section is empty; not taking any action. "
"If you want to wait a specific amount of time please use "
"`sleep`, but if you're waiting for a command to finish then "
"continue to wait."
)
return ParseResult(
[],
is_complete,
"",
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
else:
# Commands section is missing entirely
if is_complete:
return ParseResult(
[],
True,
"",
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
return ParseResult(
[],
False,
"Missing <commands> section",
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
# Parse commands directly from XML (no code blocks in plain format)
commands, parse_error = self._parse_xml_commands(commands_content, warnings)
if parse_error:
# If task is complete, parse errors are just warnings
if is_complete:
warnings.append(parse_error)
return ParseResult(
[],
True,
"",
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
return ParseResult(
[],
False,
parse_error,
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
return ParseResult(
commands,
is_complete,
"",
"- " + "\n- ".join(warnings) if warnings else "",
analysis,
plan,
)
Non-obvious design decisions
- It separates hard failures from recoverable format issues by collecting
warningslocally and returning them inParseResult.warning. That keeps stage-4.4 parsing tolerant of wrapper noise and section quirks, while still preserving machine-readable failure state inerrorfor callers that need to gate execution. - It checks
is_completebefore enforcing command validity so a completion-marked reply can omit usable commands. That choice appears in both the missing-<commands>branch and theparse_errorbranch, and it avoids rejecting final-answer messages just because they no longer carry executable actions. - It distinguishes an empty
<commands>section from a missing<commands>section by testing bothcommands_contentand"commands" in sections. This lets the parser give a softer, instructional warning for an explicitly empty section while still treating a truly absent section as malformed unless the task is already complete. - It returns bullet-formatted warning text (
"- " + "\n- ".join(warnings)) at every exit point instead of exposing the raw list. That keeps this function aligned with the stage contract described for later feedback mapping, where downstream code expects warning text rather than parser-internal list structure.
Relations
- Callers:
TerminusXMLPlainParser.parse_response - Core callees:
TerminusXMLPlainParser._check_extra_text;TerminusXMLPlainParser._extract_response_content;TerminusXMLPlainParser._check_task_complete;TerminusXMLPlainParser._extract_sections;TerminusXMLPlainParser._parse_xml_commands - Config / state sources: input parameter
response; localwarningsaccumulator populated by helper calls; section mapping returned by_extract_sections; command parse result returned by_parse_xml_commands - Results to:
TerminusXMLPlainParser.parse_response; stage-4.4 response-parse output contract;_handle_llm_interactionwarning/error to feedback mapping described in the owning stage; later command-wrapping logic that consumesParseResult.commands - Related siblings:
TerminusJSONPlainParser._try_parse_responseis the JSON-path counterpart with the sameParseResulttarget but different extraction and validation strategy.;TerminusXMLPlainParser.parse_responseis the public XML entry point that wraps this lower-level attempt function.
TerminusXMLPlainParser._check_extra_textterminus_xml_plain_parser.py:196–223 ↗
Warn on XML text outside response block
Stage context: This helper belongs to the XML response parser path in
TerminusXMLPlainParser. It runs as a structural check on the rawresponsestring and contributes only warning text, complementing_try_parse_response, which handles the main XML extraction and error reporting.
What this code does
_check_extra_text inspects a raw XML-like response string for non-whitespace text outside the first <response> / </response> block. It takes response and a mutable warnings list, returns None, and communicates solely by appending warning strings to warnings. It adds "Extra text detected before <response> tag" when stripped text exists before the first opening tag, "Extra text detected after </response> tag" when stripped text exists after a found closing tag, and an additional IMPORTANT: warning only when that trailing extra text exists and response.count("<response>") > 1.
Interface · params / IO
(self, response: str, warnings: List[str]) -> None
- params:
self:?— parser instance; this body does not read instance attributes;response:str— raw model output to scan for text before or after<response>tags;warnings:List[str]— mutable accumulator that receives any detected warning messages - returns: None; the real output is mutation of
warnings - effects: Appends
"Extra text detected before <response> tag"ifresponse[:start_pos].strip()is non-empty; Appends"Extra text detected after </response> tag"ifend_pos != -1andresponse[end_pos + len("</response>"):].strip()is non-empty; Appends"IMPORTANT: Only issue one <response> block at a time. You issued {total_response_count} and only the first was executed."only when trailing extra text was detected andresponse.count("<response>") > 1
Execution flow
- It computes both tag positions up front with
response.find("<response>")andresponse.find("</response>")intostart_posandend_pos. - If
start_pos == -1, it returns immediately and emits no warnings from this helper. - It slices
response[:start_pos], strips whitespace intobefore_text, and appends"Extra text detected before <response> tag"when that text is non-empty. - It checks trailing text only when
end_pos != -1: it slices from just after</response>, strips intoafter_text, and appends"Extra text detected after </response> tag"when that text is non-empty. - Inside that same trailing-text branch, it counts opening tags with
response.count("<response>")and appends theIMPORTANT:warning only if the count is greater than 1.
Source
def _check_extra_text(self, response: str, warnings: List[str]) -> None:
"""Check for extra text before/after <response> tags."""
# Find response tag positions
start_pos = response.find("<response>")
end_pos = response.find("</response>")
if start_pos == -1:
return # Will be handled as error later
# Check text before <response>
before_text = response[:start_pos].strip()
if before_text:
warnings.append("Extra text detected before <response> tag")
# Check text after </response> if closing tag exists
if end_pos != -1:
after_text = response[end_pos + len("</response>") :].strip()
if after_text:
warnings.append("Extra text detected after </response> tag")
# Count total <response> tags
total_response_count = response.count("<response>")
if total_response_count > 1:
warnings.append(
f"IMPORTANT: Only issue one <response> block at a time. "
f"You issued {total_response_count} and only the first "
f"was executed."
)
Non-obvious design decisions
- The function treats a missing opening tag as out of scope for warnings: the
if start_pos == -1: returnbranch suppresses all extra-text checks in that case. - It warns about leading text independently of the closing tag, because the
before_textcheck runs as soon as an opening tag exists, while theafter_textcheck is separately gated byif end_pos != -1. - It ties duplicate-
<response>reporting to actual trailing spillover, not merely to multiple openings anywhere in the string, becauseresponse.count("<response>")runs only inside theif after_text:branch.
Relations
- Callers:
TerminusXMLPlainParser._try_parse_response - Core callees:
str.findonresponsefor<response>and</response>;str.stripon the before/after slices;warnings.append;str.countonresponsefor<response> - Config / state sources:
responseargument;warningsargument - Results to: the caller-provided
warningslist;TerminusXMLPlainParser._try_parse_responsewarning accumulation; the XML parser path alongsideTerminusXMLPlainParser.parse_response; sibling XML parse logic that also validates response structure - Related siblings:
TerminusXMLPlainParser.parse_response;TerminusXMLPlainParser._try_parse_response
TerminusXMLPlainParser._extract_sectionsterminus_xml_plain_parser.py:238–318 ↗
Extract and validate XML response sections
Stage context: This helper sits inside stage 4.4's XML response parsing path.
TerminusXMLPlainParser._try_parse_responsecalls it after it has identified the XML-like response body and before later logic interprets section contents such as command XML and completion flags. It complements sibling helpers that check extra wrapper text and parse commands by turning raw section markup into a normalizeddictplus warning messages.
What this code does
_extract_sections scans content for <analysis>, <plan>, <commands>, and <task_complete> blocks and returns a dictionary of the sections it found, with each value stripped to plain inner text. It accepts three encodings for each section: a normal open/close pair, a self-closing tag, or an explicitly empty pair, and the normal pair search uses re.DOTALL so section bodies may span multiple lines. It appends diagnostics to warnings for missing required sections except task_complete, unknown direct-child tags reported by _find_top_level_tags, duplicate expected tags counted over self.required_sections + ["task_complete"], and any ordering issues delegated to _check_section_order.
Interface · params / IO
(self, content: str, warnings: List[str]) -> dict
- params:
content:str— Raw XML-like response text to scan for section tags;warnings:List[str]— Caller-owned list that collects non-fatal validation messages - reads:
self.required_sections,self._find_top_level_tags,self._check_section_order - returns: A
dictmapping found section names to stripped inner text, with empty strings for self-closing or explicitly empty tags - effects: Appends missing-section warnings to
warningsfor required sections other thantask_complete; Appends unknown top-level tag warnings towarningsbased onself._find_top_level_tags(content); Appends duplicate-section warnings towarningswhen counts overself.required_sections + ["task_complete"]exceed one; Passescontentandwarningstoself._check_section_order, which may append ordering warnings
Execution flow
- Initialize
sectionsandfound_sections, then definesection_patternsfor the four supported section names: each name gets a full open/close regex, a self-closing regex, and an explicit empty-pair regex. - For each entry in
section_patterns, try the full open/close pattern first withre.search(full_pattern, content, re.DOTALL); on a match, storematch.group(1).strip()under that section name and mark it infound_sections. - If the full pattern did not match, look for the self-closing form and then the explicit empty form; either case records that section with
""as its value and adds the name tofound_sections. - Compute
missingasset(self.required_sections) - found_sectionsand appendMissing <...> sectionfor each missing required section excepttask_complete, which this branch treats as optional. - Call
self._find_top_level_tags(content)to get direct-child tag names, compare them againstset(self.required_sections + ["task_complete"]), and append anUnknown tag foundwarning for each unexpected top-level tag. - Count duplicates by iterating over
self.required_sections + ["task_complete"]and runningre.findall(f"<{section_name}(?:\\s|>|/>)", content); append a specialIMPORTANT:warning for duplicatecommandstags and a generic multiple-section warning for other names. - Delegate final ordering checks to
self._check_section_order(content, warnings), then return the assembledsectionsdictionary.
Source
def _extract_sections(self, content: str, warnings: List[str]) -> dict:
"""Extract analysis, plan, commands, and task_complete sections."""
sections = {}
found_sections = set()
# Define patterns for each section
section_patterns = {
"analysis": (
r"<analysis>(.*?)</analysis>",
r"<analysis\s*/>",
r"<analysis></analysis>",
),
"plan": (r"<plan>(.*?)</plan>", r"<plan\s*/>", r"<plan></plan>"),
"commands": (
r"<commands>(.*?)</commands>",
r"<commands\s*/>",
r"<commands></commands>",
),
"task_complete": (
r"<task_complete>(.*?)</task_complete>",
r"<task_complete\s*/>",
r"<task_complete></task_complete>",
),
}
for section_name, patterns in section_patterns.items():
full_pattern, self_closing_pattern, empty_pattern = patterns
# Try full pattern first
match = re.search(full_pattern, content, re.DOTALL)
if match:
sections[section_name] = match.group(1).strip()
found_sections.add(section_name)
continue
# Try self-closing pattern
if re.search(self_closing_pattern, content):
sections[section_name] = "" # Self-closing = empty content
found_sections.add(section_name)
continue
# Try empty pattern
if re.search(empty_pattern, content):
sections[section_name] = "" # Empty = empty content
found_sections.add(section_name)
continue
# Check for missing required sections
required = set(self.required_sections)
missing = required - found_sections
for section in missing:
if section != "task_complete": # task_complete is optional
warnings.append(f"Missing <{section}> section")
# Check for unexpected tags at the direct child level of <response> only
# Find all top-level tags (not nested inside other tags)
top_level_tags = self._find_top_level_tags(content)
expected_tags = set(self.required_sections + ["task_complete"])
unexpected = set(top_level_tags) - expected_tags
for tag in unexpected:
warnings.append(
f"Unknown tag found: <{tag}>, expected "
f"analysis/plan/commands/task_complete"
)
# Check for multiple instances of same tag
for section_name in self.required_sections + ["task_complete"]:
tag_count = len(re.findall(f"<{section_name}(?:\\s|>|/>)", content))
if tag_count > 1:
if section_name == "commands":
warnings.append(
f"IMPORTANT: Only issue one <commands> block at a time. "
f"You issued {tag_count} and only the first was executed."
)
else:
warnings.append(f"Multiple <{section_name}> sections found")
# Check for correct order of sections (analysis, plan, commands)
self._check_section_order(content, warnings)
return sections
Non-obvious design decisions
- It recognizes three tag forms per section—full, self-closing, and explicit empty—so callers receive a section key with
""instead of treating empty content as missing. Without the extra self-closing and empty-pattern branches, the function would collapse 'present but empty' into 'not found'. - It searches the full open/close form before empty forms and only stores one value per section name. That choice makes the first full match the extracted payload while leaving duplicate detection to a later counting pass, instead of trying to merge or reconcile repeated sections.
- It exempts
task_completein the missing-section loop even thoughtask_completestill appears in extraction and duplicate checks. The code separates 'may appear and should be parsed if present' from 'must exist to avoid a warning'. - It limits unknown-tag warnings to names returned by
_find_top_level_tags(content)rather than scanning every nested tag. That keeps the warning scope tied to direct children of<response>and avoids flagging nested command markup as an unknown response section. - It iterates duplicate checks over
self.required_sections + ["task_complete"], not over the hard-codedsection_patternskeys alone. That keeps the duplicate-warning scope aligned with configured expected sections while still always checkingtask_complete.
Relations
- Callers:
TerminusXMLPlainParser._try_parse_response - Core callees:
re.search;re.findall;self._find_top_level_tags;self._check_section_order - Config / state sources:
self.required_sections - Results to: Returned
sectionsdict feeds later section interpretation inTerminusXMLPlainParser._try_parse_response; Mutatedwarningslist contributes non-fatal parser diagnostics returned fromTerminusXMLPlainParser._try_parse_response; Extractedcommandssection text is later consumed by XML command parsing in the caller; Extractedtask_completetext is later consumed by completion detection in the caller - Related siblings:
TerminusXMLPlainParser.parse_response;TerminusXMLPlainParser._try_parse_response;TerminusXMLPlainParser._check_extra_text
TerminusXMLPlainParser._check_section_orderterminus_xml_plain_parser.py:442–480 ↗
XML section order warning checker
Stage context: This helper is a local validator for XML-like section ordering. It inspects a raw
contentstring for the opening tags ofanalysis,plan, andcommands, and reports only a warning through the caller-providedwarningslist when the found order disagrees with the fixed expected sequence.
What this code does
_check_section_order scans content for the first opening-tag occurrence of analysis, plan, and commands. For each section, it records match.start() from re.search(f"<{section}(?:\\s|>|/>)", content), so it recognizes <section>, <section ...>, and <section/> forms. It returns None and communicates only by possibly appending one warning to warnings, and it does that only when at least two target sections are present and their observed text order differs from the expected analysis → plan → commands order after filtering to the sections actually found.
Interface · params / IO
(self, content: str, warnings: List[str]) -> None
- params:
self:?— instance parameter; this body does not read instance state;content:str— input string scanned for the first opening-tag match ofanalysis,plan, andcommands;warnings:List[str]— mutable output list that receives one human-readable order warning when two or more matched sections are out of order - returns: None; the real product is a possible
warnings.append(...)side effect - effects: May append
"Sections appear in wrong order. Found: ... expected: ..."towarningsonly when at least two target sections were matched andactual_order != expected_present
Execution flow
- Build
positionsby looping over"analysis","plan", and"commands", runningre.search(f"<{section}(?:\\s|>|/>)", content)for each, and storingpositions[section] = match.start()only for sections whose first opening-tag match exists. - Stop early with
returnwhenlen(positions) < 2, because there are not enough matched sections to compare order. - Define the fixed
expected_order = ["analysis", "plan", "commands"], then buildpresent_sectionsby iterating that expected order and collecting(section, positions[section])pairs only for sections present inpositions. - Sort
present_sectionsby the recorded start offset to produceactual_order, and separately buildexpected_presentby filteringexpected_orderdown to the sections found inpositions. - Compare
actual_orderagainstexpected_present; if they differ, format both sequences with" → ".join(...)and append one warning string towarnings.
Source
def _check_section_order(self, content: str, warnings: List[str]) -> None:
"""Check if sections appear in the correct order: analysis, plan, commands."""
# Find positions of each section
positions = {}
for section in ["analysis", "plan", "commands"]:
# Look for opening tags
match = re.search(f"<{section}(?:\\s|>|/>)", content)
if match:
positions[section] = match.start()
# Check if we have at least 2 sections to compare order
if len(positions) < 2:
return
# Expected order
expected_order = ["analysis", "plan", "commands"]
# Get sections that are present, in the order they appear
present_sections = []
for section in expected_order:
if section in positions:
present_sections.append((section, positions[section]))
# Sort by position to get actual order
actual_order = [
section for section, pos in sorted(present_sections, key=lambda x: x[1])
]
# Get expected order for present sections only
expected_present = [s for s in expected_order if s in positions]
# Compare orders
if actual_order != expected_present:
actual_str = " → ".join(actual_order)
expected_str = " → ".join(expected_present)
warnings.append(
f"Sections appear in wrong order. Found: {actual_str}, "
f"expected: {expected_str}"
)
Non-obvious design decisions
- It uses a regex on opening tags instead of parsing XML structure. The pattern
(?:\s|>|/>)deliberately accepts three syntactic forms—plain open tags, open tags with attributes or whitespace, and self-closing tags—while keeping the check lightweight. - It records only
match.start()fromre.search, so each section's position comes from the first matching opening tag incontent. That choice makes the check a first-occurrence ordering test and ignores later duplicate tags. - It returns immediately when fewer than two sections matched. This avoids emitting a misleading order warning when the code cannot form a meaningful comparison.
- It filters the expected order down to
expected_presentbefore comparing. That means missing sections do not count as an ordering error; only the relative order of sections that were actually found matters. - It builds
present_sectionsby iteratingexpected_orderbefore sorting by position, rather than iteratingpositions.items(). That keeps the comparison anchored to the fixed canonical section list and avoids depending on dictionary insertion order.
Relations
- Callers: external caller not proven from this function's source
- Core callees:
re.search;sorted;warnings.append;str.join - Config / state sources: hard-coded
expected_order = ["analysis", "plan", "commands"]inside this function; hard-coded regex templatef"<{section}(?:\\s|>|/>)"inside this function - Results to: the caller-provided
warningslist; localpositionsmapping; localpresent_sectionslist; localactual_orderandexpected_presentcomparisons - Related siblings:
TerminusXMLPlainParser._extract_sectionsis a related helper mentioned in sibling context, but that call relationship is not proven by this function's source
TerminusXMLPlainParser._check_task_completeterminus_xml_plain_parser.py:514–526 ↗
XML task completion flag detector
Stage context: This helper contributes one small part of stage 4.4 response parsing: it turns the XML
<task_complete>section into the boolean completion flag that ends up in the stage parse result.TerminusXMLPlainParser._try_parse_responseinvokes it while assemblingParseResult(...)from an LLM reply. It complements sibling helpers that extract sections and warnings, but unlike_extract_sectionsor_check_section_order, it produces only a boolean and no diagnostics.
What this code does
_check_task_complete inspects the response_content string for a <task_complete>true</task_complete> tag pair. It returns True only when that tag contains true, ignoring case and surrounding whitespace. For every other form of the section, including missing tags or non-true content, it returns False. It does not read or write any self state.
Interface · params / IO
(self, response_content: str) -> bool
- params:
self:?— parser instance; unused in this body;response_content:str— raw XML-like response text to inspect for task completion - returns: A boolean completion flag:
Trueonly for a matching<task_complete>true</task_complete>marker, otherwiseFalse.
Execution flow
- Search
response_contentwithre.search(...)for the exact tag patternr"<task_complete>\s*true\s*</task_complete>", usingre.IGNORECASEsotruemay vary in case and may be surrounded by whitespace. - If that search returns
true_match, returnTrueimmediately. - If no such match exists, fall through to the default
Falsereturn for all other cases.
Source
def _check_task_complete(self, response_content: str) -> bool:
"""Check if the response indicates the task is complete."""
# Check for <task_complete>true</task_complete>
true_match = re.search(
r"<task_complete>\s*true\s*</task_complete>",
response_content,
re.IGNORECASE,
)
if true_match:
return True
# All other cases (false, empty, self-closing, missing) = not complete
return False
Non-obvious design decisions
- The function accepts only the explicit positive form
<task_complete>true</task_complete>. That keeps completion detection conservative: malformed, empty, self-closing,false, or absent tags all map to the same non-complete result instead of trying to classify several negative or invalid variants. - It uses a direct regex on
response_contentinstead of reusing section extraction state. That keeps this check independent and cheap inside_try_parse_response, at the cost of not distinguishing parse errors from an ordinaryFalsecompletion flag.
Relations
- Callers:
TerminusXMLPlainParser._try_parse_responsewhile buildingParseResult.is_task_complete;TerminusXMLPlainParser.parse_responseindirectly through_try_parse_response - Core callees:
re.search - Config / state sources:
response_contentargument; hard-coded regexr"<task_complete>\s*true\s*</task_complete>"; hard-coded flagre.IGNORECASE - Results to:
ParseResult.is_task_completeproduced byTerminusXMLPlainParser._try_parse_response; stage-4.4 parse output consumed by later completion-handling stages - Related siblings:
TerminusXMLPlainParser._try_parse_response;TerminusXMLPlainParser._extract_sections;TerminusXMLPlainParser._check_extra_text;TerminusXMLPlainParser._check_section_order
TerminusXMLPlainParser._parse_xml_commandsterminus_xml_plain_parser.py:320–391 ↗
Parse XML keystroke command blocks
Stage context: This helper handles the
<commands>payload inside the XML parser's response path.TerminusXMLPlainParser._try_parse_responsecalls it after_extract_sectionshas isolated thecommandssection, and its output becomes thecommandsfield of the stage'sParseResult. Unlike_extract_sections, which finds high-level sections, this function focuses only on repeated<keystrokes ...>...</keystrokes>command entries and adds command-specific warnings.
What this code does
_parse_xml_commands scans xml_content for <keystrokes> elements, converts each match into a ParsedCommand, and returns (commands, ""). It reads each tag's duration attribute, falls back to 1.0 when the attribute is missing or not numeric, and appends human-readable warnings into the caller-supplied warnings list. It also warns about missing trailing newlines between adjacent commands, literal XML entities found in xml_content, and literal "\\r\\n" sequences.
Interface · params / IO
(self, xml_content: str, warnings: List[str]) -> tuple[List[ParsedCommand], str]
- params:
self:?— XML parser instance used only to call_check_attribute_issues;xml_content:str— raw XML-like command section content to scan for<keystrokes>blocks;warnings:List[str]— mutable warning sink that collects non-fatal parse diagnostics - returns: A tuple
(commands, ""), wherecommandsis a list ofParsedCommand(keystrokes, duration)built from matched<keystrokes>elements and the error string is always empty. - effects: Appends warning strings to the provided
warningslist; Callsself._check_attribute_issues(attributes_str, i + 1, warnings)for each matched command tag
Execution flow
- Compile
keystrokes_pattern = re.compile(r"<keystrokes([^>]*)>(.*?)</keystrokes>", re.DOTALL)and usefindall(xml_content)to collect every matched command body plus its raw attribute text. - For each
(attributes_str, keystrokes_content)match, callself._check_attribute_issues(attributes_str, i + 1, warnings)before parsing any known attributes. - Initialize
duration = 1.0, then searchattributes_strforduration\s*=\s*["\']([^"\']*)["\']; if present, convert the captured value withfloat(...), and if that conversion raisesValueError, keep1.0and append an invalid-duration warning. - If the
durationattribute is absent, append a missing-duration warning and keep the default1.0. - For every command except the last one, check
keystrokes_content.endswith("\n"); when it does not, append a warning that the next command will concatenate onto the same line. - Append
ParsedCommand(keystrokes=keystrokes_content, duration=duration)tocommandsfor each match. - After command extraction, scan the full
xml_contentfor each literal entity key inentities = {"<": "<", ">": ">", "&": "&", """: '"', "'": "'"}and append a warning for every entity found. - Check whether the literal substring
"\\r\\n"appears inxml_content; if it does, append a line-ending warning, then returncommands, "".
Source
def _parse_xml_commands(
self, xml_content: str, warnings: List[str]
) -> tuple[List[ParsedCommand], str]:
"""Parse XML content and extract command objects manually."""
# Find all keystrokes elements manually for better error reporting
commands = []
keystrokes_pattern = re.compile(
r"<keystrokes([^>]*)>(.*?)</keystrokes>", re.DOTALL
)
matches = keystrokes_pattern.findall(xml_content)
for i, (attributes_str, keystrokes_content) in enumerate(matches):
# Check for attribute issues
self._check_attribute_issues(attributes_str, i + 1, warnings)
# Parse attributes
duration = 1.0
# Parse duration attribute
duration_match = re.search(
r'duration\s*=\s*["\']([^"\']*)["\']', attributes_str
)
if duration_match:
try:
duration = float(duration_match.group(1))
except ValueError:
warnings.append(
f"Command {i + 1}: Invalid duration value "
f"'{duration_match.group(1)}', using default 1.0"
)
else:
warnings.append(
f"Command {i + 1}: Missing duration attribute, using default 1.0"
)
# Check for newline at end of keystrokes
if i < len(matches) - 1 and not keystrokes_content.endswith("\n"):
warnings.append(
f"Command {i + 1} should end with newline when followed "
f"by another command. Otherwise the two commands will be "
f"concatenated together on the same line."
)
commands.append(
ParsedCommand(keystrokes=keystrokes_content, duration=duration)
)
# Check for XML entities and warn
entities = {
"<": "<",
">": ">",
"&": "&",
""": '"',
"'": "'",
}
for entity, char in entities.items():
if entity in xml_content:
warnings.append(
f"Warning: {entity} is read verbatim and not converted to {char}. "
f"NEVER USE {entity}, unless you want these exact characters to "
f"appear directly in the output."
)
# Check for \r\n line endings and warn
if "\\r\\n" in xml_content:
warnings.append(
"Warning: \\r\\n line endings are not necessary - use \\n "
"instead for simpler output"
)
return commands, ""
Non-obvious design decisions
- It uses a targeted regex over
<keystrokes>tags instead of a full XML parser so it can keep parsing simple command blocks while issuing command-numbered diagnostics tied toattributes_str,keystrokes_content, and adjacency checks. A stricter XML parser would validate structure differently, but it would make these focused warnings harder to phrase in terms of the original command text. - It treats missing or bad
durationas a warning, not a hard error, by initializingduration = 1.0and preserving that value through theexcept ValueErrorbranch. That choice keeps usable commands flowing downstream even when metadata is malformed. - It checks newline termination only when
i < len(matches) - 1, so the warning targets the specific case that changes terminal behavior: one command's text running directly into the next command. It does not require a trailing newline on the last command because there is no following command to concatenate with. - It warns that entity strings like
<and&are read literally instead of decoding them. This matches the function's plain-text extraction model:keystrokes_contentgoes straight intoParsedCommandwithout any entity unescaping, so warning early is safer than silently changing user-visible output.
Relations
- Callers:
TerminusXMLPlainParser._try_parse_response - Core callees:
self._check_attribute_issues;re.compile;re.search;ParsedCommand - Config / state sources: No
self._...configuration or register-backed state is read; Behavior is driven entirely byxml_content,warnings, and hard-coded patterns such askeystrokes_patternandentities - Results to: Returns
commandstoTerminusXMLPlainParser._try_parse_responsefor inclusion inParseResult.commands; Appendedwarningscontinue upward throughTerminusXMLPlainParser._try_parse_responseintoParseResult.warning; SuccessfulParsedCommand.durationvalues later feed stage-4.4 command wrapping intoCommand(..., duration_sec=min(duration, 60)) - Related siblings:
TerminusXMLPlainParser._try_parse_responsedelegates command-section parsing here after_extract_sections;TerminusXMLPlainParser._extract_sectionsfinds the<commands>block that becomes this function'sxml_contentinput;TerminusJSONPlainParser._parse_commandsis the JSON-side analogue: both build parsed command lists and report non-fatal issues through warnings
TerminusXMLPlainParser._check_attribute_issuesterminus_xml_plain_parser.py:482–512 ↗
XML attribute warning checker
Stage context: This helper validates an XML attribute substring by adding human-readable warnings to a caller-supplied list. In the Response Parse stage, it serves as a narrow diagnostic step alongside other XML parser helpers such as
_check_extra_textand_extract_sections, but this function itself only inspectsattributes_strand does not parse whole responses.
What this code does
_check_attribute_issues inspects attributes_str for three advisory problems: unquoted attribute values, single-quoted values, and attribute names outside a hard-coded allowed set. It uses command_num only to label each warning message and appends all findings into the mutable warnings list. It returns None and does not read or write any instance state. The three checks run independently, so one attribute text can produce multiple warnings and the function does not deduplicate them.
Interface · params / IO
(self, attributes_str: str, command_num: int, warnings: List[str]) -> None
- params:
self:?— instance reference; unused by this body;attributes_str:str— raw attribute text to inspect;command_num:int— command identifier interpolated into warning text;warnings:List[str]— mutable sink for diagnostic messages - returns: None; the real output is any warning strings appended to
warnings - effects: appends warning strings to the caller-provided
warningslist
Execution flow
- Compile
unquoted_pattern = re.compile(r'(\w+)\s*=\s*([^"\'\s>]+)'), find all matches inattributes_str, and append one warning per(attr_name, attr_value)saying the value should be written asname="value". - Compile
single_quote_pattern = re.compile(r"(\w+)\s*=\s*'([^']*)'"), find all matches inattributes_str, and append one warning per match telling the caller to use double quotes for that attribute. - Set
known_attributes = {"duration"}, collect every attribute name matched byre.findall(r"(\w+)\s*=", attributes_str), and append an unknown-attribute warning for each name not in that set. - Return
Nonewithout short-circuiting or deduplicating, so warnings from earlier passes remain and later passes can add more messages for the same source text.
Source
def _check_attribute_issues(
self, attributes_str: str, command_num: int, warnings: List[str]
) -> None:
"""Check for attribute-related issues."""
# Check for missing quotes
unquoted_pattern = re.compile(r'(\w+)\s*=\s*([^"\'\s>]+)')
unquoted_matches = unquoted_pattern.findall(attributes_str)
for attr_name, attr_value in unquoted_matches:
warnings.append(
f"Command {command_num}: Attribute '{attr_name}' value should be "
f'quoted: {attr_name}="{attr_value}"'
)
# Check for single quotes (should use double quotes for consistency)
single_quote_pattern = re.compile(r"(\w+)\s*=\s*'([^']*)'")
single_quote_matches = single_quote_pattern.findall(attributes_str)
for attr_name, attr_value in single_quote_matches:
warnings.append(
f"Command {command_num}: Use double quotes for attribute "
f"'{attr_name}': {attr_name}=\"{attr_value}\""
)
# Check for unknown attribute names
known_attributes = {"duration"}
all_attributes = re.findall(r"(\w+)\s*=", attributes_str)
for attr_name in all_attributes:
if attr_name not in known_attributes:
warnings.append(
f"Command {command_num}: Unknown attribute '{attr_name}' - "
f"known attributes are: {', '.join(sorted(known_attributes))}"
)
Non-obvious design decisions
- The function reports problems through
warnings.append(...)instead of raising or returning a status object. That keeps attribute issues advisory in this helper and lets the caller accumulate multiple diagnostics from one input string. - It hard-codes
known_attributes = {"duration"}inside the function. That makes the accepted attribute set explicit at the point of validation, but any new supported XML attributes would require changing this function. - It uses regex heuristics (
unquoted_pattern,single_quote_pattern, andre.findall(r"(\w+)\s*=", ...)) rather than a stricter XML attribute parser. The result is lightweight validation focused on specific formatting and naming issues, not full XML conformance. - The three passes are intentionally independent. Because the unknown-name scan runs over all
name=occurrences and there is no deduplication, a single attribute can trigger both a quoting/style warning and an unknown-attribute warning.
Relations
- Callers: caller that extracts an XML attribute substring and wants advisory diagnostics; XML command parsing path in
TerminusXMLPlainParser - Core callees:
re.compile;Pattern.findall;re.findall;warnings.append;sorted;str.join - Config / state sources:
attributes_strinput text;command_numwarning label input;warningsmutable output list; localknown_attributesconstant - Results to: caller-owned
warningslist; human-readable warning strings labeled withcommand_num; downstream parser diagnostics that consume the accumulatedwarningslist - Related siblings:
TerminusXMLPlainParser._parse_xml_commands;TerminusXMLPlainParser._check_extra_text;TerminusXMLPlainParser._extract_sections