V2 event provider adapters - #134
Conversation
|
Cursor Agent can help with this pull request. Just |
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
| import { makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; | ||
|
|
||
| const PROVIDER = "cursor" as const; | ||
| const ACP_BINARY = "agent"; |
There was a problem hiding this comment.
🟠 High Layers/CursorAdapter.ts:49
Missing child 'error' handling: if the agent binary is invalid or not on PATH, spawn emits an 'error' that crashes the process. Suggest adding an 'error' listener immediately after spawn and converting it into a provider error with clean session cleanup.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/CursorAdapter.ts around line 49:
Missing `child` `'error'` handling: if the `agent` binary is invalid or not on `PATH`, `spawn` emits an `'error'` that crashes the process. Suggest adding an `'error'` listener immediately after `spawn` and converting it into a provider error with clean session cleanup.
Evidence trail:
apps/server/src/provider/Layers/CursorAdapter.ts:507-513 (spawnAcpProcess function), apps/server/src/provider/Layers/CursorAdapter.ts:698 (child = spawnAcpProcess call), apps/server/src/provider/Layers/CursorAdapter.ts:722-737 (only exit event handled, no error event), git_grep for `child.(on|once).*'error'` returns no results confirming no error listener exists
| if (cause && typeof cause === "object" && "message" in cause) { | ||
| return String((cause as { message: unknown }).message); | ||
| } |
There was a problem hiding this comment.
🟢 Low Layers/CursorAdapter.ts:63
When cause is an Error with an empty message, the first check correctly skips it, but the object check on line 63 re-matches and returns the empty string anyway. Consider adding a length check to the object branch as well.
| if (cause && typeof cause === "object" && "message" in cause) { | |
| return String((cause as { message: unknown }).message); | |
| } | |
| if (cause && typeof cause === "object" && "message" in cause) { | |
| const msg = String((cause as { message: unknown }).message); | |
| if (msg.length > 0) return msg; | |
| } |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/CursorAdapter.ts around lines 63-65:
When `cause` is an `Error` with an empty message, the first check correctly skips it, but the object check on line 63 re-matches and returns the empty string anyway. Consider adding a length check to the object branch as well.
Evidence trail:
apps/server/src/provider/Layers/CursorAdapter.ts lines 58-68 at REVIEWED_COMMIT. The function `toMessage` has the first check `if (cause instanceof Error && cause.message.length > 0)` at line 59, and the object check `if (cause && typeof cause === "object" && "message" in cause)` at line 63. An Error object satisfies all three conditions in the object check (truthy, typeof object, has message property), so an Error with empty message bypasses the length check but gets caught by line 63 and returns the empty string instead of the fallback.
| Effect.gen(function* () { | ||
| const context = yield* requireSession(sessionId); | ||
|
|
||
| if (!context.turnState) return; |
There was a problem hiding this comment.
🟡 Medium Layers/CursorAdapter.ts:911
interruptTurn should reuse the shared cleanup. Calling stopSessionInternal (or a shared interrupt helper) instead of setting context.stopped and child.kill() ensures pending RPCs/timers are cleared and the session is removed, avoiding hangs.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/CursorAdapter.ts around line 911:
`interruptTurn` should reuse the shared cleanup. Calling `stopSessionInternal` (or a shared interrupt helper) instead of setting `context.stopped` and `child.kill()` ensures pending RPCs/timers are cleared and the session is removed, avoiding hangs.
Evidence trail:
apps/server/src/provider/Layers/CursorAdapter.ts lines 908-935 (interruptTurn implementation), lines 581-661 (stopSessionInternal implementation), lines 620-631 (pendingRpc cleanup in stopSessionInternal), line 661 (sessions.delete in stopSessionInternal), line 51 (DEFAULT_REQUEST_TIMEOUT_MS = 120_000)
| kind: "approval.requested", | ||
| summary: | ||
| event.requestKind === "command" | ||
| requestKind === "command" |
There was a problem hiding this comment.
🟡 Medium Layers/ProviderRuntimeIngestion.ts:119
Suggestion: Normalize event.payload.requestType to a simplified requestKind (command/file-change/other) via a shared helper and use it for both request.opened and request.resolved. Derive summary from this (use "Approval requested" for other) to fix the mismatch and incorrect "File-change..." text.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts around line 119:
Suggestion: Normalize `event.payload.requestType` to a simplified `requestKind` (`command`/`file-change`/`other`) via a shared helper and use it for both `request.opened` and `request.resolved`. Derive `summary` from this (use "Approval requested" for `other`) to fix the mismatch and incorrect "File-change..." text.
Evidence trail:
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts lines 106-147 at REVIEWED_COMMIT. Lines 106-110 show normalization to `requestKind` (`command`/`file-change`/`other`). Lines 119-121 show summary only handles two cases, defaulting to "File-change approval requested" for both `file-change` AND `other`. Lines 140-141 show `request.resolved` passes raw `event.payload.requestType` directly as `requestKind` without normalization.
7a9b3d3 to
ac45b82
Compare
Second upstream sync on this branch: pingdotgg/t3code main at 2c7267a ("stop the reaper from silently killing live background subagents", pingdotgg#5677) into the DevGame fork. 19 conflicted paths, resolved under the standing doctrine (upstream structure wins; fork features re-expressed inside it; the fork's deliberate deletions stand). Highlights taken from upstream: background-subagent reaper + settling fixes (pingdotgg#5677/pingdotgg#5568), sidebar v2 promoted to THE sidebar (pingdotgg#5672 -- SidebarV2.tsx renamed Sidebar.tsx, old sidebar now LegacySidebar.tsx behind Settings -> Legacy features), plans fold into chat (pingdotgg#5558, plan sidebar deleted), agents observability panel (pingdotgg#5219), MCP tool-result payload slimming (pingdotgg#5482), thread pagination (pingdotgg#5493), per-device provider settings (pingdotgg#4479), theme library + configurable fonts (pingdotgg#5103), thread pinning (pingdotgg#5312/pingdotgg#5581), reconnect-warning grace (pingdotgg#5670), mobile 1.0.2. Notable resolution rulings: - Right panel collapses to agents-only: upstream retired "plan", the fork had already moved preview/terminal/diff/files to the dock. ChatView's right-panel plumbing reduced accordingly; persistence shim now ALLOWLISTS the surviving kind, tolerates corrupt entries, and prunes records it empties (three tests updated to the pruned contract). - Migration id space: fork ids 36/37 (spaces) stay where deployed DevGame databases recorded them; upstream's three arrivals take 38/39/40 with files renamed to match. Crossover hazard from stock-T3 data dirs is documented at the manifest and tracked with the storage-isolation task. - useThreadSidebarComponent re-pointed at Sidebar/LegacySidebar via useLegacySidebarEnabled (forceV1 gone -- /settings* mounts SettingsSidebarNav, no thread sidebar). The pingdotgg#111 aria-label derivation ported into the renamed Sidebar.tsx. - Locale-fragile upstream snooze tests (pingdotgg#4438 asserts en-US "PM") made locale-agnostic; ThemeSettings copy re-branded to DevGame; upstream's text-secondary-label token applied in the fork's MessageImageGrid. Verification: typecheck exit 0 across every package (incl. server + desktop run separately); suites green -- server 2197, web 2400, scripts 227, contracts 228. Four-lens Opus merge-gate review over the resolution: 31 findings, 29 fixed in this commit, 2 filed as follow-up tasks (pingdotgg#133 right-panel chooser UX -- pre-existing; pingdotgg#134 migration ledger guard). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update Codex and Claude Code adapters to use the v2 canonical event structure and add a new Cursor agent provider with v2 event support.
Note
Migrate provider adapters to emit V2 canonical runtime events and add Cursor adapter in serverLayers.ts
Adapters and tests switch to V2 event names, payloads, and Runtime* ID wrappers, unify message/tool events under
item.*, replace approvals withrequest.*, and add a newcursorprovider with ACP schemas and registry integration.📍Where to Start
Start with the event mapping pipeline in
events.mapToRuntimeEventsin CodexAdapter.ts, then reviewmakeClaudeCodeAdapterin ClaudeCodeAdapter.ts, and the Cursor layer in CursorAdapter.ts.📊 Macroscope summarized 32779bd. 10 files reviewed, 28 issues evaluated, 2 issues filtered, 4 comments posted
🗂️ Filtered Issues
apps/server/src/provider/Layers/CursorAdapter.ts — 3 comments posted, 13 evaluated, 2 filtered
item.completedevents due to state loss. TheCursorTurnStatedefinition (Code Object 2) only tracksseenToolCallIdsand fails to persist theCanonicalItemTypedetermined for each tool call. Consequently, when processing atool_call_updateat line 360, the code invokesclassifyToolItemType(Code Object 0) withundefinedarguments, forcing the return value to"dynamic_tool_call". This overwrites any specific type (e.g.,"command_execution") established at the start of the item, resulting in anitem.completedevent payload that conflicts with theitem.startedevent type. [ Out of scope ]turn.completedevents. ThesendTurnmethod initializesturnState(Code Object 2) and manages the turn lifecycle. IfinterruptTurnis called, it emits aturn.completedevent (state: interrupted) and kills the child process. However, the concurrentsendTurnfiber catches the resulting process termination error (lines 838-853) and blindly emits a secondturn.completedevent (state: failed) at line 879. ThesendTurnerror handler fails to checkcontext.stoppedorturnStatevalidity, leading to a protocol violation where the client receives two contradictory completion events for the same turn. [ Out of scope ]