diff --git a/packages/web/server/lib/notifications/DOCUMENTATION.md b/packages/web/server/lib/notifications/DOCUMENTATION.md index 27ea3a96..a0a27dd0 100644 --- a/packages/web/server/lib/notifications/DOCUMENTATION.md +++ b/packages/web/server/lib/notifications/DOCUMENTATION.md @@ -1,7 +1,7 @@ # Notifications Module Documentation ## Purpose -This module provides notification message preparation utilities for the web server runtime, including text truncation and optional message summarization for system notifications. +This module provides notification message preparation utilities for the web server runtime, including text truncation, plain-text normalization, and optional message summarization for system notifications. ## Entrypoints and structure - `packages/web/server/lib/notifications/index.js`: public entrypoint imported by `packages/web/server/index.js`. @@ -107,6 +107,7 @@ The `settings` parameter for `prepareNotificationLastMessage` supports: - Returns empty string for empty/null message. - Returns truncated original message if summarization disabled, message under threshold, or summarization fails. - Returns truncated summary if summarization succeeds and returns non-empty string. +- Normalizes markdown-like formatting to plain text before truncation. - Always applies `maxLastMessageLength` truncation to final result. ## Notes for contributors diff --git a/packages/web/server/lib/notifications/message.js b/packages/web/server/lib/notifications/message.js index 98224222..8eb2e52d 100644 --- a/packages/web/server/lib/notifications/message.js +++ b/packages/web/server/lib/notifications/message.js @@ -9,6 +9,26 @@ const resolvePositiveNumber = (value, fallback) => { return value; }; +const normalizeNotificationPlainText = (text) => { + if (typeof text !== 'string') { + return ''; + } + + return text + .replace(/```[\s\S]*?```/g, ' ') + .replace(/`([^`]*)`/g, '$1') + .replace(/^[\t ]*[-*+]\s+/gm, '') + .replace(/^#{1,6}\s+/gm, '') + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/__(.*?)__/g, '$1') + .replace(/\*(.*?)\*/g, '$1') + .replace(/_(.*?)_/g, '$1') + .replace(/\[(.*?)\]\((.*?)\)/g, '$1') + .replace(/\s*\n\s*/g, ' ') + .replace(/\s+/g, ' ') + .trim(); +}; + export const truncateNotificationText = (text, maxLength = DEFAULT_NOTIFICATION_MESSAGE_MAX_LENGTH) => { if (typeof text !== 'string') { return ''; @@ -45,5 +65,6 @@ export const prepareNotificationLastMessage = async ({ message, settings, summar } } - return truncateNotificationText(messageForNotification, maxLastMessageLength); + const plainTextMessage = normalizeNotificationPlainText(messageForNotification) || normalizeNotificationPlainText(originalMessage); + return truncateNotificationText(plainTextMessage, maxLastMessageLength); }; diff --git a/packages/web/server/lib/notifications/message.test.js b/packages/web/server/lib/notifications/message.test.js index 8d4f74cd..e455910d 100644 --- a/packages/web/server/lib/notifications/message.test.js +++ b/packages/web/server/lib/notifications/message.test.js @@ -56,4 +56,19 @@ describe('notification message helpers', () => { expect(result).toBe('short summary'); }); + + it('normalizes markdown summary to plain text', async () => { + const result = await prepareNotificationLastMessage({ + message: '0123456789', + summarize: async () => "**Committed.**\n\n- Commit: `85924b9d`\n- Message: `fix desktop notifications`", + settings: { + summarizeLastMessage: true, + summaryThreshold: 5, + summaryLength: 80, + maxLastMessageLength: 200, + }, + }); + + expect(result).toBe('Committed. Commit: 85924b9d Message: fix desktop notifications'); + }); }); diff --git a/packages/web/server/lib/notifications/runtime.js b/packages/web/server/lib/notifications/runtime.js index 8bd276d6..e8bb32c3 100644 --- a/packages/web/server/lib/notifications/runtime.js +++ b/packages/web/server/lib/notifications/runtime.js @@ -134,7 +134,6 @@ export const createNotificationTriggerRuntime = (deps) => { } const sessionId = extractSessionIdFromPayload(payload); - if (payload.type === 'message.updated') { const info = payload.properties?.info; if (info?.role === 'assistant' && info?.finish === 'stop' && sessionId) { diff --git a/packages/web/server/lib/notifications/template-runtime.js b/packages/web/server/lib/notifications/template-runtime.js index f0641174..a93005ed 100644 --- a/packages/web/server/lib/notifications/template-runtime.js +++ b/packages/web/server/lib/notifications/template-runtime.js @@ -138,7 +138,7 @@ export const createNotificationTemplateRuntime = (deps) => { if (!text || typeof text !== 'string' || text.trim().length === 0) return text; try { - const prompt = `Summarize the following text in approximately ${targetLength} characters. Be concise and capture the key point. Output ONLY the summary text, nothing else.\n\nText:\n${text}`; + const prompt = `Summarize the following text in approximately ${targetLength} characters. Be concise and capture the key point. Output plain text only. Do not use markdown, bullets, headings, code fences, backticks, or quotes. Output only the summary text.\n\nText:\n${text}`; const completionTimeout = createTimeoutSignal(15000); let response;