Skip to content

feat: add turn_end observer hook#2578

Open
AresNing wants to merge 11 commits into
Hmbown:mainfrom
AresNing:1364-phase2-turn-end
Open

feat: add turn_end observer hook#2578
AresNing wants to merge 11 commits into
Hmbown:mainfrom
AresNing:1364-phase2-turn-end

Conversation

@AresNing
Copy link
Copy Markdown
Contributor

@AresNing AresNing commented Jun 2, 2026

Summary

Adds the Phase 2 turn_end lifecycle hook from #1364 as an observer-only post-turn event.

Scope:

  • Adds HookEvent::TurnEnd / event = "turn_end" discovery via /hooks events.
  • Adds structured observer hook execution that writes JSON to stdin, ignores stdout, logs failures, and never blocks caller state.
  • Fires turn_end from the TUI TurnComplete branch after post-turn state updates and before queued-message dispatch.
  • Documents the config, payload, stdout ignored behavior, continue_on_error observer semantics, and updates the Hooks need mutation rights on user submit and a turn-end event #1364 RFC baseline.
  • Syncs web/package-lock.json so Web Frontend CI can run npm ci after the web docs change.

Not in this slice:

  • No transcript, user text, model response, tool argument, or tool result mutation.
  • No subagent lifecycle hooks; those remain Phase 3.
  • No gating/blocking behavior for turn_end.

Builds on: #2434
Issues: Refs #1364 (partial)

Testing

  • cargo fmt --check
  • cargo check
  • cargo clippy --workspace --all-targets --all-features
  • cargo test -p codewhale-tui hooks::
  • env HOME=/private/tmp/codewhale-test-home-turnend CARGO_HOME=/Users/aresning/.cargo RUSTUP_HOME=/Users/aresning/.rustup cargo test -p codewhale-tui mcp::tests::legacy_sse_closed_stream_reconnects_and_retries_tool_call --all-features
  • env HOME=/private/tmp/codewhale-test-home-turnend CARGO_HOME=/Users/aresning/.cargo RUSTUP_HOME=/Users/aresning/.rustup RUST_TEST_THREADS=1 cargo test --workspace --all-features
  • npm install --package-lock-only --ignore-scripts --registry=https://registry.npmjs.org/ in web/
  • npm ci --ignore-scripts --registry=https://registry.npmjs.org/ in web/
  • npm run lint in web/
  • npx tsc --noEmit in web/
  • git diff --check

Notes:

  • The un-isolated full Rust test hit a readonly default ~/.deepseek/state.db; final full run used isolated HOME.
  • Parallel full Rust runs exposed a local MCP SSE reconnect flake (connection closed before message completed); the exact test passed, and the final single-thread full workspace run passed.
  • Local full npm ci without --ignore-scripts hung in a macOS postinstall path, so dependency/lockfile validation used --ignore-scripts; GitHub Actions runs full npm ci on Ubuntu.
  • Greptile's observer continue_on_error documentation feedback was addressed and the review thread is resolved.

Checklist

  • Updated docs or comments as needed
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes (no visual UI behavior; dispatch behavior covered by hook/unit tests)

Greptile Summary

This PR implements the Phase 2 turn_end observer hook from the #1364 lifecycle RFC. The hook fires after each terminal turn outcome (completed, interrupted, failed), runs asynchronously via tokio::task::spawn_blocking to keep the event loop non-blocking, and delivers a structured JSON payload to hook scripts on stdin while ignoring stdout.

  • Adds HookEvent::TurnEnd, execute_structured_observer, and turn_end_payload in hooks.rs; wires the hook into the EngineEvent::TurnComplete branch of ui.rs after all post-turn state updates and before queued-message dispatch.
  • Documents observer semantics (continue_on_error does not gate later hooks, stdout is ignored, failures are warn-only) in both docs/CONFIGURATION.md and the RFC; surface-level web doc update adds turn_end to the quick-reference comment.
  • Syncs web/package-lock.json to resolve a vitest esbuild version split so Web CI can run npm ci cleanly.

Confidence Score: 5/5

Safe to merge. The observer hook is strictly additive, fire-and-forget, and cannot mutate or block any caller state.

The implementation is architecturally sound: spawn_blocking keeps the Tokio event loop unblocked, has_hooks_for_event correctly gates on the enabled flag, the payload snapshot is taken before queued-message pop so counts are accurate, and the unconditional-iteration / warn-only failure model is both intentional and clearly documented. Tests cover stdin delivery, stdout-ignored contract, and the continue_on_error=false non-propagation case explicitly. No behavioral regressions to existing hook events are possible because the new code path only activates when TurnEnd hooks are explicitly configured.

No files require special attention.

Important Files Changed

Filename Overview
crates/tui/src/hooks.rs Core hook implementation: adds TurnEnd enum variant, TurnEndPayloadInput/TurnEndTotals structs, execute_structured_observer (unconditional iteration, warn-on-failure, observer-only), and turn_end_payload builder. Tests cover payload shape, stdin delivery, stdout-ignored contract, and failure non-propagation including continue_on_error=false.
crates/tui/src/tui/ui.rs Wires turn_end into TurnComplete branch: payload is built from post-update app state with queued_message_count sampled before pop_queued_message, then dispatched via spawn_blocking. Also extracts turn_outcome_status_label helper and fixes error borrow to as_deref.
crates/tui/src/commands/hooks.rs Adds turn_end to /hooks events discovery listing, event_label mapping, and corresponding unit tests. Changes are mechanical and complete.
docs/CONFIGURATION.md Adds a clear observer turn_end hooks section with TOML config example, full JSON payload sample, and explicit prose that continue_on_error does not stop later hooks in observer mode.
docs/rfcs/1364-hooks-lifecycle.md RFC updated from Draft to Phase 1 landed / Phase 2+3 spec; restructured to reflect current state, shared design rules, Phase 2 technical spec with payload rules, and Phase 3 subagent spec. Documentation only, no runtime impact.
web/app/[locale]/docs/page.tsx Adds turn_end to both the Chinese and English quick-reference comment strings and appends a one-sentence description of turn_end observer semantics to each hooks paragraph.
web/package-lock.json Lockfile sync: marks esbuild platform packages dev:true and adds vitest-scoped esbuild 0.28.0 entries; generated mechanically via npm install --package-lock-only.

Sequence Diagram

sequenceDiagram
    participant Engine
    participant EventLoop as UI Event Loop
    participant App
    participant BlockingPool as Tokio Blocking Pool
    participant HookScript as Hook Script

    Engine->>EventLoop: EngineEvent::TurnComplete
    EventLoop->>App: Clear loading/streaming state
    EventLoop->>App: Set runtime_turn_status
    EventLoop->>App: Update token counters and cost
    EventLoop->>App: Schedule session snapshot
    EventLoop->>App: has_hooks_for_event(TurnEnd)?
    alt hooks configured and enabled
        EventLoop->>App: Snapshot post-update state into payload
        EventLoop->>App: Clone HookExecutor
        EventLoop->>BlockingPool: spawn_blocking fire-and-forget
        EventLoop->>App: pop_queued_message and dispatch next turn
        BlockingPool->>HookScript: exec with JSON on stdin
        HookScript-->>BlockingPool: exit code and stderr
        Note over BlockingPool: stdout ignored, failures warn-only
    else no hooks configured
        EventLoop->>App: pop_queued_message and dispatch next turn
    end
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (8): Last reviewed commit: "merge origin/main into phase2 branch" | Re-trigger Greptile

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

Thanks @AresNing for taking the time to contribute.

This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this pull request is staying open. When enforcement is enabled, pull requests from contributors who are not listed in .github/APPROVED_CONTRIBUTORS will be closed automatically.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant PR access by commenting /lgtm on a pull request.

Comment thread crates/tui/src/hooks.rs
@AresNing AresNing force-pushed the 1364-phase2-turn-end branch from 774217e to 74d69c3 Compare June 2, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant