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:
@@ -14,6 +14,8 @@ import { createProviderSnapshotManagerStub } from "./test-utils/session-stubs.js
|
||||
import type { PushNotificationSender, PushPayload } from "./push/notifications.js";
|
||||
import type { WorkspaceAutoName } from "./workspace-auto-name.js";
|
||||
|
||||
const WORKSPACE_ID = "workspace-1";
|
||||
|
||||
const wsModuleMock = vi.hoisted(() => {
|
||||
class MockWebSocketServer {
|
||||
readonly handlers = new Map<string, (...args: unknown[]) => void>();
|
||||
@@ -86,7 +88,7 @@ function createServer(agentManagerOverrides?: Record<string, unknown>) {
|
||||
const agentManager = {
|
||||
subscribe: vi.fn(() => () => {}),
|
||||
setAgentAttentionCallback: vi.fn(),
|
||||
getAgent: vi.fn(() => null),
|
||||
getAgent: vi.fn(() => ({ workspaceId: WORKSPACE_ID, pendingPermissions: new Map() })),
|
||||
getLastAssistantMessage: vi.fn(async () => null),
|
||||
getMetricsSnapshot: vi.fn(() => ({
|
||||
total: 0,
|
||||
@@ -225,6 +227,7 @@ describe("VoiceAssistantWebSocketServer notification payloads", () => {
|
||||
getAgent: vi.fn(() => ({
|
||||
config: { title: null },
|
||||
cwd: "/tmp/worktree",
|
||||
workspaceId: WORKSPACE_ID,
|
||||
pendingPermissions: new Map(),
|
||||
})),
|
||||
getLastAssistantMessage,
|
||||
@@ -242,6 +245,7 @@ describe("VoiceAssistantWebSocketServer notification payloads", () => {
|
||||
body: "Done. Updated README.md and link.",
|
||||
data: {
|
||||
serverId: "srv-test",
|
||||
workspaceId: WORKSPACE_ID,
|
||||
agentId: "agent-1",
|
||||
reason: "finished",
|
||||
},
|
||||
@@ -256,6 +260,7 @@ describe("VoiceAssistantWebSocketServer notification payloads", () => {
|
||||
getAgent: vi.fn(() => ({
|
||||
config: { title: null },
|
||||
cwd: "/tmp/worktree",
|
||||
workspaceId: WORKSPACE_ID,
|
||||
labels: {},
|
||||
pendingPermissions: new Map(),
|
||||
})),
|
||||
|
||||
@@ -2152,13 +2152,17 @@ export class VoiceAssistantWebSocketServer {
|
||||
const allStates = clientEntries.map((e) => e.state);
|
||||
const nowMs = Date.now();
|
||||
const agent = this.agentManager.getAgent(params.agentId);
|
||||
if (!agent?.workspaceId) {
|
||||
return;
|
||||
}
|
||||
const assistantMessage = await this.agentManager.getLastAssistantMessage(params.agentId);
|
||||
const notification = buildAgentAttentionNotificationPayload({
|
||||
reason: params.reason,
|
||||
serverId: this.serverId,
|
||||
workspaceId: agent.workspaceId,
|
||||
agentId: params.agentId,
|
||||
assistantMessage,
|
||||
permissionRequest: agent ? findLatestPermissionRequest(agent.pendingPermissions) : null,
|
||||
permissionRequest: findLatestPermissionRequest(agent.pendingPermissions),
|
||||
});
|
||||
|
||||
const plan = computeNotificationPlan({
|
||||
|
||||
Reference in New Issue
Block a user