mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-12 17:12:06 +08:00
fix: correct notification session id
This commit is contained in:
@@ -98,6 +98,44 @@ function normalizeSessionName(sessionName) {
|
||||
return normalized.length > 80 ? `${normalized.slice(0, 77)}...` : normalized;
|
||||
}
|
||||
|
||||
function rowMatchesProvider(row, provider) {
|
||||
return row && (!provider || row.provider === provider);
|
||||
}
|
||||
|
||||
function resolveSessionRow(sessionId, provider) {
|
||||
if (!sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const appSessionRow = sessionsDb.getSessionById(sessionId);
|
||||
if (rowMatchesProvider(appSessionRow, provider)) {
|
||||
return appSessionRow;
|
||||
}
|
||||
|
||||
const providerSessionRow = sessionsDb.getSessionByProviderSessionId(sessionId);
|
||||
if (rowMatchesProvider(providerSessionRow, provider)) {
|
||||
return providerSessionRow;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeNotificationSession(event) {
|
||||
if (!event?.sessionId || !event.provider || event.provider === 'system') {
|
||||
return event;
|
||||
}
|
||||
|
||||
const row = resolveSessionRow(event.sessionId, event.provider);
|
||||
if (!row || row.session_id === event.sessionId) {
|
||||
return event;
|
||||
}
|
||||
|
||||
return {
|
||||
...event,
|
||||
sessionId: row.session_id
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSessionName(event) {
|
||||
const explicitSessionName = normalizeSessionName(event.meta?.sessionName);
|
||||
if (explicitSessionName) {
|
||||
@@ -112,28 +150,29 @@ function resolveSessionName(event) {
|
||||
}
|
||||
|
||||
function buildPushBody(event) {
|
||||
const normalizedEvent = normalizeNotificationSession(event);
|
||||
const CODE_MAP = {
|
||||
'permission.required': event.meta?.toolName
|
||||
? `Action Required: Tool "${event.meta.toolName}" needs approval`
|
||||
'permission.required': normalizedEvent.meta?.toolName
|
||||
? `Action Required: Tool "${normalizedEvent.meta.toolName}" needs approval`
|
||||
: 'Action Required: A tool needs your approval',
|
||||
'run.stopped': event.meta?.stopReason || 'Run Stopped: The run has stopped',
|
||||
'run.failed': event.meta?.error ? `Run Failed: ${event.meta.error}` : 'Run Failed: The run encountered an error',
|
||||
'agent.notification': event.meta?.message ? String(event.meta.message) : 'You have a new notification',
|
||||
'run.stopped': normalizedEvent.meta?.stopReason || 'Run Stopped: The run has stopped',
|
||||
'run.failed': normalizedEvent.meta?.error ? `Run Failed: ${normalizedEvent.meta.error}` : 'Run Failed: The run encountered an error',
|
||||
'agent.notification': normalizedEvent.meta?.message ? String(normalizedEvent.meta.message) : 'You have a new notification',
|
||||
'push.enabled': 'Push notifications are now enabled!'
|
||||
};
|
||||
const providerLabel = PROVIDER_LABELS[event.provider] || 'Assistant';
|
||||
const sessionName = resolveSessionName(event);
|
||||
const message = CODE_MAP[event.code] || 'You have a new notification';
|
||||
const providerLabel = PROVIDER_LABELS[normalizedEvent.provider] || 'Assistant';
|
||||
const sessionName = resolveSessionName(normalizedEvent);
|
||||
const message = CODE_MAP[normalizedEvent.code] || 'You have a new notification';
|
||||
|
||||
return {
|
||||
title: sessionName || 'CloudCLI',
|
||||
body: `${providerLabel}: ${message}`,
|
||||
data: {
|
||||
sessionId: event.sessionId || null,
|
||||
code: event.code,
|
||||
provider: event.provider || null,
|
||||
sessionId: normalizedEvent.sessionId || null,
|
||||
code: normalizedEvent.code,
|
||||
provider: normalizedEvent.provider || null,
|
||||
sessionName,
|
||||
tag: `${event.provider || 'assistant'}:${event.sessionId || 'none'}:${event.code}`
|
||||
tag: `${normalizedEvent.provider || 'assistant'}:${normalizedEvent.sessionId || 'none'}:${normalizedEvent.code}`
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -175,15 +214,16 @@ function notifyUserIfEnabled({ userId, event }) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedEvent = normalizeNotificationSession(event);
|
||||
const preferences = notificationPreferencesDb.getPreferences(userId);
|
||||
if (!shouldSendPush(preferences, event)) {
|
||||
if (!shouldSendPush(preferences, normalizedEvent)) {
|
||||
return;
|
||||
}
|
||||
if (isDuplicate(event)) {
|
||||
if (isDuplicate(normalizedEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendWebPush(userId, event).catch((err) => {
|
||||
sendWebPush(userId, normalizedEvent).catch((err) => {
|
||||
console.error('Web push send error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
80
server/services/notification-orchestrator.test.js
Normal file
80
server/services/notification-orchestrator.test.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import webPush from 'web-push';
|
||||
|
||||
import {
|
||||
closeConnection,
|
||||
initializeDatabase,
|
||||
notificationPreferencesDb,
|
||||
pushSubscriptionsDb,
|
||||
sessionsDb,
|
||||
userDb,
|
||||
} from '../modules/database/index.js';
|
||||
|
||||
import { notifyRunStopped } from './notification-orchestrator.js';
|
||||
|
||||
async function withIsolatedDatabase(runTest) {
|
||||
const previousDatabasePath = process.env.DATABASE_PATH;
|
||||
const tempDirectory = await mkdtemp(path.join(tmpdir(), 'notification-orchestrator-'));
|
||||
const databasePath = path.join(tempDirectory, 'auth.db');
|
||||
|
||||
closeConnection();
|
||||
process.env.DATABASE_PATH = databasePath;
|
||||
await initializeDatabase();
|
||||
|
||||
try {
|
||||
await runTest();
|
||||
} finally {
|
||||
closeConnection();
|
||||
if (previousDatabasePath === undefined) {
|
||||
delete process.env.DATABASE_PATH;
|
||||
} else {
|
||||
process.env.DATABASE_PATH = previousDatabasePath;
|
||||
}
|
||||
await rm(tempDirectory, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test('push payload uses the app session id when notified with a provider session id', async () => {
|
||||
const originalSendNotification = webPush.sendNotification;
|
||||
const sentPayloads = [];
|
||||
|
||||
webPush.sendNotification = async (_subscription, payload) => {
|
||||
sentPayloads.push(JSON.parse(payload));
|
||||
return {};
|
||||
};
|
||||
|
||||
try {
|
||||
await withIsolatedDatabase(async () => {
|
||||
const user = userDb.createUser('notify-user', 'hash');
|
||||
const userId = Number(user.id);
|
||||
|
||||
notificationPreferencesDb.updatePreferences(userId, {
|
||||
channels: { webPush: true },
|
||||
events: { actionRequired: true, stop: true, error: true },
|
||||
});
|
||||
pushSubscriptionsDb.saveSubscription(userId, 'https://example.test/push', 'p256dh', 'auth');
|
||||
sessionsDb.createAppSession('app-session-1', 'claude', '/workspace/demo');
|
||||
sessionsDb.assignProviderSessionId('app-session-1', 'claude-native-1');
|
||||
|
||||
notifyRunStopped({
|
||||
userId,
|
||||
provider: 'claude',
|
||||
sessionId: 'claude-native-1',
|
||||
stopReason: 'completed',
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.equal(sentPayloads.length, 1);
|
||||
assert.equal(sentPayloads[0]?.data?.sessionId, 'app-session-1');
|
||||
assert.match(sentPayloads[0]?.data?.tag, /app-session-1/);
|
||||
});
|
||||
} finally {
|
||||
webPush.sendNotification = originalSendNotification;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user