feat: introduce notification system and claude notifications (#450)

* feat: introduce notification system and claude notifications

* fix(sw): prevent caching of API requests and WebSocket upgrades

* default to false for webpush notifications and translations for the button

* fix: notifications orchestrator and add a notification when  first enabled

* fix: remove unused state update and dependency in settings controller hook

* fix: show notifications settings tab

* fix: add notifications for response completion for all providers

* feat: show session name in notification and don't reload tab on clicking
--- the notification

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Haileyesus <something@gmail.com>
This commit is contained in:
Simos Mikelatos
2026-03-13 16:59:09 +01:00
committed by GitHub
parent 6f6dacad5e
commit 45e71a0e73
36 changed files with 1629 additions and 69 deletions

View File

@@ -9,11 +9,12 @@ import os from 'os';
import { getSessions, getSessionMessages } from './projects.js';
import sessionManager from './sessionManager.js';
import GeminiResponseHandler from './gemini-response-handler.js';
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
let activeGeminiProcesses = new Map(); // Track active processes by session ID
async function spawnGemini(command, options = {}, ws) {
const { sessionId, projectPath, cwd, resume, toolsSettings, permissionMode, images } = options;
const { sessionId, projectPath, cwd, resume, toolsSettings, permissionMode, images, sessionSummary } = options;
let capturedSessionId = sessionId; // Track session ID throughout the process
let sessionCreatedSent = false; // Track if we've already sent session-created event
let assistantBlocks = []; // Accumulate the full response blocks including tools
@@ -172,6 +173,36 @@ async function spawnGemini(command, options = {}, ws) {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env } // Inherit all environment variables
});
let terminalNotificationSent = false;
let terminalFailureReason = null;
const notifyTerminalState = ({ code = null, error = null } = {}) => {
if (terminalNotificationSent) {
return;
}
terminalNotificationSent = true;
const finalSessionId = capturedSessionId || sessionId || processKey;
if (code === 0 && !error) {
notifyRunStopped({
userId: ws?.userId || null,
provider: 'gemini',
sessionId: finalSessionId,
sessionName: sessionSummary,
stopReason: 'completed'
});
return;
}
notifyRunFailed({
userId: ws?.userId || null,
provider: 'gemini',
sessionId: finalSessionId,
sessionName: sessionSummary,
error: error || terminalFailureReason || `Gemini CLI exited with code ${code}`
});
};
// Attach temp file info to process for cleanup later
geminiProcess.tempImagePaths = tempImagePaths;
@@ -196,10 +227,12 @@ async function spawnGemini(command, options = {}, ws) {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
const socketSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : (capturedSessionId || sessionId || processKey);
terminalFailureReason = `Gemini CLI timeout - no response received for ${timeoutMs / 1000} seconds`;
ws.send({
type: 'gemini-error',
sessionId: socketSessionId,
error: `Gemini CLI timeout - no response received for ${timeoutMs / 1000} seconds`
error: terminalFailureReason,
provider: 'gemini'
});
try {
geminiProcess.kill('SIGTERM');
@@ -340,7 +373,8 @@ async function spawnGemini(command, options = {}, ws) {
ws.send({
type: 'gemini-error',
sessionId: socketSessionId,
error: errorMsg
error: errorMsg,
provider: 'gemini'
});
});
@@ -367,6 +401,7 @@ async function spawnGemini(command, options = {}, ws) {
type: 'claude-complete', // Use claude-complete for compatibility with UI
sessionId: finalSessionId,
exitCode: code,
provider: 'gemini',
isNewSession: !sessionId && !!command // Flag to indicate this was a new session
});
@@ -381,8 +416,13 @@ async function spawnGemini(command, options = {}, ws) {
}
if (code === 0) {
notifyTerminalState({ code });
resolve();
} else {
notifyTerminalState({
code,
error: code === null ? 'Gemini CLI process was terminated or timed out' : null
});
reject(new Error(code === null ? 'Gemini CLI process was terminated or timed out' : `Gemini CLI exited with code ${code}`));
}
});
@@ -397,8 +437,10 @@ async function spawnGemini(command, options = {}, ws) {
ws.send({
type: 'gemini-error',
sessionId: errorSessionId,
error: error.message
error: error.message,
provider: 'gemini'
});
notifyTerminalState({ error });
reject(error);
});