Skip to content

Commit e96796d

Browse files
authored
Merge pull request #340 from essinghigh/notification-toast-dev
feat: support toast notifications via pruneNotificationType
2 parents 4e03a4b + 7e3a43c commit e96796d

3 files changed

Lines changed: 103 additions & 0 deletions

File tree

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: 20 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,8 @@ 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:
750+
result.data.pruneNotificationType ?? config.pruneNotificationType,
735751
commands: mergeCommands(config.commands, result.data.commands as any),
736752
turnProtection: {
737753
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
@@ -775,6 +791,8 @@ export function getConfig(ctx: PluginInput): PluginConfig {
775791
enabled: result.data.enabled ?? config.enabled,
776792
debug: result.data.debug ?? config.debug,
777793
pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
794+
pruneNotificationType:
795+
result.data.pruneNotificationType ?? config.pruneNotificationType,
778796
commands: mergeCommands(config.commands, result.data.commands as any),
779797
turnProtection: {
780798
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
@@ -815,6 +833,8 @@ export function getConfig(ctx: PluginInput): PluginConfig {
815833
enabled: result.data.enabled ?? config.enabled,
816834
debug: result.data.debug ?? config.debug,
817835
pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
836+
pruneNotificationType:
837+
result.data.pruneNotificationType ?? config.pruneNotificationType,
818838
commands: mergeCommands(config.commands, result.data.commands as any),
819839
turnProtection: {
820840
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,

lib/ui/notification.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ 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(
87+
message: string,
88+
maxChars: number = TOAST_SUMMARY_MAX_CHARS,
89+
): string {
90+
const marker = "\n\n▣ Extracted"
91+
const index = message.indexOf(marker)
92+
if (index === -1) {
93+
return message
94+
}
95+
const extracted = message.slice(index)
96+
if (extracted.length <= maxChars) {
97+
return message
98+
}
99+
return message.slice(0, index) + truncateToastSummary(extracted, maxChars)
100+
}
101+
66102
export async function sendUnifiedNotification(
67103
client: any,
68104
logger: Logger,
@@ -100,6 +136,22 @@ export async function sendUnifiedNotification(
100136
showDistillation,
101137
)
102138

139+
if (config.pruneNotificationType === "toast") {
140+
let toastMessage = truncateExtractedSection(message)
141+
toastMessage =
142+
config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage)
143+
144+
await client.tui.showToast({
145+
body: {
146+
title: "DCP: Prune Notification",
147+
message: toastMessage,
148+
variant: "info",
149+
duration: 5000,
150+
},
151+
})
152+
return true
153+
}
154+
103155
await sendIgnoredMessage(client, sessionId, message, params, logger)
104156
return true
105157
}
@@ -150,6 +202,31 @@ export async function sendCompressNotification(
150202
}
151203
}
152204

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

0 commit comments

Comments
 (0)