fix(server): Cursor adapter turn-lifecycle hardening (races, missing terminal events, lock retention)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Summary
While building the omp ACP provider (#8985 / #9038, modeled on the Cursor driver) we verified three latent issues in apps/server/src/provider/Layers/CursorAdapter.ts. They are inherited patterns, not regressions — filing separately so they can be fixed on their own merits. The omp adapter shipped with the fixes; Cursor still has the originals.
1. sendTurn binds activeTurnId too late
ctx.promptsInFlight += 1 (CursorAdapter.ts ~L927) and ctx.activeTurnId = turnId (~L948, after effectful session configuration) leave an interleaving window: a second concurrent sendTurn reads the previous turn's activeTurnId and steers/attributes events onto the wrong turn. Fix (as in #9038): bind activeTurnId in the same synchronous stretch as the increment.
2. No terminal event when a started turn fails
turn.started is emitted before attachment reads and acp.prompt; failures from either propagate with no turn.completed, relying entirely on ProviderCommandReactor.handleTurnStartFailure to settle the session. RuntimeTurnState already supports "failed" with errorMessage — emitting the terminal event adapter-side (guarded on !ctx.stopped and the last-in-flight rule) makes the runtime event stream self-consistent.
3. Cancellation during turn preparation is ignored
interruptTurn only calls ctx.acp.cancel, which is a no-op before acp.prompt starts. A turn cancelled during applyRequestedSessionConfiguration / attachment reads still gets prompted. Fix (as in #9038): record the cancelled turn id in a session set, check it before turn.started and again before prompting, settle as cancelled.
4. threadLocksRef grows forever
Every thread that passes through getThreadSemaphore retains its semaphore for the adapter's lifetime — stopSession/stopAll/failed startups never remove it. Fix: delete the per-thread lock on session teardown and startup-failure paths (careful: the startSession replacement path must keep the lock it holds).
All four were verified against Cursor's code by reading; fixes 1-3 exist and are tested on the omp side (#9038, tests in OmpAdapter.test.ts) so the port is mechanical.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apps/server/src/provider/Layers/CursorAdapter.ts, focusing on sendTurn, interruptTurn, getThreadSemaphore, and session teardown or startup-failure paths. Read the corresponding fixes and tests in OmpAdapter.test.ts, then verify the Cursor adapter covers turn binding races, failed-turn terminal events, preparation-time cancellation, and per-thread lock cleanup.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100