Bind sessionless ACP extensions to their admitted wire session

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-28 11:43:24 -05:00
1 parent 1cee50ed48
commit 88f34b30f8
2 files changed
+15 -3

No files matched your search

@@ -70,12 +70,20 @@ describe("Codex ACPX runtime adapter", () => {
const admittedFirst = port.startTurn({ text: "First", requestId: "turn-1", onExtensionRequest: request, onExtensionNotification: notification });
const signal = new AbortController().signal;
await expect(created.onExtensionRequest!("cursor/ask_question", { sessionId: "backend-1" }, { requestId: 0, signal })).resolves.toEqual({ requestId: 0 });
const sessionless = { toolCallId: "native-cursor-plan", plan: "Review the complete plan" };
await expect(created.onExtensionRequest!("cursor/create_plan", sessionless, { requestId: "plan", signal })).resolves.toEqual({ requestId: "plan" });
expect(request).toHaveBeenLastCalledWith("cursor/create_plan", { ...sessionless, sessionId: "backend-1" }, expect.objectContaining({ requestId: "plan" }));
expect(sessionless).not.toHaveProperty("sessionId");
await expect(created.onExtensionRequest!("pi/steer", {}, { requestId: 1, signal })).rejects.toThrow("admitted active turn");
await expect(created.onExtensionRequest!("cursor/ask_question", { sessionId: "other" }, { requestId: 1, signal })).rejects.toThrow("admitted active turn");
created.onExtensionNotification!("cursor/task", { sessionId: "backend-1", taskId: "t" });
created.onExtensionNotification!("cursor/task", { sessionId: "other" });
created.onExtensionNotification!("cursor/ask_question", { sessionId: "backend-1" });
expect(notification).toHaveBeenCalledExactlyOnceWith("cursor/task", { sessionId: "backend-1", taskId: "t" });
created.onExtensionNotification!("cursor/task", { toolCallId: "native-child" });
expect(notification).toHaveBeenLastCalledWith("cursor/task", { sessionId: "backend-1", toolCallId: "native-child" });
created.onExtensionNotification!("cursor/task", { sessionId: "agent-1" });
expect(notification).toHaveBeenCalledTimes(2);
request.mockImplementationOnce(() => new Promise(() => undefined));
const pending = created.onExtensionRequest!("cursor/ask_question", {}, { requestId: 2, signal });
const rejected = expect(pending).rejects.toThrow("expired");
@@ -84,6 +84,7 @@ interface AcpxRuntimeGoalState {
interface AcpxRuntimeExtensionTurn {
requestId: string;
sessionId: string;
controller: AbortController;
signal: AbortSignal;
promptStarted: Promise<void>;
@@ -320,7 +321,10 @@ export async function openQualifiedAcpxRuntime(
throw new Error("ACPX extension request does not own an admitted active turn");
}
const signal = AbortSignal.any([active.signal, context.signal]);
const response = await abortableExtensionResult(active.onRequest(method, params, { requestId: context.requestId, signal }), signal);
// Cursor's native extensions omit sessionId. Only after admission may
// that omission inherit this exact prompt's ACP wire session identity.
const boundParams = params.sessionId === undefined ? { ...params, sessionId: active.sessionId } : params;
const response = await abortableExtensionResult(active.onRequest(method, boundParams, { requestId: context.requestId, signal }), signal);
signal.throwIfAborted();
if (!ownsExtensionTurn(active, params)) throw new Error("ACPX extension request turn expired");
return response;
@@ -328,7 +332,7 @@ export async function openQualifiedAcpxRuntime(
onExtensionNotification: (method, params) => {
const active = extensionBoundary.active;
if (!extensionNotifications.has(method) || !active?.onNotification || !ownsExtensionTurn(active, params)) return;
active.onNotification(method, params);
active.onNotification(method, params.sessionId === undefined ? { ...params, sessionId: active.sessionId } : params);
},
nonInteractivePermissions: "fail",
permissionPolicy: {
@@ -1316,7 +1320,7 @@ function runtimePort(
const approval = new AbortController();
const controller = new AbortController();
const extensionTurn: AcpxRuntimeExtensionTurn = {
requestId: input.requestId, controller,
requestId: input.requestId, sessionId: identity.backendSessionId, controller,
signal: input.signal ? AbortSignal.any([controller.signal, input.signal, approval.signal]) : AbortSignal.any([controller.signal, approval.signal]),
promptStarted: Promise.resolve(),
onRequest: input.onExtensionRequest, onNotification: input.onExtensionNotification,