Make toast notifications more informative
- Server: Add bodySnippet, identifier, issueTitle to comment_added activity details so the UI can show comment content - Client: Show comment snippet in comment toasts instead of just "posted a comment on PAP-39" - Client: Add agent title/role as body text in agent status toasts - Client: Show trigger detail in run status toasts for context PAP-31 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -302,7 +302,12 @@ export function issueRoutes(db: Db, storage: StorageService) {
|
|||||||
action: "issue.comment_added",
|
action: "issue.comment_added",
|
||||||
entityType: "issue",
|
entityType: "issue",
|
||||||
entityId: issue.id,
|
entityId: issue.id,
|
||||||
details: { commentId: comment.id },
|
details: {
|
||||||
|
commentId: comment.id,
|
||||||
|
bodySnippet: comment.body.slice(0, 120),
|
||||||
|
identifier: issue.identifier,
|
||||||
|
issueTitle: issue.title,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -557,7 +562,12 @@ export function issueRoutes(db: Db, storage: StorageService) {
|
|||||||
action: "issue.comment_added",
|
action: "issue.comment_added",
|
||||||
entityType: "issue",
|
entityType: "issue",
|
||||||
entityId: currentIssue.id,
|
entityId: currentIssue.id,
|
||||||
details: { commentId: comment.id },
|
details: {
|
||||||
|
commentId: comment.id,
|
||||||
|
bodySnippet: comment.body.slice(0, 120),
|
||||||
|
identifier: currentIssue.identifier,
|
||||||
|
issueTitle: currentIssue.title,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Merge all wakeups from this comment into one enqueue per agent to avoid duplicate runs.
|
// Merge all wakeups from this comment into one enqueue per agent to avoid duplicate runs.
|
||||||
|
|||||||
@@ -173,9 +173,14 @@ function buildActivityToast(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const commentId = readString(details?.commentId);
|
const commentId = readString(details?.commentId);
|
||||||
|
const bodySnippet = readString(details?.bodySnippet);
|
||||||
return {
|
return {
|
||||||
title: `${actor} posted a comment on ${issue.ref}`,
|
title: `${actor} commented on ${issue.ref}`,
|
||||||
body: issue.title ? truncate(issue.title, 96) : undefined,
|
body: bodySnippet
|
||||||
|
? truncate(bodySnippet.replace(/^#+\s*/m, "").replace(/\n/g, " "), 96)
|
||||||
|
: issue.title
|
||||||
|
? truncate(issue.title, 96)
|
||||||
|
: undefined,
|
||||||
tone: "info",
|
tone: "info",
|
||||||
action: { label: `View ${issue.ref}`, href: issue.href },
|
action: { label: `View ${issue.ref}`, href: issue.href },
|
||||||
dedupeKey: `activity:${action}:${entityId}:${commentId ?? "na"}`,
|
dedupeKey: `activity:${action}:${entityId}:${commentId ?? "na"}`,
|
||||||
@@ -185,6 +190,8 @@ function buildActivityToast(
|
|||||||
function buildAgentStatusToast(
|
function buildAgentStatusToast(
|
||||||
payload: Record<string, unknown>,
|
payload: Record<string, unknown>,
|
||||||
nameOf: (id: string) => string | null,
|
nameOf: (id: string) => string | null,
|
||||||
|
queryClient: QueryClient,
|
||||||
|
companyId: string,
|
||||||
): ToastInput | null {
|
): ToastInput | null {
|
||||||
const agentId = readString(payload.agentId);
|
const agentId = readString(payload.agentId);
|
||||||
const status = readString(payload.status);
|
const status = readString(payload.status);
|
||||||
@@ -199,8 +206,13 @@ function buildAgentStatusToast(
|
|||||||
? `${name} is idle`
|
? `${name} is idle`
|
||||||
: `${name} errored`;
|
: `${name} errored`;
|
||||||
|
|
||||||
|
const agents = queryClient.getQueryData<Agent[]>(queryKeys.agents.list(companyId));
|
||||||
|
const agent = agents?.find((a) => a.id === agentId);
|
||||||
|
const body = agent?.title ?? undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
body,
|
||||||
tone,
|
tone,
|
||||||
action: { label: "View agent", href: `/agents/${agentId}` },
|
action: { label: "View agent", href: `/agents/${agentId}` },
|
||||||
dedupeKey: `agent-status:${agentId}:${status}`,
|
dedupeKey: `agent-status:${agentId}:${status}`,
|
||||||
@@ -217,20 +229,26 @@ function buildRunStatusToast(
|
|||||||
if (!runId || !agentId || !status || !TERMINAL_RUN_STATUSES.has(status)) return null;
|
if (!runId || !agentId || !status || !TERMINAL_RUN_STATUSES.has(status)) return null;
|
||||||
|
|
||||||
const error = readString(payload.error);
|
const error = readString(payload.error);
|
||||||
|
const triggerDetail = readString(payload.triggerDetail);
|
||||||
const name = nameOf(agentId) ?? `Agent ${shortId(agentId)}`;
|
const name = nameOf(agentId) ?? `Agent ${shortId(agentId)}`;
|
||||||
const tone = status === "succeeded" ? "success" : status === "cancelled" ? "warn" : "error";
|
const tone = status === "succeeded" ? "success" : status === "cancelled" ? "warn" : "error";
|
||||||
const title =
|
const statusLabel =
|
||||||
status === "succeeded"
|
status === "succeeded" ? "succeeded"
|
||||||
? `${name} run succeeded`
|
: status === "failed" ? "failed"
|
||||||
: status === "failed"
|
: status === "timed_out" ? "timed out"
|
||||||
? `${name} run failed`
|
: "cancelled";
|
||||||
: status === "timed_out"
|
const title = `${name} run ${statusLabel}`;
|
||||||
? `${name} run timed out`
|
|
||||||
: `${name} run cancelled`;
|
let body: string | undefined;
|
||||||
|
if (error) {
|
||||||
|
body = truncate(error, 100);
|
||||||
|
} else if (triggerDetail) {
|
||||||
|
body = `Trigger: ${triggerDetail}`;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
body: error ? truncate(error, 100) : undefined,
|
body,
|
||||||
tone,
|
tone,
|
||||||
ttlMs: status === "succeeded" ? 5000 : 7000,
|
ttlMs: status === "succeeded" ? 5000 : 7000,
|
||||||
action: { label: "View run", href: `/agents/${agentId}/runs/${runId}` },
|
action: { label: "View run", href: `/agents/${agentId}/runs/${runId}` },
|
||||||
@@ -388,7 +406,7 @@ function handleLiveEvent(
|
|||||||
queryClient.invalidateQueries({ queryKey: queryKeys.org(expectedCompanyId) });
|
queryClient.invalidateQueries({ queryKey: queryKeys.org(expectedCompanyId) });
|
||||||
const agentId = readString(payload.agentId);
|
const agentId = readString(payload.agentId);
|
||||||
if (agentId) queryClient.invalidateQueries({ queryKey: queryKeys.agents.detail(agentId) });
|
if (agentId) queryClient.invalidateQueries({ queryKey: queryKeys.agents.detail(agentId) });
|
||||||
const toast = buildAgentStatusToast(payload, nameOf);
|
const toast = buildAgentStatusToast(payload, nameOf, queryClient, expectedCompanyId);
|
||||||
if (toast) gatedPushToast(gate, pushToast, "agent-status", toast);
|
if (toast) gatedPushToast(gate, pushToast, "agent-status", toast);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user