Skip to content

Commit 05f4cb2

Browse files
juliusmarmingecodex
andcommitted
chore: restore cursor acp probe artifacts on cursor stack
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent e06782c commit 05f4cb2

3 files changed

Lines changed: 840 additions & 1 deletion

File tree

.plans/18-cursor-agent-provider.md

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
# Plan: Cursor ACP (`agent acp`) Provider Integration
2+
3+
## Goal
4+
5+
Add Cursor as a first-class provider in T3 Code using ACP (`agent acp`) over JSON-RPC 2.0 stdio, with robust session lifecycle handling and canonical `ProviderRuntimeEvent` projection.
6+
7+
---
8+
9+
## 1) Exploration Findings (from live ACP probes)
10+
11+
### 1.1 Core invocation and transport
12+
13+
1. Binary is `agent` on PATH (`2026.02.27-e7d2ef6` observed).
14+
2. ACP server command is `agent acp`.
15+
3. Transport is newline-delimited JSON-RPC 2.0 over stdio.
16+
4. Messages:
17+
- client -> server: requests and responses to server-initiated requests
18+
- server -> client: responses, notifications (`session/update`), and server requests (`session/request_permission`)
19+
20+
### 1.2 Handshake and session calls observed
21+
22+
1. `initialize` returns:
23+
- `protocolVersion`
24+
- `agentCapabilities` (`loadSession`, `mcpCapabilities`, `promptCapabilities`)
25+
- `authMethods` (includes `cursor_login`)
26+
2. `authenticate { methodId: "cursor_login" }` returns `{}` when logged in.
27+
3. `session/new` returns:
28+
- `sessionId`
29+
- `modes` (`agent`, `plan`, `ask`)
30+
4. `session/load` works and requires `sessionId`, `cwd`, `mcpServers`.
31+
5. `session/prompt` returns terminal response `{ stopReason: "end_turn" | "cancelled" }`.
32+
33+
Important sequence note:
34+
1. ACP currently allows `session/new` even without explicit `initialize`/`authenticate` when local auth already exists.
35+
2. For adapter consistency and forward compatibility, we should still send `initialize` and `authenticate` during startup.
36+
37+
### 1.3 `session/update` event families observed
38+
39+
Observed `params.update.sessionUpdate` values:
40+
41+
1. `available_commands_update`
42+
2. `agent_thought_chunk`
43+
3. `agent_message_chunk`
44+
4. `tool_call`
45+
5. `tool_call_update`
46+
47+
Observed payload behavior:
48+
49+
1. `agent_*_chunk` provides `content: { type: "text", text: string }`.
50+
2. `tool_call` may be emitted multiple times for same `toolCallId`:
51+
- initial generic form (`title: "Terminal"`, `rawInput: {}`)
52+
- enriched form (`title: "\`pwd\`"`, `rawInput: { command: "pwd" }`)
53+
3. `tool_call_update` statuses observed:
54+
- `in_progress`
55+
- `completed`
56+
4. `tool_call_update` on completion may include `rawOutput`:
57+
- terminal: `{ exitCode, stdout, stderr }`
58+
- search/find: `{ totalFiles, truncated }`
59+
60+
### 1.4 Permission flow observed
61+
62+
1. ACP server sends `session/request_permission` (JSON-RPC request with `id`).
63+
2. Request shape includes:
64+
- `params.sessionId`
65+
- `params.toolCall`
66+
- `params.options` (`allow-once`, `allow-always`, `reject-once`)
67+
3. Client must respond on same `id` with:
68+
- `{ outcome: { outcome: "selected", optionId: "<one-option-id>" } }`
69+
4. Reject path still results in tool lifecycle completion events (`tool_call_update status: completed`), typically without `rawOutput`.
70+
71+
### 1.5 Error and capability quirks
72+
73+
1. `session/cancel` currently returns:
74+
- JSON-RPC error `-32601` Method not found
75+
2. Error shape examples:
76+
- unknown auth method: `-32602`
77+
- `session/load` missing/invalid params: `-32602`
78+
- `session/prompt` unknown session: `-32603` with details
79+
3. Parallel prompts on same session are effectively single-flight:
80+
- second prompt can cause first to complete with `stopReason: "cancelled"`.
81+
4. `session/new` accepts a `model` field (no explicit echo in response).
82+
83+
Probe artifacts:
84+
1. `.tmp/acp-probe/*/transcript.ndjson`
85+
2. `.tmp/acp-probe/*/summary.json`
86+
3. `scripts/cursor-acp-probe.mjs`
87+
88+
---
89+
90+
## 2) Integration Constraints for T3
91+
92+
1. T3 adapter contract still requires:
93+
- `startSession`, `sendTurn`, `interruptTurn`, `respondToRequest`, `readThread`, `rollbackThread`, `stopSession`, `listSessions`, `hasSession`, `stopAll`, `streamEvents`.
94+
2. Orchestration consumes canonical `ProviderRuntimeEvent` only.
95+
3. `ProviderCommandReactor` provider precedence fix remains required (respect explicit provider on turn start).
96+
4. ACP now supports external permission decisions, so Cursor can participate in T3 approval UX via adapter-managed request/response plumbing.
97+
98+
---
99+
100+
## 3) Proposed Architecture
101+
102+
### 3.1 New server components
103+
104+
1. `apps/server/src/provider/Services/CursorAdapter.ts` (service contract/tag + ACP event schemas).
105+
2. `apps/server/src/provider/Layers/CursorAdapter.ts` (single implementation unit; owns ACP process lifecycle, JSON-RPC routing, runtime projection).
106+
3. No manager indirection; keep logic in layer implementation.
107+
108+
### 3.2 Session model
109+
110+
1. One long-lived ACP child process per T3 Cursor provider session.
111+
2. Track:
112+
- `providerSessionId` (T3 synthetic ID)
113+
- `acpSessionId` (from `session/new` or restored via `session/load`)
114+
- `cwd`, `model`, in-flight turn state
115+
- pending permission requests by JSON-RPC request id
116+
3. Resume support:
117+
- persist `acpSessionId` in provider resume metadata and call `session/load` on reattach.
118+
119+
### 3.3 Command strategy
120+
121+
1. `startSession`:
122+
- spawn `agent acp`
123+
- `initialize`
124+
- `authenticate(cursor_login)` (best-effort, typed failure handling)
125+
- `session/new` or `session/load`
126+
2. `sendTurn`:
127+
- send `session/prompt { sessionId, prompt: [...] }`
128+
- consume streaming `session/update` notifications until terminal prompt response
129+
3. `interruptTurn`:
130+
- no native `session/cancel` today; implement fallback:
131+
- terminate ACP process + restart + `session/load` for subsequent turns
132+
- mark in-flight turn as interrupted/failed in canonical events
133+
4. `respondToRequest`:
134+
- map T3 approval decision -> ACP `optionId`
135+
- reply to exact JSON-RPC request id from `session/request_permission`
136+
137+
### 3.4 Effect-first implementation style (required)
138+
139+
1. Keep logic inside `CursorAdapterLive`.
140+
2. Use Effect primitives:
141+
- `Queue` + `Stream.fromQueue` for event fan-out
142+
- `Ref` / `Ref.Synchronized` for session/process/request state
143+
- scoped fibers for stdout/stderr read loops
144+
3. Typed JSON decode at boundary:
145+
- request/response envelopes
146+
- `session/update` union schema
147+
- permission-request schema
148+
4. Keep adapter errors in typed error algebra with explicit mapping at process/protocol boundaries.
149+
150+
---
151+
152+
## 4) Canonical Event Mapping Plan (ACP -> ProviderRuntimeEvent)
153+
154+
1. `session/update: agent_message_chunk`
155+
- emit `message.delta` for assistant stream
156+
2. prompt terminal response (`session/prompt` result `stopReason: end_turn`)
157+
- emit `message.completed` + `turn.completed`
158+
3. `session/update: agent_thought_chunk`
159+
- initial mapping: emit thinking activity (or ignore if we keep current canonical surface minimal)
160+
4. `session/update: tool_call`
161+
- first-seen `toolCallId` emits `tool.started`
162+
- subsequent `tool_call` for same ID treated as metadata update (no duplicate started event)
163+
5. `session/update: tool_call_update`
164+
- `in_progress`: optional progress activity
165+
- `completed`: emit `tool.completed` with summarized `rawOutput` when present
166+
6. `session/request_permission`
167+
- emit `approval.requested` with mapped options
168+
- when client decision sent, emit `approval.resolved`
169+
7. protocol/process error
170+
- emit `runtime.error`
171+
- fail active turn/session as appropriate
172+
173+
Synthetic IDs:
174+
1. `turnId`: T3-generated UUID per `sendTurn`.
175+
2. `itemId`:
176+
- assistant stream: `${turnId}:assistant`
177+
- tools: `${turnId}:${toolCallId}`
178+
179+
---
180+
181+
## 5) Approval, Resume, and Rollback Behavior
182+
183+
### 5.1 Approvals
184+
185+
1. Cursor ACP permission requests are externally controllable; implement full `respondToRequest` path in v1.
186+
2. Decision mapping:
187+
- allow once -> `allow-once`
188+
- allow always -> `allow-always`
189+
- reject -> `reject-once`
190+
191+
### 5.2 Resume
192+
193+
1. `session/load` is available and should be first-class for adapter restart/reconnect.
194+
2. Must send required params: `sessionId`, `cwd`, `mcpServers`.
195+
196+
### 5.3 Rollback / thread read
197+
198+
1. ACP currently has no observed rollback API.
199+
2. Plan for v1:
200+
- `readThread`: adapter-maintained snapshot projection
201+
- `rollbackThread`: explicit unsupported error
202+
3. Product guard:
203+
- disable checkpoint revert for Cursor threads in UI until rollback exists.
204+
205+
---
206+
207+
## 6) Required Contract and Runtime Changes
208+
209+
### 6.1 Contracts
210+
211+
1. Add `cursor` to `ProviderKind`.
212+
2. Add Cursor provider start options (`providerOptions.cursor`), ACP-oriented:
213+
- optional `binaryPath`
214+
- optional auth/mode knobs if needed later
215+
3. Extend model options for Cursor list and traits mapping.
216+
4. Add schemas for ACP-native event union in Cursor adapter service file.
217+
218+
### 6.2 Server orchestration and registry
219+
220+
1. Register `CursorAdapter` in provider registry and server layer wiring.
221+
2. Update provider-kind persistence decoding for `cursor`.
222+
3. Fix `ProviderCommandReactor` precedence to honor explicit provider in turn-start command.
223+
224+
### 6.3 Web
225+
226+
1. Cursor in provider picker and model picker (already partially done).
227+
2. Trait controls map to concrete Cursor model identifiers.
228+
3. Surface unsupported rollback behavior in UX.
229+
230+
---
231+
232+
## 7) Implementation Phases
233+
234+
### Phase A: ACP process and protocol skeleton
235+
236+
1. Implement ACP process lifecycle in `CursorAdapterLive`.
237+
2. Implement JSON-RPC request/response multiplexer.
238+
3. Implement `initialize`/`authenticate`/`session/new|load` flow.
239+
4. Wire `streamEvents` from ACP notifications.
240+
241+
### Phase B: Runtime projection and approvals
242+
243+
1. Map `session/update` variants to canonical runtime events.
244+
2. Implement permission-request bridging to `respondToRequest`.
245+
3. Implement dedupe for repeated `tool_call` on same `toolCallId`.
246+
247+
### Phase C: Turn control and interruption
248+
249+
1. Implement single in-flight prompt protection per session.
250+
2. Implement interruption fallback (process restart + reload) because `session/cancel` unavailable.
251+
3. Ensure clean state recovery on ACP process crash.
252+
253+
### Phase D: Orchestration + UX polish
254+
255+
1. Provider routing precedence fix.
256+
2. Cursor-specific UX notes for unsupported rollback.
257+
3. End-to-end smoke and event log validation.
258+
259+
---
260+
261+
## 8) Test Plan
262+
263+
Follow project rule: backend external-service integrations tested via layered fakes, not by mocking core business logic.
264+
265+
### 8.1 Unit tests (`CursorAdapter`)
266+
267+
1. JSON-RPC envelope parsing:
268+
- response matching by id
269+
- server request handling (`session/request_permission`)
270+
- notification decode (`session/update`)
271+
2. Event projection:
272+
- `agent_message_chunk` / `agent_thought_chunk`
273+
- `tool_call` + `tool_call_update` dedupe/lifecycle
274+
- permission request -> approval events
275+
3. Error mapping:
276+
- unknown session
277+
- method-not-found (`session/cancel`)
278+
- invalid params
279+
280+
### 8.2 Provider service/routing tests
281+
282+
1. Registry resolves `cursor`.
283+
2. Session directory persistence reads/writes `cursor`.
284+
3. ProviderService fan-out ordering with Cursor ACP events.
285+
286+
### 8.3 Orchestration tests
287+
288+
1. `thread.turn.start` with `provider: cursor` routes to Cursor adapter.
289+
2. approval response command maps to ACP permission response.
290+
3. checkpoint revert on Cursor thread returns controlled unsupported failure.
291+
292+
### 8.4 Optional live smoke
293+
294+
1. Env-gated ACP smoke:
295+
- start session
296+
- run prompt
297+
- observe deltas + completion
298+
- exercise permission request path with one tool call
299+
300+
---
301+
302+
## 9) Operational Notes
303+
304+
1. Keep one in-flight turn per ACP session.
305+
2. Keep per-session ACP process logs/NDJSON artifacts for debugging.
306+
3. Treat `session/cancel` as unsupported until Cursor ships it; avoid relying on it.
307+
4. Preserve resume metadata (`acpSessionId`) for crash recovery.
308+
309+
---
310+
311+
## 10) Open Questions
312+
313+
1. Should we call `authenticate` always, or only after auth-required errors?
314+
2. Should model selection be passed at `session/new` only, or can/should we support model switching mid-session if ACP adds API?
315+
3. For interruption UX, do we expose “hard interrupt” semantics (process restart) explicitly?
316+
317+
---
318+
319+
## 11) Delivery Checklist
320+
321+
1. Plan/documentation switched from headless `agent -p` to ACP `agent acp`.
322+
2. Contracts updated (`ProviderKind`, Cursor options, model/trait mapping).
323+
3. Cursor ACP adapter layer implemented and registered.
324+
4. Provider precedence fixed in orchestration router.
325+
5. Approval response path wired through ACP permission requests.
326+
6. Tests added for protocol decode, projection, approval flow, and routing.
327+
7. Lint + tests green.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"test": "turbo run test",
3636
"test:desktop-smoke": "turbo run smoke-test --filter=@t3tools/desktop",
3737
"fmt": "oxfmt",
38+
"probe:cursor-acp": "node scripts/cursor-acp-probe.mjs",
3839
"build:contracts": "turbo run build --filter=@t3tools/contracts",
3940
"dist:desktop:artifact": "node scripts/build-desktop-artifact.ts",
4041
"dist:desktop:dmg": "node scripts/build-desktop-artifact.ts --platform mac --target dmg",
@@ -62,4 +63,4 @@
6263
"apps/web/public"
6364
]
6465
}
65-
}
66+
}

0 commit comments

Comments
 (0)