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:
Mohamed Boudra
2026-07-23 03:32:28 +08:00
committed by GitHub
parent de69b2a2af
commit 5dfe50ca74
19 changed files with 627 additions and 110 deletions
+43 -9
View File
@@ -29,6 +29,7 @@ import type { AgentAttachment, SessionOutboundMessage } from "@getpaseo/protocol
import { parseServerInfoStatusPayload } from "@getpaseo/protocol/messages";
import {
buildAgentAttentionNotificationPayload,
type AgentAttentionReason,
type AgentAttentionNotificationPayload,
type NotificationPermissionRequest,
} from "@getpaseo/protocol/agent-attention-notification";
@@ -154,6 +155,35 @@ const getLatestPermissionRequest = (
return null;
};
interface AgentAttentionNotificationInput {
notification?: AgentAttentionNotificationPayload;
reason: AgentAttentionReason;
serverId: string;
workspaceId: string | undefined;
agentId: string;
assistantMessage: string | null;
permissionRequest: NotificationPermissionRequest | null;
}
function resolveAgentAttentionNotification(
input: AgentAttentionNotificationInput,
): AgentAttentionNotificationPayload | null {
if (input.notification) {
return input.notification.data.workspaceId ? input.notification : null;
}
if (!input.workspaceId) {
return null;
}
return buildAgentAttentionNotificationPayload({
reason: input.reason,
serverId: input.serverId,
workspaceId: input.workspaceId,
agentId: input.agentId,
assistantMessage: input.reason === "finished" ? input.assistantMessage : null,
permissionRequest: input.reason === "permission" ? input.permissionRequest : null,
});
}
type WorkspaceSetupProgressPayload = Extract<
SessionOutboundMessage,
{ type: "workspace_setup_progress" }
@@ -500,16 +530,20 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
const assistantMessage =
findLatestAssistantMessageText(head) ?? findLatestAssistantMessageText(tail);
const permissionRequest = getLatestPermissionRequest(session, params.agentId);
const workspaceId = session?.agents?.get(params.agentId)?.workspaceId;
const notification =
params.notification ??
buildAgentAttentionNotificationPayload({
reason: params.reason,
serverId,
agentId: params.agentId,
assistantMessage: params.reason === "finished" ? assistantMessage : null,
permissionRequest: params.reason === "permission" ? permissionRequest : null,
});
const notification = resolveAgentAttentionNotification({
notification: params.notification,
reason: params.reason,
serverId,
workspaceId,
agentId: params.agentId,
assistantMessage,
permissionRequest,
});
if (!notification) {
return;
}
void sendOsNotification({
title: notification.title,