diff --git a/agent/channels/eve.ts b/agent/channels/eve.ts index aa0c5581..2db319c1 100644 --- a/agent/channels/eve.ts +++ b/agent/channels/eve.ts @@ -155,13 +155,17 @@ export function sessionIdFromPath(pathname: string) { return token ? sessionIdFromHookToken(token) : undefined; } -// Hook tokens are derived from the session id: `eve:session::inbox`, -// `:turn-control:[:cancel|:inbox]`, and `:auth`. +// Current inbox tokens wrap `eve:session::inbox` in `eve:inbox:v1:`. +// Earlier session, turn-control, and authorization token formats remain valid. function sessionIdFromHookToken(token: string) { + const inboxPrefix = "eve:inbox:v1:"; + const subject = token.startsWith(inboxPrefix) + ? token.slice(inboxPrefix.length) + : token; return ( - /^eve:session:([^:]+):inbox$/.exec(token)?.[1] ?? - /^([^:]+):turn-control:\d+(?::(?:cancel|inbox))?$/.exec(token)?.[1] ?? - /^([^:]+):auth$/.exec(token)?.[1] + /^eve:session:([^:]+):inbox$/.exec(subject)?.[1] ?? + /^([^:]+):turn-control:\d+(?::(?:cancel|inbox))?$/.exec(subject)?.[1] ?? + /^([^:]+):auth$/.exec(subject)?.[1] ); } diff --git a/tests/agent/channels/eve-channel-auth.test.ts b/tests/agent/channels/eve-channel-auth.test.ts index 90f7b6ce..8b3a61a9 100644 --- a/tests/agent/channels/eve-channel-auth.test.ts +++ b/tests/agent/channels/eve-channel-auth.test.ts @@ -74,6 +74,11 @@ describe("Eve channel authentication", () => { }); it.each([ + [ + "GET", + "/eve/v1/connections/:name/callback/:attemptId/:token", + "/eve/v1/connections/link__retrieve_user_info__inline_auth/callback/attempt/eve%3Ainbox%3Av1%3Aeve%3Asession%3Awrun_victim%3Ainbox", + ], [ "GET", "/eve/v1/connections/:name/callback/:attemptId/:token", @@ -126,11 +131,64 @@ describe("Eve channel authentication", () => { expect(isSessionOwnedMock).not.toHaveBeenCalled(); }); + it("passes an owned versioned inbox callback to Eve's callback handler", async () => { + isSessionOwnedMock.mockResolvedValue(true); + const route = findRoute( + "GET", + "/eve/v1/connections/:name/callback/:attemptId/:token" + ); + const token = "eve:inbox:v1:eve:session:wrun_owned:inbox"; + const response = await route.handler( + new Request( + `https://assistant.example/eve/v1/connections/link/callback/attempt/${encodeURIComponent(token)}?attempt=link-attempt` + ), + { + ...unexpectedRouteContext(), + params: { name: "link", attemptId: "attempt", token }, + } + ); + + expect(isSessionOwnedMock).toHaveBeenCalledWith( + expect.objectContaining({ userId: "better-auth:user-1" }), + "wrun_owned" + ); + // No workflow is registered in this test; this response comes from Eve, + // after the application's ownership guard has admitted the callback. + expect(response.status).toBe(404); + await expect(response.json()).resolves.toEqual({ + error: "Connection callback not pending.", + ok: false, + }); + }); + it("extracts session ids from paths and derived hook tokens", () => { expect(sessionIdFromPath("/eve/v1/session/wrun_1/stream")).toBe("wrun_1"); expect(sessionIdFromPath("/eve/v1/callback/eve:session:wrun_1:inbox")).toBe( "wrun_1" ); + expect( + sessionIdFromPath( + "/eve/v1/callback/eve:inbox:v1:eve:session:wrun_1:inbox" + ) + ).toBe("wrun_1"); + expect( + sessionIdFromPath( + "/eve/v1/connections/link__retrieve_user_info__inline_auth/callback/attempt/eve%3Ainbox%3Av1%3Aeve%3Asession%3Awrun_1%3Ainbox" + ) + ).toBe("wrun_1"); + expect( + sessionIdFromPath( + "/eve/v1/connections/link/callback/eve:inbox:v1:eve:session:wrun_1:inbox" + ) + ).toBe("wrun_1"); + expect( + sessionIdFromPath( + "/eve/v1/callback/eve:inbox:v2:eve:session:wrun_1:inbox" + ) + ).toBeUndefined(); + expect( + sessionIdFromPath("/eve/v1/callback/eve:inbox:v1:unrecognized:wrun_1") + ).toBeUndefined(); expect( sessionIdFromPath("/eve/v1/callback/wrun_1:turn-control:3:cancel") ).toBe("wrun_1");