notification initial commit

This commit is contained in:
Arjun 2026-04-24 00:34:40 +05:30
parent 0bb256879c
commit 8595190bd4
5 changed files with 91 additions and 1 deletions

View file

@ -32,10 +32,11 @@ import started from "electron-squirrel-startup";
import { execSync, exec, execFileSync } from "node:child_process";
import { promisify } from "node:util";
import { init as initChromeSync } from "@x/core/dist/knowledge/chrome-extension/server/server.js";
import { registerBrowserControlService } from "@x/core/dist/di/container.js";
import { registerBrowserControlService, registerNotificationService } from "@x/core/dist/di/container.js";
import { browserViewManager, BROWSER_PARTITION } from "./browser/view.js";
import { setupBrowserEventForwarding } from "./browser/ipc.js";
import { ElectronBrowserControlService } from "./browser/control-service.js";
import { ElectronNotificationService } from "./notification/electron-notification-service.js";
const execAsync = promisify(exec);
@ -231,6 +232,7 @@ app.whenReady().then(async () => {
await initConfigs();
registerBrowserControlService(new ElectronBrowserControlService());
registerNotificationService(new ElectronNotificationService());
setupIpcHandlers();
setupBrowserEventForwarding();

View file

@ -0,0 +1,37 @@
import { BrowserWindow, Notification, shell } from "electron";
import type { INotificationService, NotifyInput } from "@x/core/dist/application/notification/service.js";
const HTTP_URL = /^https?:\/\//i;
export class ElectronNotificationService implements INotificationService {
isSupported(): boolean {
return Notification.isSupported();
}
notify({ title = "Rowboat", message, link }: NotifyInput): void {
const notification = new Notification({
title,
body: message,
});
notification.on("click", () => {
if (link && HTTP_URL.test(link)) {
shell.openExternal(link).catch((err) => {
console.error("[notification] failed to open link:", err);
});
return;
}
this.focusMainWindow();
});
notification.show();
}
private focusMainWindow(): void {
const [win] = BrowserWindow.getAllWindows();
if (!win) return;
if (win.isMinimized()) win.restore();
win.show();
win.focus();
}
}

View file

@ -27,6 +27,7 @@ import { getAccessToken } from "../../auth/tokens.js";
import { API_URL } from "../../config/env.js";
import { updateContent, updateTrackBlock } from "../../knowledge/track/fileops.js";
import type { IBrowserControlService } from "../browser-control/service.js";
import type { INotificationService } from "../notification/service.js";
// Parser libraries are loaded dynamically inside parseFile.execute()
// to avoid pulling pdfjs-dist's DOM polyfills into the main bundle.
// Import paths are computed so esbuild cannot statically resolve them.
@ -1514,4 +1515,37 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
}
},
},
'notify-user': {
description: "Show a native OS notification to the user. Clicking the notification opens the provided link in the default browser, or focuses the Rowboat app if no link is given.",
inputSchema: z.object({
title: z.string().min(1).max(120).optional().describe("Bold headline shown at the top of the notification. Defaults to 'Rowboat'."),
message: z.string().min(1).describe("Body text of the notification."),
link: z.string().url().refine((v) => /^https?:\/\//i.test(v), {
message: "link must be an http:// or https:// URL",
}).optional().describe("Optional http(s) URL opened when the user clicks the notification."),
}),
isAvailable: async () => {
try {
return container.resolve<INotificationService>('notificationService').isSupported();
} catch {
return false;
}
},
execute: async ({ title, message, link }: { title?: string; message: string; link?: string }) => {
try {
const service = container.resolve<INotificationService>('notificationService');
if (!service.isSupported()) {
return { success: false, error: 'Notifications are not supported on this system' };
}
service.notify({ title, message, link });
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
},
},
};

View file

@ -0,0 +1,10 @@
export interface NotifyInput {
title?: string;
message: string;
link?: string;
}
export interface INotificationService {
isSupported(): boolean;
notify(input: NotifyInput): void;
}

View file

@ -16,6 +16,7 @@ import { FSAgentScheduleRepo, IAgentScheduleRepo } from "../agent-schedule/repo.
import { FSAgentScheduleStateRepo, IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js";
import { FSSlackConfigRepo, ISlackConfigRepo } from "../slack/repo.js";
import type { IBrowserControlService } from "../application/browser-control/service.js";
import type { INotificationService } from "../application/notification/service.js";
const container = createContainer({
injectionMode: InjectionMode.PROXY,
@ -49,3 +50,9 @@ export function registerBrowserControlService(service: IBrowserControlService):
browserControlService: asValue(service),
});
}
export function registerNotificationService(service: INotificationService): void {
container.register({
notificationService: asValue(service),
});
}