Gate detail for the structured outcome, when the run rests at a gate. A paused or gate-aborted run is otherwise indistinguishable from any other pause/abort in the machine-readable payload; surfacing the gate's prompt, options, and (after an interactive choice) the decision lets orc
(state: Any)
| 202 | |
| 203 | |
| 204 | def _gate_outcome(state: Any) -> dict[str, Any] | None: |
| 205 | """Gate detail for the structured outcome, when the run rests at a gate. |
| 206 | |
| 207 | A paused or gate-aborted run is otherwise indistinguishable from any |
| 208 | other pause/abort in the machine-readable payload; surfacing the gate's |
| 209 | prompt, options, and (after an interactive choice) the decision lets |
| 210 | orchestrators drive review gates without parsing the human-facing stream. |
| 211 | """ |
| 212 | # Two run states rest *on* a gate: `paused` (awaiting a decision) and |
| 213 | # `aborted` (a gate rejected with `on_reject: abort` — the only path that |
| 214 | # sets ABORTED, leaving current_step_id on that gate). Any other status — |
| 215 | # notably `completed`/`failed` — must be suppressed: current_step_id is |
| 216 | # not cleared when a run whose last executed step was a gate moves on, so |
| 217 | # without this guard it would surface stale detail (run/resume/status). |
| 218 | if getattr(state.status, "value", state.status) not in ("paused", "aborted"): |
| 219 | return None |
| 220 | step = (getattr(state, "step_results", None) or {}).get(state.current_step_id) |
| 221 | if not isinstance(step, dict) or not _is_gate_step(step): |
| 222 | return None |
| 223 | output = step.get("output") or {} |
| 224 | # `message`, `options`, and `choice` may be non-string YAML literals in an |
| 225 | # unvalidated workflow (GateStep coerces none of them for the payload), so |
| 226 | # normalise all three for a stable JSON schema: message → str, options → |
| 227 | # list[str] | None, choice → str | None (None means no decision yet). |
| 228 | message = output.get("message") |
| 229 | choice = output.get("choice") |
| 230 | return { |
| 231 | "step_id": state.current_step_id, |
| 232 | "message": None if message is None else str(message), |
| 233 | "options": _normalize_gate_options(output.get("options")), |
| 234 | "choice": None if choice is None else str(choice), |
| 235 | } |
| 236 | |
| 237 | |
| 238 | def _normalize_gate_options(options: Any) -> list[str] | None: |