Skip to content

fix(cursor): bridge native TodoWrite completions - #8432

Merged
diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.49from
makcimbx:fix/cursor-native-todo-bridge
Jul 26, 2026
Merged

diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.49from
makcimbx:fix/cursor-native-todo-bridge

Conversation

@makcimbx

@makcimbx makcimbx commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Decode Cursor native TodoWrite completions nested under InteractionUpdate.tool_call_completed -> ToolCall.todo_write.
  • Surface a schema-compatible native completion as an OpenAI todowrite tool call so OpenCode can update its own Todo state.
  • Preserve priorities from the latest structured assistant TodoWrite call; never invent missing values.
  • Accept native merge=true only when its unique content set exactly matches structured history; reject partial/new/ambiguous merges fail-closed.
  • Reject duplicate-content, malformed, constrained-schema, and ambiguous-priority cases fail-closed.
  • Close the native Cursor h2 session after bridging so the subsequent role: "tool" request resumes from full history.

Related Issues

Validation

  • Focused tests for the change: npx tsx --test tests/unit/cursor*.test.ts (219/219)
  • npm run lint — four pre-existing @typescript-eslint/no-explicit-any errors remain in unchanged tests/unit/claude-to-openai-think-close-5123.test.ts; targeted ESLint and Prettier checks on all six changed files pass
  • Production-code changes include new or updated automated tests in this PR
  • SonarQube PR analysis — no SonarQube check was emitted; repository Semgrep, Semgrep Cloud, and DAST checks passed

Additional validation:

  • Focused decoder/bridge/executor subset: 91/91
  • npm run typecheck:core
  • ESLint on all six changed files
  • Prettier check on all six changed files
  • git diff --check
  • Real 13,871-byte Cursor capture replayed through decoder, bridge, and OpenAI response builders
  • Real-capture non-stream shape: HTTP 200, finish_reason: "tool_calls", complete todowrite JSON
  • Real-capture streaming shape: tool deltas, finish_reason: "tool_calls", usage, [DONE]
  • Isolated backend release build with webpack fallback under a separate cgroup
  • Live Cursor frame replay: complete merge=true payload bridges to todowrite with finish_reason: "tool_calls"
  • Live HTTP non-streaming TodoWrite plus cold-resume follow-up
  • Live HTTP streaming todowrite, finish_reason: "tool_calls", and [DONE]
  • Final hardened backend rebuild and staging v3 refresh after independent security review findings
  • Independent final targeted review: no blocking findings and no merge-required suggestions
  • File-size blocker addressed by extracting nativeTodoWrite.ts: host 1505/1521 lines, leaf 111/800 lines by CI counting semantics

Note: the live streaming response did not include a usage chunk even with stream_options.include_usage=true. The TodoWrite tool call, arguments, finish reason, and [DONE] all passed; the usage observation is separate from this bridge.

After rebasing onto the current release tip, the full file-size gate reports only two unrelated base-branch drifts in providers/page.tsx and tokenHealthCheck.ts; neither changed file from this PR is a violation.

Tests Added Or Updated

  • tests/unit/cursor-agent-protobuf.test.ts
    • verifies the exact nested protobuf path and status enum mapping.
  • tests/unit/cursor-builtin-tool-bridge.test.ts
    • verifies priority preservation, assistant-role-only history, missing-priority rejection, exact-set merge acceptance, partial-merge rejection, duplicate rejection, and schema compatibility.
  • tests/unit/cursor-agent-tool-calls.test.ts
    • verifies one-time emission, arguments, no native MCP pending state, and forced cold resume.

Coverage Notes

The production changes are covered at three layers:

  1. crafted wire frame -> decodeAgentServerMessage;
  2. decoded native TodoWrite -> schema-compatible OpenAI arguments;
  3. processFrame -> emitted tool-call state and cold-resume lifecycle.

A sanitized real Cursor capture was also replayed outside the committed test fixtures. It contains all four observed statuses (pending, in_progress, completed, cancelled) and is intentionally not committed because raw captures can contain prompt/account metadata.

Reviewer Notes

Wire semantics

TodoWrite differs from the shell/read path fixed by #8171: there is no ExecServerMessage to reject. Cursor reports the complete structured action inside a tool-call completion interaction update. The decoder keeps the existing generic tool_call_completed delta for compatibility and additionally emits native_todo_write when the nested message is complete and recognized.

Fail-closed rules

  • Narration text is never parsed.
  • tool_choice filtering happens before TodoWrite matching.
  • Only the conservative supported JSON Schema subset is accepted.
  • Native merge: true is accepted only when its unique content set exactly equals the latest structured TodoWrite history, proving that the payload can safely replace the full list.
  • Partial/new/ambiguous merge payloads are rejected because OpenCode's TodoWrite cannot preserve native partial-merge semantics.
  • Duplicate content is rejected because priorities are recovered by exact content match.
  • Priorities are read only from structured role: "assistant" tool calls.
  • If the declared schema requires a priority that history cannot supply, no bridge call is emitted.
  • Unknown status values or malformed nested protobuf fields do not produce a native TodoWrite event.
  • Duplicate singular protobuf fields/messages are rejected rather than interpreted with first-match semantics.
  • Empty or invalid-UTF-8 native IDs/content, malformed assistant tool-call history, and malformed tool_choice fail closed.

Lifecycle

The native TodoWrite is already completed inside Cursor and has no open ExecMcp result channel. The bridge therefore emits an external OpenAI tool call, sets requiresColdResume, and closes the native h2 session. The next role: "tool" request uses the existing full-history cold path rather than attempting an invalid inline result.

@makcimbx
makcimbx marked this pull request as ready for review July 24, 2026 13:35
@makcimbx
makcimbx requested a review from diegosouzapw as a code owner July 24, 2026 13:35
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for this — really thorough work, both on the protobuf reverse-engineering and on the fail-closed safety semantics (exact-set merge proof, priority-history recovery, duplicate/malformed rejection). I re-ran the full validation locally on a fresh checkout of your branch: 91/91 on the three focused suites and 219/219 on the full cursor* test suite, plus a clean npm run typecheck:core and a clean targeted ESLint pass on all six changed files — everything matches what you documented in the PR body.

One thing is blocking merge right now that CI is also flagging: this PR's own diff pushes open-sse/utils/cursorAgentProtobuf.ts from 1491 to 1608 lines, past its frozen ceiling of 1521 in config/quality/file-size-baseline.json (our "file can only shrink" ratchet). Could you either split the new decodeNativeTodoWriteCompletion block into a small sibling module, or bump that baseline entry with a short justification comment? Either is fine on our end.

The other two red checks are not on you — "No new ESLint warnings" is the known pre-existing stale-suppression drift in tests/unit/claude-to-openai-think-close-5123.test.ts you already correctly called out in your validation checklist (tracked separately, fix already in our merge queue), and the single Unit Tests fast-path (2/4) red is in an unrelated cooldown-timing test outside your diff — we'll re-verify that ourselves as a possible flake. Once the file-size item is addressed we'll get this in.

@makcimbx
makcimbx force-pushed the fix/cursor-native-todo-bridge branch from 53dd71b to 576f6d0 Compare July 25, 2026 07:52
@makcimbx

Copy link
Copy Markdown
Contributor Author

Addressed in 576f6d096 by extracting the native TodoWrite wire decoder into open-sse/utils/cursorAgentProtobuf/nativeTodoWrite.ts; the frozen baseline entry was not changed.

Using the same split("\n").length counting semantics as check-file-size.mjs:

  • open-sse/utils/cursorAgentProtobuf.ts: 1505 / 1521
  • open-sse/utils/cursorAgentProtobuf/nativeTodoWrite.ts: 111 / 800 new-file cap

Post-extraction validation on the rebased head:

  • focused decoder/bridge/executor suites: 91/91
  • full cursor*.test.ts suite: 219/219
  • npm run typecheck:core: pass
  • targeted ESLint + Prettier: pass
  • npm run check:circular-deps: pass (advisory baseline unchanged)

The full npm run check:file-size no longer reports either changed file. After rebasing onto the current release tip it does report two unrelated inherited drifts in src/app/(dashboard)/dashboard/providers/page.tsx and src/lib/tokenHealthCheck.ts; I left those and the baseline untouched.

@diegosouzapw
diegosouzapw merged commit c9be34a into cheaper-inference:release/v3.8.49 Jul 26, 2026
14 of 15 checks passed
@diegosouzapw diegosouzapw mentioned this pull request Jul 28, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
)

Co-authored-by: Makcim Ivanov <10184529+makcimbx@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
)

Co-authored-by: Makcim Ivanov <10184529+makcimbx@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
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.

fix(providers): Cursor native TodoWrite completion dropped from OpenAI responses

2 participants