Skip to content

Commit b3d9e5b

Browse files
committed
feat: support toast notifications via notificationType config option
1 parent 7453ed4 commit b3d9e5b

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

dcp.schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
"default": "detailed",
2727
"description": "Level of notification shown when pruning occurs"
2828
},
29+
"pruneNotificationType": {
30+
"type": "string",
31+
"enum": ["chat", "toast"],
32+
"default": "chat",
33+
"description": "Where to display prune notifications (chat message or toast notification)"
34+
},
2935
"commands": {
3036
"type": "object",
3137
"description": "Configuration for DCP slash commands (/dcp)",

lib/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface PluginConfig {
6060
enabled: boolean
6161
debug: boolean
6262
pruneNotification: "off" | "minimal" | "detailed"
63+
pruneNotificationType: "chat" | "toast"
6364
commands: Commands
6465
turnProtection: TurnProtection
6566
protectedFilePatterns: string[]
@@ -91,6 +92,7 @@ export const VALID_CONFIG_KEYS = new Set([
9192
"debug",
9293
"showUpdateToasts", // Deprecated but kept for backwards compatibility
9394
"pruneNotification",
95+
"pruneNotificationType",
9496
"turnProtection",
9597
"turnProtection.enabled",
9698
"turnProtection.turns",
@@ -173,6 +175,17 @@ function validateConfigTypes(config: Record<string, any>): ValidationError[] {
173175
}
174176
}
175177

178+
if (config.pruneNotificationType !== undefined) {
179+
const validValues = ["chat", "toast"]
180+
if (!validValues.includes(config.pruneNotificationType)) {
181+
errors.push({
182+
key: "pruneNotificationType",
183+
expected: '"chat" | "toast"',
184+
actual: JSON.stringify(config.pruneNotificationType),
185+
})
186+
}
187+
}
188+
176189
if (config.protectedFilePatterns !== undefined) {
177190
if (!Array.isArray(config.protectedFilePatterns)) {
178191
errors.push({
@@ -454,6 +467,7 @@ const defaultConfig: PluginConfig = {
454467
enabled: true,
455468
debug: false,
456469
pruneNotification: "detailed",
470+
pruneNotificationType: "chat",
457471
commands: {
458472
enabled: true,
459473
protectedTools: [...DEFAULT_PROTECTED_TOOLS],
@@ -732,6 +746,7 @@ export function getConfig(ctx: PluginInput): PluginConfig {
732746
enabled: result.data.enabled ?? config.enabled,
733747
debug: result.data.debug ?? config.debug,
734748
pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
749+
pruneNotificationType: result.data.pruneNotificationType ?? config.pruneNotificationType,
735750
commands: mergeCommands(config.commands, result.data.commands as any),
736751
turnProtection: {
737752
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
@@ -775,6 +790,7 @@ export function getConfig(ctx: PluginInput): PluginConfig {
775790
enabled: result.data.enabled ?? config.enabled,
776791
debug: result.data.debug ?? config.debug,
777792
pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
793+
pruneNotificationType: result.data.pruneNotificationType ?? config.pruneNotificationType,
778794
commands: mergeCommands(config.commands, result.data.commands as any),
779795
turnProtection: {
780796
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
@@ -815,6 +831,7 @@ export function getConfig(ctx: PluginInput): PluginConfig {
815831
enabled: result.data.enabled ?? config.enabled,
816832
debug: result.data.debug ?? config.debug,
817833
pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
834+
pruneNotificationType: result.data.pruneNotificationType ?? config.pruneNotificationType,
818835
commands: mergeCommands(config.commands, result.data.commands as any),
819836
turnProtection: {
820837
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,

lib/ui/notification.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,39 @@ function buildDetailedMessage(
6363
return (message + formatExtracted(showDistillation ? distillation : undefined)).trim()
6464
}
6565

66+
const TOAST_BODY_MAX_LINES = 12
67+
const TOAST_SUMMARY_MAX_CHARS = 600
68+
69+
function truncateToastBody(body: string, maxLines: number = TOAST_BODY_MAX_LINES): string {
70+
const lines = body.split("\n")
71+
if (lines.length <= maxLines) {
72+
return body
73+
}
74+
const kept = lines.slice(0, maxLines - 1)
75+
const remaining = lines.length - maxLines + 1
76+
return kept.join("\n") + `\n... and ${remaining} more`
77+
}
78+
79+
function truncateToastSummary(summary: string, maxChars: number = TOAST_SUMMARY_MAX_CHARS): string {
80+
if (summary.length <= maxChars) {
81+
return summary
82+
}
83+
return summary.slice(0, maxChars - 3) + "..."
84+
}
85+
86+
function truncateExtractedSection(message: string, maxChars: number = TOAST_SUMMARY_MAX_CHARS): string {
87+
const marker = "\n\n▣ Extracted"
88+
const index = message.indexOf(marker)
89+
if (index === -1) {
90+
return message
91+
}
92+
const extracted = message.slice(index)
93+
if (extracted.length <= maxChars) {
94+
return message
95+
}
96+
return message.slice(0, index) + truncateToastSummary(extracted, maxChars)
97+
}
98+
6699
export async function sendUnifiedNotification(
67100
client: any,
68101
logger: Logger,
@@ -100,6 +133,22 @@ export async function sendUnifiedNotification(
100133
showDistillation,
101134
)
102135

136+
if (config.pruneNotificationType === "toast") {
137+
let toastMessage = truncateExtractedSection(message)
138+
toastMessage =
139+
config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage)
140+
141+
await client.tui.showToast({
142+
body: {
143+
title: "DCP: Prune Notification",
144+
message: toastMessage,
145+
variant: "info",
146+
duration: 5000,
147+
},
148+
})
149+
return true
150+
}
151+
103152
await sendIgnoredMessage(client, sessionId, message, params, logger)
104153
return true
105154
}
@@ -150,6 +199,31 @@ export async function sendCompressNotification(
150199
}
151200
}
152201

202+
if (config.pruneNotificationType === "toast") {
203+
let toastMessage = message
204+
if (config.tools.compress.showCompression) {
205+
const truncatedSummary = truncateToastSummary(summary)
206+
if (truncatedSummary !== summary) {
207+
toastMessage = toastMessage.replace(
208+
`\n→ Compression: ${summary}`,
209+
`\n→ Compression: ${truncatedSummary}`,
210+
)
211+
}
212+
}
213+
toastMessage =
214+
config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage)
215+
216+
await client.tui.showToast({
217+
body: {
218+
title: "DCP: Compress Notification",
219+
message: toastMessage,
220+
variant: "info",
221+
duration: 5000,
222+
},
223+
})
224+
return true
225+
}
226+
153227
await sendIgnoredMessage(client, sessionId, message, params, logger)
154228
return true
155229
}

0 commit comments

Comments
 (0)