Skip to content

feat: add turn_end observer hook#2578

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

feat: add turn_end observer hook#2578
AresNing wants to merge 10 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 lands Phase 2 of the #1364 hooks-lifecycle RFC: a turn_end observer hook that fires after every completed, interrupted, or failed model turn. The hook receives a structured JSON payload on stdin (status, usage counters, turn duration, tool count, queued message count) and is executed non-blocking via tokio::task::spawn_blocking, with failures logged as warnings and stdout ignored.

  • turn_end hook: adds HookEvent::TurnEnd, execute_structured_observer, and turn_end_payload builder; fires from the TurnComplete branch in ui.rs after all post-turn state updates and before queued-message dispatch; documents observer semantics and continue_on_error behaviour.
  • siliconflow-CN provider: adds a dedicated provider ID for the regional .cn endpoint, sharing credentials and config table with siliconflow; fixes duplicate match arms and indentation regressions from an earlier SiliconflowCn addition.
  • Cleanup: removes duplicate ApiProvider::SiliconflowCn match arms, reformats several long match arms for cargo fmt, and updates CHANGELOG, docs, RFC, web UI, and scripts/check-provider-registry.py.

Confidence Score: 5/5

Safe to merge. The turn_end hook fires correctly for all terminal turn outcomes, is fully non-blocking, and cannot mutate app state. The siliconflow-CN provider and duplicate match-arm removals are straightforward.

The core hook infrastructure is well-implemented: execute_structured_observer iterates unconditionally (documented and tested), spawn_blocking correctly offloads blocking execution, tool_evidence is cleared at turn start so the count is accurate, and the payload ordering matches the RFC spec. The only gap is that unit tests cover status = 'completed' only; 'interrupted' and 'failed' paths are correct by exhaustive Rust match but have no dedicated test cases. No runtime defects were found.

No files require special attention. The hooks.rs test suite is the only area where additional coverage for non-completed turn statuses would strengthen the implementation per the RFC's own review checklist.

Important Files Changed

Filename Overview
crates/tui/src/hooks.rs Adds HookEvent::TurnEnd, execute_structured_observer (observer-only, ignores stdout, warns on failure, runs all hooks regardless of continue_on_error), and turn_end_payload builder; well-structured with unit tests covering stdin delivery and multi-hook failure semantics.
crates/tui/src/tui/ui.rs Fires turn_end hook via tokio::task::spawn_blocking after all post-turn state updates and before queued-message dispatch; correctly captures post-update token totals, tool_evidence count (cleared at turn start), and runtime_turn_id; ordering matches RFC spec.
crates/tui/src/commands/hooks.rs Adds TurnEnd to the /hooks events listing and event_label mapping; tests verify round-trip serialisation and event discovery list.
crates/tui/src/config.rs Removes duplicate SiliconflowCn match arms in default_base_url_for_provider, save_api_key_for, and provider_config_key; fixes indentation regressions from the earlier SiliconflowCN addition.
scripts/check-provider-registry.py Adds PROVIDER_TABLE_ALIASES map so siliconflow-CN resolves to the shared siliconflow config table in registry validation, preventing a false-positive provider-drift error.
docs/CONFIGURATION.md Adds the turn_end observer hook section with payload schema, TOML config example, and clear statement that continue_on_error does not stop later hooks; also adds siliconflow-CN to provider lists and env var documentation.

Sequence Diagram

sequenceDiagram
    participant Engine
    participant EventLoop as run_event_loop (ui.rs)
    participant App
    participant SpawnBlocking as tokio::task::spawn_blocking
    participant HookExecutor

    Engine->>EventLoop: "EngineEvent::TurnComplete { usage, status, error, ... }"
    EventLoop->>App: Clear loading/streaming state
    EventLoop->>App: Record turn_elapsed
    EventLoop->>App: Update runtime_turn_status
    EventLoop->>App: Accumulate session token counters
    EventLoop->>App: Accrue session cost estimate
    EventLoop->>App: Emit desktop notification / receipt
    EventLoop->>App: Schedule session persistence
    Note over EventLoop,App: All post-turn state updates done

    alt hooks configured for TurnEnd
        EventLoop->>EventLoop: Build turn_end_payload(status, usage, totals, tool_count, ...)
        EventLoop->>SpawnBlocking: spawn_blocking (fire-and-forget)
        SpawnBlocking->>HookExecutor: execute_structured_observer(TurnEnd, context, payload)
        loop each matching hook
            HookExecutor->>HookExecutor: write payload to stdin
            HookExecutor->>HookExecutor: ignore stdout
            alt hook fails
                HookExecutor->>HookExecutor: tracing::warn!(...) — continue to next hook
            end
        end
    end

    EventLoop->>App: pop_queued_message() — dispatch next queued message if any
Loading

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

Reviews (7): Last reviewed commit: "fix: document siliconflow cn registry" | 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