Fix notifications opening the wrong workspace (#2331)
* fix(app): open notifications on the right host Agent notifications previously omitted workspace ownership, so a cold target host was treated as missing and fell back to its empty home route. Carry the authoritative workspace and keep older notifications on a target-host resolver until lookup is conclusive. * fix(app): separate notification and agent URL routing Notifications use their authoritative workspace target directly. Stable agent URLs remain server-and-agent targets whose workspace is resolved by the agent route. * fix(protocol): preserve notification workspace targets Both attention message variants retain workspaceId through outbound validation so notification clicks receive the authoritative route target. * test(app): use workspace-scoped notification target * test(server): give notification agents workspace targets * test(app): target notification workspace in navigation e2e
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Buffer } from "buffer";
|
||||
import { buildAgentDeepLinkRoute } from "@getpaseo/protocol/agent-deep-link";
|
||||
|
||||
type NullableString = string | null | undefined;
|
||||
const BASE64_WORKSPACE_ID_PREFIX = "b64_";
|
||||
@@ -389,7 +390,10 @@ export function buildHostAgentDetailRoute(serverId: string, agentId: string, wor
|
||||
if (!normalizedServerId || !normalizedAgentId) {
|
||||
return "/" as const;
|
||||
}
|
||||
return `${buildHostRootRoute(normalizedServerId)}/agent/${encodeSegment(normalizedAgentId)}` as const;
|
||||
return buildAgentDeepLinkRoute({
|
||||
serverId: normalizedServerId,
|
||||
agentId: normalizedAgentId,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildHostRootRoute(serverId: string) {
|
||||
|
||||
@@ -56,13 +56,11 @@ describe("buildNotificationRoute", () => {
|
||||
agentId: "agent-1",
|
||||
workspaceId: "ws-main",
|
||||
}),
|
||||
).toBe("/h/srv-1/agent/agent-1");
|
||||
).toBe("/h/srv-1/workspace/ws-main?open=agent%3Aagent-1");
|
||||
});
|
||||
|
||||
it("routes directly to server-scoped agent path when both ids are present", () => {
|
||||
expect(buildNotificationRoute({ serverId: "srv-1", agentId: "agent-1" })).toBe(
|
||||
"/h/srv-1/agent/agent-1",
|
||||
);
|
||||
it("does not treat an incomplete notification as an agent URL", () => {
|
||||
expect(buildNotificationRoute({ serverId: "srv-1", agentId: "agent-1" })).toBe("/h/srv-1");
|
||||
});
|
||||
|
||||
it("routes to the workspace terminal tab when workspace and terminal ids are present", () => {
|
||||
@@ -98,8 +96,9 @@ describe("buildNotificationRoute", () => {
|
||||
expect(
|
||||
buildNotificationRoute({
|
||||
serverId: "srv/with/slash",
|
||||
workspaceId: "workspace-1",
|
||||
agentId: "agent with space",
|
||||
}),
|
||||
).toBe("/h/srv%2Fwith%2Fslash/agent/agent%20with%20space");
|
||||
).toBe("/h/srv%2Fwith%2Fslash/workspace/workspace-1?open=agent%3Aagent%20with%20space");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { Href } from "expo-router";
|
||||
import {
|
||||
buildHostAgentDetailRoute,
|
||||
buildHostRootRoute,
|
||||
buildHostWorkspaceOpenRoute,
|
||||
} from "@/utils/host-routes";
|
||||
import { buildHostRootRoute, buildHostWorkspaceOpenRoute } from "@/utils/host-routes";
|
||||
|
||||
type NotificationData = Record<string, unknown> | null | undefined;
|
||||
type NotificationRoute = Extract<Href, string>;
|
||||
@@ -33,8 +29,8 @@ export function resolveNotificationTarget(data: NotificationData): {
|
||||
|
||||
export function buildNotificationRoute(data: NotificationData): NotificationRoute {
|
||||
const { serverId, agentId, workspaceId, terminalId } = resolveNotificationTarget(data);
|
||||
if (serverId && agentId) {
|
||||
return buildHostAgentDetailRoute(serverId, agentId);
|
||||
if (serverId && workspaceId && agentId) {
|
||||
return buildHostWorkspaceOpenRoute(serverId, workspaceId, `agent:${agentId}`);
|
||||
}
|
||||
if (serverId && workspaceId && terminalId) {
|
||||
return buildHostWorkspaceOpenRoute(serverId, workspaceId, `terminal:${terminalId}`);
|
||||
|
||||
@@ -196,14 +196,20 @@ describe("sendOsNotification", () => {
|
||||
|
||||
await sendOsNotification({
|
||||
title: "Agent finished",
|
||||
data: { serverId: "srv with space", agentId: "agent/1" },
|
||||
data: {
|
||||
serverId: "srv with space",
|
||||
workspaceId: "workspace-1",
|
||||
agentId: "agent/1",
|
||||
},
|
||||
});
|
||||
|
||||
const clicked = created[0];
|
||||
expect(clicked.clickListeners).toHaveLength(1);
|
||||
clicked.clickListeners[0]?.({} as Event);
|
||||
|
||||
expect(assign).toHaveBeenCalledWith("/h/srv%20with%20space/agent/agent%2F1");
|
||||
expect(assign).toHaveBeenCalledWith(
|
||||
"/h/srv%20with%20space/workspace/workspace-1?open=agent%3Aagent%2F1",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false when the Notification API is unavailable", async () => {
|
||||
|
||||
Reference in New Issue
Block a user