fix(events): address Codex review — balance step on step/start-listener throw, restore /goal guard

Codex review of PR-A found three blockers:

- A throwing step/start session-event listener left an unbalanced log
  (turn/start → step/start → turn/end with no step/end), which the invariants
  oracle rejects — masked because that rejection was itself contained as a
  throwing turn/end listener. Fix the root cause in the loop: mark the step open
  BEFORE appending step/start (Session.append pushes before notifying), so the
  outer catch's closeStep() appends the balancing step/end. The test now asserts
  the balanced outcome (stepEnd:1, step/end before turn/end); proven load-bearing
  (revert the reorder → the test goes red with stepEnd:0).
- Reintroduce the /goal-pattern guard deleted in the prior commit, migrated to a
  step/end session-event listener (the surviving step-boundary hook point), with
  a no-tools first step so it exercises the hasSteering continuation override.
- Update packages/core/agent/README.md: step boundaries are no longer agent/*
  emits.
This commit is contained in:
Tianyi Cui
2026-06-30 12:19:18 +08:00
parent 05b75abbca
commit 8df89d8e33
3 changed files with 54 additions and 15 deletions
+6 -1
View File
@@ -382,8 +382,13 @@ async function runTurn(ctx: Context, agent: ReactLoopAgent, handle: LoopHandle,
// turn-start listeners on the first step) joins before the request.
drainSteering(ctx, agent, turn)
session.append('step/start', { turn, step })
// Mark the step open BEFORE the append: Session.append pushes the event
// to the log before notifying session/event listeners, so a THROWING
// step/start listener leaves step/start in the log. Setting stepOpen first
// means the outer catch's closeStep() then appends the balancing step/end
// (turn stays enclosed) instead of stranding an open step under turn/end.
stepOpen = true
session.append('step/start', { turn, step })
const abort = new AbortController()
handle.setAbort(abort)