* 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
120 lines
3.4 KiB
TypeScript
120 lines
3.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildAgentAttentionNotificationPayload,
|
|
findLatestAssistantMessageFromTimeline,
|
|
findLatestPermissionRequest,
|
|
} from "./agent-attention-notification.js";
|
|
|
|
describe("buildAgentAttentionNotificationPayload", () => {
|
|
it("carries the workspace needed to open a cold agent destination", () => {
|
|
const payload = buildAgentAttentionNotificationPayload({
|
|
reason: "finished",
|
|
serverId: "srv-1",
|
|
workspaceId: "workspace-1",
|
|
agentId: "agent-1",
|
|
});
|
|
|
|
expect(payload.data).toEqual({
|
|
serverId: "srv-1",
|
|
workspaceId: "workspace-1",
|
|
agentId: "agent-1",
|
|
reason: "finished",
|
|
});
|
|
});
|
|
|
|
it("builds finished notifications from markdown assistant text", () => {
|
|
const payload = buildAgentAttentionNotificationPayload({
|
|
reason: "finished",
|
|
serverId: "srv-1",
|
|
workspaceId: "workspace-1",
|
|
agentId: "agent-1",
|
|
assistantMessage: "**Done**. Updated `README.md` and [link](https://example.com).",
|
|
});
|
|
|
|
expect(payload).toEqual({
|
|
title: "Agent finished",
|
|
body: "Done. Updated README.md and link.",
|
|
data: {
|
|
serverId: "srv-1",
|
|
workspaceId: "workspace-1",
|
|
agentId: "agent-1",
|
|
reason: "finished",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("builds permission notifications from request details", () => {
|
|
const payload = buildAgentAttentionNotificationPayload({
|
|
reason: "permission",
|
|
serverId: "srv-2",
|
|
workspaceId: "workspace-2",
|
|
agentId: "agent-2",
|
|
permissionRequest: {
|
|
id: "perm-1",
|
|
provider: "claude",
|
|
name: "exec",
|
|
kind: "tool",
|
|
title: "**Approve command**",
|
|
description: "Run `git push`",
|
|
},
|
|
});
|
|
|
|
expect(payload).toEqual({
|
|
title: "Agent needs permission",
|
|
body: "Approve command - Run git push",
|
|
data: {
|
|
serverId: "srv-2",
|
|
workspaceId: "workspace-2",
|
|
agentId: "agent-2",
|
|
reason: "permission",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("uses error-specific defaults when reason is error", () => {
|
|
const payload = buildAgentAttentionNotificationPayload({
|
|
reason: "error",
|
|
serverId: "srv-3",
|
|
workspaceId: "workspace-3",
|
|
agentId: "agent-3",
|
|
});
|
|
|
|
expect(payload).toEqual({
|
|
title: "Agent needs attention",
|
|
body: "Encountered an error.",
|
|
data: {
|
|
serverId: "srv-3",
|
|
workspaceId: "workspace-3",
|
|
agentId: "agent-3",
|
|
reason: "error",
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("findLatestAssistantMessageFromTimeline", () => {
|
|
it("joins the latest contiguous assistant chunks", () => {
|
|
expect(
|
|
findLatestAssistantMessageFromTimeline([
|
|
{ type: "user_message", text: "start" },
|
|
{ type: "assistant_message", text: "Part " },
|
|
{ type: "assistant_message", text: "one" },
|
|
{ type: "reasoning", text: "thinking..." },
|
|
{ type: "assistant_message", text: "Done " },
|
|
{ type: "assistant_message", text: "now" },
|
|
]),
|
|
).toBe("Done now");
|
|
});
|
|
});
|
|
|
|
describe("findLatestPermissionRequest", () => {
|
|
it("returns the most recently inserted request", () => {
|
|
const pending = new Map([
|
|
["first", { id: "first", provider: "claude", name: "a", kind: "tool" } as const],
|
|
["second", { id: "second", provider: "claude", name: "b", kind: "tool" } as const],
|
|
]);
|
|
|
|
expect(findLatestPermissionRequest(pending)?.id).toBe("second");
|
|
});
|
|
});
|