mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
Bind sessionless ACP extensions to their admitted wire session
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
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,
|
||||
|
||||
Reference in new issue
Block a user