Skip to content

Commit d85c0b1

Browse files
feat(notify): ensure all WhatsApp messages include session ID and min 2 options
- Add session ID to all notification titles: [Claude <id>] - formatSummaryMessage now accepts sessionId parameter - generateSuggestions always returns minimum 2 options - notifyReviewReady ensures min 2 options (Approve/Request changes) - notifyTaskComplete adds options (Start next task/View details) - notifyError adds options (Retry/View logs) - notifyWithYesNo already has 2 options (Yes/No)
1 parent b96a0af commit d85c0b1

3 files changed

Lines changed: 106 additions & 26 deletions

File tree

src/cli/claude-sm.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -429,28 +429,39 @@ class ClaudeSM {
429429
};
430430

431431
const summary = await generateSessionSummary(context);
432-
const message = formatSummaryMessage(summary);
432+
const message = formatSummaryMessage(summary, this.config.instanceId);
433433

434434
console.log(chalk.cyan('\nSending session summary via WhatsApp...'));
435435

436-
// Build options from suggestions for interactive response
437-
const options = summary.suggestions.slice(0, 4).map((s) => ({
436+
// Build options from suggestions for interactive response (always min 2)
437+
let options = summary.suggestions.slice(0, 4).map((s) => ({
438438
key: s.key,
439439
label: s.label,
440440
action: s.action,
441441
}));
442442

443+
// Ensure minimum 2 options
444+
if (options.length < 2) {
445+
const defaults = [
446+
{ key: '1', label: 'Start new session', action: 'claude-sm' },
447+
{
448+
key: '2',
449+
label: 'View logs',
450+
action: 'tail -30 ~/.claude/logs/*.log',
451+
},
452+
];
453+
options = defaults.slice(0, 2 - options.length).concat(options);
454+
options.forEach((o, i) => (o.key = String(i + 1)));
455+
}
456+
443457
const result = await sendNotification({
444458
type: 'task_complete',
445-
title: 'Claude Session Complete',
459+
title: `Claude Session ${this.config.instanceId}`,
446460
message,
447-
prompt:
448-
options.length > 0
449-
? {
450-
type: 'options',
451-
options,
452-
}
453-
: undefined,
461+
prompt: {
462+
type: 'options',
463+
options,
464+
},
454465
});
455466

456467
if (result.success) {

src/hooks/session-summary.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,28 @@ async function generateSuggestions(
207207

208208
// Sort by priority (highest first) and re-key
209209
suggestions.sort((a, b) => b.priority - a.priority);
210+
211+
// Ensure minimum 2 options always
212+
if (suggestions.length < 2) {
213+
// Add default options if not enough suggestions
214+
if (suggestions.length === 0) {
215+
suggestions.push({
216+
key: '1',
217+
label: 'Start new Claude session',
218+
action: 'claude-sm',
219+
priority: 50,
220+
});
221+
}
222+
if (suggestions.length < 2) {
223+
suggestions.push({
224+
key: '2',
225+
label: 'View session logs',
226+
action: 'cat ~/.claude/logs/claude-*.log | tail -30',
227+
priority: 40,
228+
});
229+
}
230+
}
231+
210232
suggestions.forEach((s, i) => {
211233
s.key = String(i + 1);
212234
});
@@ -252,13 +274,18 @@ export async function generateSessionSummary(
252274
/**
253275
* Format session summary as WhatsApp message
254276
*/
255-
export function formatSummaryMessage(summary: SessionSummary): string {
277+
export function formatSummaryMessage(
278+
summary: SessionSummary,
279+
sessionId?: string
280+
): string {
256281
const statusEmoji = summary.status === 'success' ? '' : '';
257282
const exitInfo =
258283
summary.exitCode !== null ? ` | Exit: ${summary.exitCode}` : '';
284+
const sessionInfo = sessionId ? ` | Session: ${sessionId}` : '';
259285

260286
let message = `Claude session complete ${statusEmoji}\n`;
261-
message += `Duration: ${summary.duration}${exitInfo} | Branch: ${summary.branch}\n\n`;
287+
message += `Duration: ${summary.duration}${exitInfo}${sessionInfo}\n`;
288+
message += `Branch: ${summary.branch}\n\n`;
262289

263290
if (summary.suggestions.length > 0) {
264291
message += `What to do next:\n`;

src/hooks/sms-notify.ts

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -524,30 +524,51 @@ export function processIncomingResponse(
524524
return { matched: false, prompt };
525525
}
526526

527+
// Get session ID from environment or generate short ID
528+
function getSessionId(): string {
529+
return (
530+
process.env['CLAUDE_INSTANCE_ID'] ||
531+
process.env['STACKMEMORY_SESSION_ID'] ||
532+
Math.random().toString(36).substring(2, 8)
533+
);
534+
}
535+
527536
// Convenience functions for common notifications
528537

529538
export async function notifyReviewReady(
530539
title: string,
531540
description: string,
532541
options?: { label: string; action?: string }[]
533542
): Promise<{ success: boolean; promptId?: string; error?: string }> {
543+
const sessionId = getSessionId();
544+
545+
// Ensure minimum 2 options
546+
let finalOptions = options || [];
547+
if (finalOptions.length < 2) {
548+
const defaults = [
549+
{ label: 'Approve', action: 'echo "Approved"' },
550+
{ label: 'Request changes', action: 'echo "Changes requested"' },
551+
];
552+
finalOptions = [...finalOptions, ...defaults].slice(
553+
0,
554+
Math.max(2, finalOptions.length)
555+
);
556+
}
557+
534558
const payload: NotificationPayload = {
535559
type: 'review_ready',
536-
title: `Review Ready: ${title}`,
560+
title: `[Claude ${sessionId}] Review Ready: ${title}`,
537561
message: description,
538-
};
539-
540-
if (options && options.length > 0) {
541-
payload.prompt = {
562+
prompt: {
542563
type: 'options',
543-
options: options.map((opt, i) => ({
564+
options: finalOptions.map((opt, i) => ({
544565
key: String(i + 1),
545566
label: opt.label,
546567
action: opt.action,
547568
})),
548569
question: 'What would you like to do?',
549-
};
550-
}
570+
},
571+
};
551572

552573
return sendSMSNotification(payload);
553574
}
@@ -558,9 +579,10 @@ export async function notifyWithYesNo(
558579
yesAction?: string,
559580
noAction?: string
560581
): Promise<{ success: boolean; promptId?: string; error?: string }> {
582+
const sessionId = getSessionId();
561583
return sendSMSNotification({
562584
type: 'custom',
563-
title,
585+
title: `[Claude ${sessionId}] ${title}`,
564586
message: question,
565587
prompt: {
566588
type: 'yesno',
@@ -575,22 +597,42 @@ export async function notifyWithYesNo(
575597
export async function notifyTaskComplete(
576598
taskName: string,
577599
summary: string
578-
): Promise<{ success: boolean; error?: string }> {
600+
): Promise<{ success: boolean; promptId?: string; error?: string }> {
601+
const sessionId = getSessionId();
579602
return sendSMSNotification({
580603
type: 'task_complete',
581-
title: `Task Complete: ${taskName}`,
604+
title: `[Claude ${sessionId}] Task Complete: ${taskName}`,
582605
message: summary,
606+
prompt: {
607+
type: 'options',
608+
options: [
609+
{ key: '1', label: 'Start next task', action: 'claude-sm' },
610+
{ key: '2', label: 'View details', action: 'stackmemory status' },
611+
],
612+
},
583613
});
584614
}
585615

586616
export async function notifyError(
587617
error: string,
588618
context?: string
589-
): Promise<{ success: boolean; error?: string }> {
619+
): Promise<{ success: boolean; promptId?: string; error?: string }> {
620+
const sessionId = getSessionId();
590621
return sendSMSNotification({
591622
type: 'error',
592-
title: 'Error Alert',
623+
title: `[Claude ${sessionId}] Error Alert`,
593624
message: context ? `${error}\n\nContext: ${context}` : error,
625+
prompt: {
626+
type: 'options',
627+
options: [
628+
{ key: '1', label: 'Retry', action: 'claude-sm' },
629+
{
630+
key: '2',
631+
label: 'View logs',
632+
action: 'tail -50 ~/.claude/logs/*.log',
633+
},
634+
],
635+
},
594636
});
595637
}
596638

0 commit comments

Comments
 (0)