Files

1432 lines
56 KiB
JavaScript

// Generated by scripts/build-client.mjs — do not edit this file.
// Source of truth: src/client/**. Run `pnpm run build` after changing it.
// Served by dsh-client-modules at /plugins/@dsh-plugin/session-notify/client.js.
window.__ModuleLoader__.load({
id: '@dsh-plugin/session-notify',
factory(require) {
const module = { exports: {} }
// Same object as `module.exports`; kept so the emitted body may assign
// through either binding.
const exports = module.exports
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/client/index.js
var index_exports = {};
__export(index_exports, {
apply: () => apply,
inject: () => inject
});
module.exports = __toCommonJS(index_exports);
// src/client/constants.js
var NS = "session-notify";
var PACKAGE_NAME = "@dsh-plugin/session-notify";
var KINDS = ["completion", "approval", "question"];
var PENDING_KINDS = ["approval", "question", "plan-review"];
var THROTTLE_MS = 1500;
var SETTLE_MS = 400;
var TOAST_DURATION_MS = 6e3;
var TOAST_LIMIT = 3;
var DISMISS_CHOICES = [3e3, 6e3, 1e4, 3e4, 0];
var RAISED_LIMIT = 8;
var REPLAY_LIMIT = 8;
var REPLAY_TTL_MS = 30 * 60 * 1e3;
var REPLAY_QUIET_MS = 20 * 1e3;
// src/client/i18n/zh.js
var zh = {
"notification.completion": "会话已完成",
"notification.approval": "需要授权",
"notification.question": "需要回答",
"body.approval": "{title}:工具 {tool} 正在等待你的授权",
"body.approvalPlain": "{title}:有工具正在等待你的授权",
"body.question": "{title}:正在等待你的回答",
"body.planReview": "{title}:计划正在等待你确认",
"body.untitled": "未命名会话",
"toast.view": "查看",
"toast.dismiss": "关闭",
"settings.description": "窗口不在前台时用系统通知,窗口在前台时用应用内轻弹窗;正在看的那个会话完成后不打扰。",
"settings.permission.granted": "系统通知已开启",
"settings.permission.default": "尚未授予系统通知权限",
"settings.permission.denied": "系统通知已被系统设置关闭",
"settings.permission.unsupported": "当前环境不支持系统通知,仍会显示应用内轻弹窗",
"settings.permission.hint.plain": "请在系统通知设置中允许 DeepSeek Harness",
"settings.permission.hint.win": "Windows 设置 → 系统 → 通知 → DeepSeek Harness",
"settings.permission.hint.mac": "系统设置 → 通知 → DeepSeek Harness",
"settings.permission.hint.linux": "在桌面环境的通知设置(GNOME / KDE)中允许 DeepSeek Harness",
"settings.action.allow": "允许通知",
"settings.action.testSystem": "测试系统通知",
"settings.action.testAny": "测试提醒",
"settings.action.unsupported": "不可用",
"settings.permission.promptFailed": "无法请求通知权限:{message}",
"settings.health.watching": "正在监听会话状态",
"settings.health.noHooks": "未收到会话状态(通知不会触发)",
"settings.health.error": "监听会话状态出错:{message}",
"settings.health.starting": "正在等待会话列表",
"settings.kind.completion": "完成",
"settings.kind.approval": "授权",
"settings.kind.question": "提问",
"settings.dismiss.label": "自动关闭",
"settings.dismiss.3000": "3 秒",
"settings.dismiss.6000": "6 秒",
"settings.dismiss.10000": "10 秒",
"settings.dismiss.30000": "30 秒",
"settings.dismiss.0": "永不",
"config.section.kinds": "提醒内容",
"config.section.popup": "轻弹窗",
"config.section.permission": "系统通知权限",
"config.section.status": "运行状态",
"config.intro": "提醒都从当前页面发出:窗口不在前台时是系统通知,窗口在前台时是这里的轻弹窗。",
"config.saveNote": "开关保存在浏览器本地,重装插件不会丢失。",
"config.dismiss.note": "鼠标停在上面时不会消失;选「永不」时,每条要自己点 ✕ 或按 Esc 关掉。",
"config.testHint": "这里的按钮直接走系统通知通道,用来验证系统通知本身是否可用(窗口在前台也照发)。",
"config.testAny": "测试提醒",
"config.testSystem": "测试系统通知",
"config.diag.window": "窗口状态",
"config.diag.watch": "会话监听",
"config.diag.inFront": "在前台(会走轻弹窗)",
"config.diag.away": "不在前台(会走系统通知)",
"config.diag.last": "最近一次投递",
"config.diag.none": "还没有投递过",
"config.diag.systemShown": "系统通知(系统已确认弹出)",
"config.diag.systemUnconfirmed": "系统通知(系统没有回报,回到窗口时补发轻弹窗)",
"config.diag.systemFailed": "系统通知没弹出来,回到窗口时补发轻弹窗",
"config.diag.replayDelivery": "应用内轻弹窗(回到窗口时补发)",
"config.diag.popup": "应用内轻弹窗",
"config.diag.refused": "系统通知被拒绝,改用了轻弹窗",
"config.diag.replay": "待补发提醒",
"config.diag.replayValue": "{count} 条",
"config.diag.replayNone": "没有"
};
// src/client/i18n/en.js
var en = {
"notification.completion": "Conversation finished",
"notification.approval": "Approval required",
"notification.question": "Answer needed",
"body.approval": "{title}: {tool} is waiting for your approval",
"body.approvalPlain": "{title}: a tool is waiting for your approval",
"body.question": "{title}: waiting for your answer",
"body.planReview": "{title}: a plan is waiting for your review",
"body.untitled": "Untitled conversation",
"toast.view": "View",
"toast.dismiss": "Dismiss",
"settings.description": "A system notification while the window is in the background, a light in-app popup while it is in the foreground; the conversation on screen is never interrupted.",
"settings.permission.granted": "System notifications are on",
"settings.permission.default": "System notification permission has not been granted",
"settings.permission.denied": "System notifications are switched off in system settings",
"settings.permission.unsupported": "This environment cannot show system notifications; the in-app popup still works",
"settings.permission.hint.plain": "Allow DeepSeek Harness in your system notification settings",
"settings.permission.hint.win": "Windows Settings → System → Notifications → DeepSeek Harness",
"settings.permission.hint.mac": "System Settings → Notifications → DeepSeek Harness",
"settings.permission.hint.linux": "Allow DeepSeek Harness in your desktop notification settings (GNOME / KDE)",
"settings.action.allow": "Allow notifications",
"settings.action.testSystem": "Test system notification",
"settings.action.testAny": "Test alert",
"settings.action.unsupported": "Unavailable",
"settings.permission.promptFailed": "Could not request notification permission: {message}",
"settings.health.watching": "Watching session status",
"settings.health.noHooks": "No session status received (notifications will not fire)",
"settings.health.error": "Watching session status failed: {message}",
"settings.health.starting": "Waiting for the session list",
"settings.kind.completion": "Finished",
"settings.kind.approval": "Approval",
"settings.kind.question": "Questions",
"settings.dismiss.label": "Auto-close",
"settings.dismiss.3000": "3s",
"settings.dismiss.6000": "6s",
"settings.dismiss.10000": "10s",
"settings.dismiss.30000": "30s",
"settings.dismiss.0": "Never",
"config.section.kinds": "What to notify about",
"config.section.popup": "Popup",
"config.section.permission": "System notification permission",
"config.section.status": "Status",
"config.intro": "Every alert comes from this page: a system notification while the window is in the background, the light popup here while it is in the foreground.",
"config.saveNote": "The switches are stored in this browser and survive reinstalling the plugin.",
"config.dismiss.note": "Hovering a card pauses the countdown; with Never, close each one with ✕ or Esc.",
"config.testHint": "The buttons here go straight to the system channel, so you can verify it on its own (they fire even while the window is in front).",
"config.testAny": "Test alert",
"config.testSystem": "Test system notification",
"config.diag.window": "Window",
"config.diag.watch": "Session watch",
"config.diag.inFront": "in front (popup is used)",
"config.diag.away": "not in front (system notification is used)",
"config.diag.last": "Last delivery",
"config.diag.none": "nothing delivered yet",
"config.diag.systemShown": "system notification (confirmed on screen)",
"config.diag.systemUnconfirmed": "system notification (no confirmation, replayed in-app on return)",
"config.diag.systemFailed": "system notification never appeared, replayed in-app on return",
"config.diag.replayDelivery": "in-app popup (replayed when you came back)",
"config.diag.popup": "in-app popup",
"config.diag.refused": "system channel refused, popup was used instead",
"config.diag.replay": "Replay",
"config.diag.replayValue": "{count} waiting",
"config.diag.replayNone": "None"
};
// src/client/i18n/index.js
var dictionaries = { zh, en };
function registerDictionaries(locale) {
if (locale === void 0 || typeof locale.register !== "function") return () => {
};
try {
return locale.register(NS, dictionaries);
} catch {
const disposers = Object.entries(dictionaries).map(([id, dict]) => locale.register(NS, id, dict));
return () => {
for (const dispose of disposers) dispose?.();
};
}
}
function createTranslator(locale) {
return (key, params) => {
if (locale !== void 0 && typeof locale.bind === "function") {
try {
const bound = locale.bind(NS);
if (typeof bound === "function") {
const translated = bound(key, params);
if (typeof translated === "string" && translated !== key) return translated;
}
} catch {
}
}
let language = "";
try {
const snapshot = locale?.getSnapshot?.();
language = String(snapshot?.active ?? "");
} catch {
language = "";
}
const dictionary = /^zh/i.test(language) ? zh : en;
const template = dictionary[key] ?? zh[key] ?? en[key] ?? key;
if (params === void 0) return template;
return template.replace(/\{(\w+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match);
};
}
// src/client/platform.js
function notificationApi() {
const Ctor = globalThis.Notification;
return typeof Ctor === "function" ? Ctor : void 0;
}
function notificationPermission() {
const Ctor = notificationApi();
if (Ctor === void 0) return "unsupported";
const permission = Ctor.permission;
if (permission === "granted" || permission === "denied" || permission === "default") return permission;
return "default";
}
function windowIsAway() {
try {
if (document.visibilityState === "hidden") return true;
} catch {
}
try {
if (typeof document.hasFocus === "function") return !document.hasFocus();
} catch {
}
return false;
}
function focusWindow() {
try {
globalThis.focus?.();
} catch {
}
}
var systemIcon = (() => {
try {
const url = new URL("./icon.svg", document.baseURI).href;
if (url !== "") return url;
} catch {
}
try {
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M8 2.35C5.7 2.35 3.83 4.22 3.83 6.52V9.02L3.06 11.1C2.99 11.29 3.13 11.48 3.33 11.48H12.67C12.87 11.48 13.01 11.29 12.94 11.1L12.17 9.02V6.52C12.17 4.22 10.3 2.35 8 2.35Z" fill="none" stroke="%23247bbf" stroke-width="1.2"/><path d="M6.3 12.75C6.52 13.66 7.2 14.28 8 14.28C8.8 14.28 9.48 13.66 9.7 12.75" fill="none" stroke="%23247bbf" stroke-width="1.2" stroke-linecap="round"/></svg>';
return `data:image/svg+xml,${svg}`;
} catch {
return void 0;
}
})();
function platformKey() {
try {
const agent = navigator.userAgentData;
const platform = String((agent?.platform ?? navigator.platform) || navigator.userAgent || "");
if (/win/i.test(platform)) return "win";
if (/mac|iphone|ipad/i.test(platform)) return "mac";
if (/linux|x11/i.test(platform)) return "linux";
} catch {
}
return "plain";
}
// src/client/core/log.js
function text(error) {
return error instanceof Error ? error.message : String(error);
}
function report(message) {
try {
console.error(`session-notify: ${message}`);
} catch {
}
}
// src/client/core/store.js
function createStore(initial) {
let value = initial;
const listeners = /* @__PURE__ */ new Set();
return {
getSnapshot: () => value,
subscribe: (listener) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
},
set: (next) => {
if (next === value) return;
value = next;
for (const listener of [...listeners]) listener();
}
};
}
// src/client/core/storage.js
var STORAGE_KEY = "dsh-plugin/session-notify";
function defaults() {
return { completion: true, approval: true, question: true, dismissAfterMs: TOAST_DURATION_MS };
}
function readStored() {
const preferences = defaults();
try {
const raw = globalThis.localStorage?.getItem(STORAGE_KEY);
if (typeof raw !== "string" || raw === "") return preferences;
const stored = JSON.parse(raw);
for (const kind of KINDS) {
if (typeof stored?.[kind] === "boolean") preferences[kind] = stored[kind];
}
if (DISMISS_CHOICES.includes(stored?.dismissAfterMs)) preferences.dismissAfterMs = stored.dismissAfterMs;
} catch {
}
return preferences;
}
function writeStored(patch) {
try {
globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify({ ...readStored(), ...patch }));
} catch {
}
}
function readStoredKinds() {
const { completion, approval, question } = readStored();
return { completion, approval, question };
}
function writeStoredKinds(kinds) {
writeStored(kinds);
}
function readStoredDismissMs() {
return readStored().dismissAfterMs;
}
function writeStoredDismissMs(ms) {
writeStored({ dismissAfterMs: ms });
}
// src/client/core/copy.js
function createCopy(t) {
const copyFor = (candidate) => {
const title = candidate.title === "" ? t("body.untitled") : candidate.title;
if (candidate.kind === "completion") {
return { kind: "completion", title: t("notification.completion"), body: title };
}
if (candidate.pendingKind === "plan-review") {
return { kind: "question", title: t("notification.question"), body: t("body.planReview", { title }) };
}
if (candidate.kind === "question") {
const question = candidate.detail;
return {
kind: "question",
title: t("notification.question"),
body: question === "" ? t("body.question", { title }) : `${title} · ${question}`
};
}
const tool = candidate.detail;
return {
kind: "approval",
title: t("notification.approval"),
body: tool === "" ? t("body.approvalPlain", { title }) : t("body.approval", { title, tool })
};
};
const deliveryFacts = (last, tr) => {
if (last === null || last === void 0) return null;
const outcome = last.outcome.startsWith("system-refused") ? tr("config.diag.refused") : last.outcome === "system" ? last.shown === true ? tr("config.diag.systemShown") : last.shown === false ? tr("config.diag.systemFailed") : tr("config.diag.systemUnconfirmed") : last.outcome === "replay" ? tr("config.diag.replayDelivery") : tr("config.diag.popup");
return { at: last.at, outcome, title: last.title ?? "" };
};
const replayValue = (count, tr) => count === 0 ? tr("config.diag.replayNone") : tr("config.diag.replayValue", { count });
return { copyFor, deliveryFacts, replayValue };
}
// src/client/core/toasts.js
function createToastStack({ store, copy }) {
const timers = /* @__PURE__ */ new Map();
const held = /* @__PURE__ */ new Set();
let seq = 0;
const lifetime = () => {
const configured = store.getSnapshot().dismissAfterMs;
return typeof configured === "number" && configured >= 0 ? configured : TOAST_DURATION_MS;
};
const closeToast = (id) => {
held.delete(id);
const timer = timers.get(id);
if (timer !== void 0) {
clearTimeout(timer);
timers.delete(id);
}
const snapshot = store.getSnapshot();
if (!snapshot.toasts.some((toast) => toast.id === id)) return;
store.set({ ...snapshot, toasts: snapshot.toasts.filter((toast) => toast.id !== id) });
};
const armToast = (id) => {
held.delete(id);
const timer = timers.get(id);
if (timer !== void 0) clearTimeout(timer);
timers.delete(id);
const duration = lifetime();
if (duration === 0) return;
timers.set(id, setTimeout(() => closeToast(id), duration));
};
const holdToast = (id) => {
held.add(id);
const timer = timers.get(id);
if (timer === void 0) return;
clearTimeout(timer);
timers.delete(id);
};
const rearmAll = () => {
for (const toast of store.getSnapshot().toasts) {
if (!held.has(toast.id)) armToast(toast.id);
}
};
const showToast = (candidate) => {
const notice = copy.copyFor(candidate);
seq += 1;
const toast = {
id: `dsn-${String(seq)}`,
kind: notice.kind,
title: notice.title,
body: notice.body,
sessionId: candidate.sessionId
};
const snapshot = store.getSnapshot();
const next = [toast, ...snapshot.toasts];
for (const dropped of next.slice(TOAST_LIMIT)) {
const timer = timers.get(dropped.id);
if (timer !== void 0) {
clearTimeout(timer);
timers.delete(dropped.id);
}
held.delete(dropped.id);
}
store.set({ ...snapshot, toasts: next.slice(0, TOAST_LIMIT) });
armToast(toast.id);
};
const dispose = () => {
for (const timer of timers.values()) clearTimeout(timer);
timers.clear();
held.clear();
};
return { showToast, closeToast, armToast, holdToast, rearmAll, dispose };
}
// src/client/core/replay.js
function createReplay({ store, publish, showToast, recordDelivery }) {
let entries = [];
const dropReplay = (candidate) => {
const next = entries.filter((entry) => entry.candidate.sessionId !== candidate.sessionId || entry.candidate.kind !== candidate.kind);
if (next.length === entries.length) return;
entries = next;
publish();
};
const holdForReplay = (candidate, monitor) => {
const now = Date.now();
entries = [
...entries.filter((entry) => entry.candidate.sessionId !== candidate.sessionId || entry.candidate.kind !== candidate.kind),
{ candidate, at: now, monitor }
].slice(-REPLAY_LIMIT);
publish();
};
const flushReplay = () => {
if (entries.length === 0) return;
const now = Date.now();
const waiting = entries;
entries = [];
const due = waiting.filter((entry) => {
if (now - entry.at > REPLAY_TTL_MS) return false;
return !(entry.monitor?.shown === true && now - entry.at <= REPLAY_QUIET_MS);
});
const shown = due.slice(-TOAST_LIMIT);
for (const entry of shown) showToast(entry.candidate);
if (shown.length > 0) recordDelivery("replay", shown[shown.length - 1].candidate);
else publish();
};
const size = () => entries.length;
const dispose = () => {
entries = [];
};
return { holdForReplay, dropReplay, flushReplay, size, dispose };
}
// src/client/core/system-channel.js
function createSystemChannel({ store, copy, notify, openSession, dropReplay }) {
const raised = /* @__PURE__ */ new Set();
const confirmDelivery = (monitor, shown) => {
if (monitor === null || monitor === void 0 || typeof monitor !== "object") return;
monitor.shown = shown;
const snapshot = store.getSnapshot();
const last = snapshot.lastDelivery;
if (last === null || last === void 0 || last.monitor !== monitor || last.shown === shown) return;
store.set({ ...snapshot, lastDelivery: { ...last, shown } });
};
const keepRaised = (notification) => {
raised.add(notification);
while (raised.size > RAISED_LIMIT) {
const oldest = raised.values().next().value;
if (oldest === void 0) break;
raised.delete(oldest);
}
};
const notifySystem = (candidate, force) => {
const Ctor = globalThis.Notification;
const permission = notificationPermission();
if (permission === "unsupported") return { outcome: "unsupported" };
if (!force && permission !== "granted") return { outcome: "permission" };
try {
const notice = copy.copyFor(candidate);
const options = {
body: notice.body,
tag: `dsh-session-${candidate.sessionId}`,
requireInteraction: false
};
if (systemIcon !== void 0) options.icon = systemIcon;
const notification = new Ctor(notice.title, options);
const record = { outcome: "raised", notice, shown: void 0 };
keepRaised(notification);
notification.onshow = () => {
confirmDelivery(record, true);
};
notification.onerror = (event) => {
confirmDelivery(record, false);
notify(`system notification was refused: ${String(event?.message ?? "unknown")}`);
};
notification.onclose = () => {
raised.delete(notification);
};
notification.onclick = () => {
dropReplay(candidate);
focusWindow();
openSession(candidate.sessionId);
try {
notification.close();
} catch {
}
};
return record;
} catch (error) {
notify(`system notification failed: ${text(error)}`);
return { outcome: "threw" };
}
};
const dispose = () => {
raised.clear();
};
return { notifySystem, confirmDelivery, dispose };
}
// src/client/core/session.js
function servedPendingKind(value) {
if (value === void 0 || value === null || typeof value !== "object") return void 0;
const kind = value.kind;
return typeof kind === "string" && PENDING_KINDS.includes(kind) ? kind : void 0;
}
function preview(value) {
if (typeof value !== "string") return "";
const flat = value.replace(/\s+/g, " ").trim();
return flat.length > 80 ? `${flat.slice(0, 79)}…` : flat;
}
function questionText(interaction) {
if (interaction === null || typeof interaction !== "object") return "";
const first = Array.isArray(interaction.questions) ? interaction.questions[0] : void 0;
if (typeof first?.question === "string") return preview(first.question);
if (typeof first?.text === "string") return preview(first.text);
if (typeof interaction.question === "string") return preview(interaction.question);
if (typeof interaction.prompt === "string") return preview(interaction.prompt);
return preview(interaction.displayReason?.text ?? interaction.reason?.text ?? "");
}
function titleOf(summary, sessionId) {
const title = typeof summary?.title === "string" ? summary.title.trim() : "";
return title === "" ? sessionId.slice(0, 8) : title;
}
function isOnScreen(sessionId, list) {
if (list === void 0) return false;
for (const id of Object.keys(list.byId ?? {})) {
const summary = list.byId[id];
if (summary !== void 0 && (summary.retainedBy?.mainView ?? 0) > 0) return id === sessionId;
}
return false;
}
function stillOwed(candidate, live) {
if (candidate.test === true) return true;
if (candidate.pendingKind !== "") {
const sessionStatus = live.status?.get?.(candidate.sessionId);
if (sessionStatus === void 0) return true;
return servedPendingKind(sessionStatus.pendingInteraction) === candidate.pendingKind;
}
const list = live.list;
if (list === void 0) return true;
const summary = list.byId?.[candidate.sessionId];
if (summary === void 0) return true;
const running = live.status?.get?.(candidate.sessionId)?.running ?? summary.running;
return running !== true;
}
// src/client/core/delivery.js
function createDelivery({ store, copy, live, toasts, system, replay }) {
const pending = [];
let settleTimer = 0;
let lastDelivery = 0;
let deliverySeq = 0;
const recordDelivery = (outcome, candidate, monitor) => {
deliverySeq += 1;
const now = /* @__PURE__ */ new Date();
const pad = (value) => String(value).padStart(2, "0");
const record = {
seq: deliverySeq,
outcome,
at: `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`,
title: copy.copyFor(candidate).title,
shown: monitor?.shown,
monitor
};
store.set({ ...store.getSnapshot(), lastDelivery: record });
return record;
};
const deliver = (candidate, ignoreThrottle) => {
if (ignoreThrottle !== true) {
const now = Date.now();
if (now - lastDelivery < THROTTLE_MS) return false;
lastDelivery = now;
}
if (!windowIsAway()) {
if (isOnScreen(candidate.sessionId, live.list)) return true;
toasts.showToast(candidate);
recordDelivery("popup", candidate);
return true;
}
const result = system.notifySystem(candidate, candidate.test === true);
if (result.outcome === "raised") {
recordDelivery("system", candidate, result);
if (candidate.test !== true) replay.holdForReplay(candidate, result);
return true;
}
toasts.showToast(candidate);
recordDelivery(`system-refused-${result.outcome}`, candidate);
return true;
};
const flushSettle = () => {
settleTimer = 0;
const candidate = pending.shift();
if (candidate !== void 0 && stillOwed(candidate, live)) {
const delivered = deliver(candidate, false);
if (delivered === false && candidate.pendingKind !== "") pending.push(candidate);
}
if (pending.length > 0) settleTimer = setTimeout(() => flushSettle(), SETTLE_MS);
};
const queue = (candidate) => {
pending.push(candidate);
if (settleTimer !== 0) return;
settleTimer = setTimeout(() => flushSettle(), SETTLE_MS);
};
const resetThrottle = () => {
lastDelivery = 0;
};
const dispose = () => {
if (settleTimer !== 0) {
clearTimeout(settleTimer);
settleTimer = 0;
}
};
return { deliver, recordDelivery, queue, flushSettle, resetThrottle, dispose };
}
// src/client/core/observe.js
function createObservation({ store, live, queue }) {
const runs = /* @__PURE__ */ new Map();
const completionNotice = /* @__PURE__ */ new Set();
const pendingNotice = /* @__PURE__ */ new Map();
const reportHealth = (next) => {
const snapshot = store.getSnapshot();
const current = snapshot.health;
if (current.state === next.state && current.message === next.message) return;
if (current.state !== "watching") report(`observer health: ${next.state} ${next.message}`);
store.set({ ...snapshot, health: next });
};
const observe = (list, status) => {
if (list === void 0 || status === void 0) return;
live.list = list;
live.status = status;
const kinds = store.getSnapshot().kinds;
for (const sessionId of Object.keys(list.byId ?? {})) {
const summary = list.byId[sessionId];
if (summary === void 0 || summary.origin === "subagent") continue;
const sessionStatus = status.get(sessionId);
const running = sessionStatus?.running ?? summary.running;
if (running === true) {
runs.set(sessionId, true);
completionNotice.delete(sessionId);
} else {
if (runs.get(sessionId) === true) {
runs.set(sessionId, false);
if (summary.blank !== true && !completionNotice.has(sessionId) && kinds.completion) {
completionNotice.add(sessionId);
queue({
kind: "completion",
sessionId,
title: titleOf(summary, sessionId),
detail: "",
pendingKind: ""
});
}
} else if (!runs.has(sessionId)) {
runs.set(sessionId, false);
}
}
const interaction = sessionStatus?.pendingInteraction;
const pendingKind = servedPendingKind(interaction);
if (pendingKind === void 0) {
pendingNotice.delete(sessionId);
continue;
}
if (pendingNotice.get(sessionId) === pendingKind) continue;
pendingNotice.set(sessionId, pendingKind);
const trigger = pendingKind === "approval" ? "approval" : "question";
if (!kinds[trigger]) continue;
queue({
kind: trigger,
sessionId,
title: titleOf(summary, sessionId),
detail: pendingKind === "approval" ? String(interaction?.toolName ?? "") : questionText(interaction),
pendingKind
});
}
for (const sessionId of [...runs.keys()]) {
if (list.byId?.[sessionId] !== void 0) continue;
runs.delete(sessionId);
completionNotice.delete(sessionId);
pendingNotice.delete(sessionId);
}
};
return { observe, reportHealth };
}
// src/client/core/actions.js
function createActions({ store, t, delivery, system, toasts, publish }) {
const testCandidate = (title) => ({
kind: "completion",
sessionId: "test",
title,
detail: "",
pendingKind: "",
test: true
});
const sendTest = () => {
delivery.deliver(testCandidate(t("config.testAny")), true);
};
const sendTestSystem = () => {
const candidate = testCandidate(t("config.testSystem"));
const result = system.notifySystem(candidate, true);
if (result.outcome === "raised") {
delivery.recordDelivery("system", candidate, result);
return;
}
toasts.showToast(candidate);
delivery.recordDelivery(`system-refused-${result.outcome}`, candidate);
};
const requestPermission = async () => {
const Ctor = notificationApi();
if (Ctor === void 0) {
publish();
return;
}
try {
let result = Ctor.requestPermission();
if (result === void 0) {
result = new Promise((resolve) => {
Ctor.requestPermission((value) => resolve(value));
});
}
await result;
} catch (error) {
const snapshot2 = store.getSnapshot();
store.set({ ...snapshot2, promptError: t("settings.permission.promptFailed", { message: text(error) }) });
return;
}
const snapshot = store.getSnapshot();
store.set({ ...snapshot, permission: notificationPermission(), promptError: "" });
if (notificationPermission() === "granted") sendTest();
};
const setKindEnabled = (kind, enabled) => {
const snapshot = store.getSnapshot();
const kinds = { ...snapshot.kinds, [kind]: enabled };
writeStoredKinds(kinds);
store.set({ ...snapshot, kinds });
};
const setDismissAfter = (ms) => {
writeStoredDismissMs(ms);
const snapshot = store.getSnapshot();
store.set({ ...snapshot, dismissAfterMs: ms });
toasts.rearmAll();
};
return { sendTest, sendTestSystem, requestPermission, setKindEnabled, setDismissAfter };
}
// src/client/ui/ToastLayer.js
var React3 = __toESM(require("react"), 1);
// src/client/ui/icons.js
var React = __toESM(require("react"), 1);
var h = React.createElement;
function BellIcon({ size = 16 }) {
return h(
"svg",
{
width: size,
height: size,
viewBox: "0 0 16 16",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": true
},
h("path", {
d: "M8 2.35C5.7 2.35 3.83 4.22 3.83 6.52V9.02L3.06 11.1C2.99 11.29 3.13 11.48 3.33 11.48H12.67C12.87 11.48 13.01 11.29 12.94 11.1L12.17 9.02V6.52C12.17 4.22 10.3 2.35 8 2.35Z",
stroke: "currentColor",
strokeWidth: 1.2,
strokeLinejoin: "round"
}),
h("path", {
d: "M6.3 12.75C6.52 13.66 7.2 14.28 8 14.28C8.8 14.28 9.48 13.66 9.7 12.75",
stroke: "currentColor",
strokeWidth: 1.2,
strokeLinecap: "round"
})
);
}
function ShieldIcon({ size = 16 }) {
return h(
"svg",
{
width: size,
height: size,
viewBox: "0 0 16 16",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": true
},
h("path", {
d: "M8 1.9 13.1 3.7V7.9C13.1 11 11 13.3 8 14.2C5 13.3 2.9 11 2.9 7.9V3.7L8 1.9Z",
stroke: "currentColor",
strokeWidth: 1.2,
strokeLinejoin: "round"
}),
h("path", {
d: "M5.9 8.05L7.35 9.5L10.15 6.6",
stroke: "currentColor",
strokeWidth: 1.2,
strokeLinecap: "round",
strokeLinejoin: "round"
})
);
}
function QuestionIcon({ size = 16 }) {
return h(
"svg",
{
width: size,
height: size,
viewBox: "0 0 16 16",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": true
},
h("circle", { cx: 8, cy: 8, r: 6.15, stroke: "currentColor", strokeWidth: 1.2 }),
h("path", {
d: "M6.35 6.35C6.35 5.44 7.09 4.7 8 4.7C8.91 4.7 9.65 5.44 9.65 6.35C9.65 7.75 8 7.7 8 9.15",
stroke: "currentColor",
strokeWidth: 1.2,
strokeLinecap: "round"
}),
h("circle", { cx: 8, cy: 11.3, r: 0.85, fill: "currentColor" })
);
}
function CloseIcon({ size = 12 }) {
return h(
"svg",
{
width: size,
height: size,
viewBox: "0 0 16 16",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
"aria-hidden": true
},
h("path", { d: "M2.5 2.5L13.5 13.5", stroke: "currentColor", strokeWidth: 1.4 }),
h("path", { d: "M13.5 2.5L2.5 13.5", stroke: "currentColor", strokeWidth: 1.4 })
);
}
function KindIcon({ kind, size }) {
if (kind === "approval") return h(ShieldIcon, { size });
if (kind === "question") return h(QuestionIcon, { size });
return h(BellIcon, { size });
}
// src/client/ui/hooks.js
var React2 = __toESM(require("react"), 1);
function useOwnStore(source) {
return React2.useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
}
function useWindowState() {
const subscribe = React2.useCallback((listener) => {
try {
window.addEventListener("focus", listener);
window.addEventListener("blur", listener);
document.addEventListener("visibilitychange", listener);
} catch {
return () => {
};
}
return () => {
window.removeEventListener("focus", listener);
window.removeEventListener("blur", listener);
document.removeEventListener("visibilitychange", listener);
};
}, []);
return React2.useSyncExternalStore(subscribe, () => !windowIsAway(), () => true);
}
function seatTranslator(props, fallback) {
return typeof props.t === "function" ? props.t : fallback;
}
// src/client/ui/styles.js
var CSS = `
.dsn-stack{position:fixed;top:40px;right:24px;z-index:1100;display:flex;flex-direction:column;align-items:flex-end;gap:8px;pointer-events:none}
@media (max-width:520px){.dsn-stack{right:12px}}
.dsn-toast{box-sizing:border-box;pointer-events:auto;display:flex;align-items:flex-start;gap:10px;width:max-content;max-width:min(420px,calc(100vw - 48px));padding:12px 12px 12px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-2);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px;animation:dsn-toast-in var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-out)}
@keyframes dsn-toast-in{from{opacity:0;transform:translateX(8px)}to{opacity:1;transform:translateX(0)}}
@media (prefers-reduced-motion: reduce){.dsn-toast{animation:none}}
.dsn-toast-icon{display:grid;place-items:center;flex:none;height:22px;color:var(--dsw-alias-state-warn-label)}
.dsn-toast-icon.is-completion{color:var(--dsw-alias-state-success-primary)}
.dsn-toast-icon.is-question{color:var(--dsw-alias-state-business-primary)}
.dsn-toast-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.dsn-toast-title{color:var(--dsw-alias-label-primary);font-weight:500;font-size:14px;line-height:22px}
.dsn-toast-desc{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;overflow-wrap:break-word}
.dsn-toast-actions{display:flex;align-items:center;gap:2px;flex:none}
.dsn-toast-action{display:inline-flex;align-items:center;height:28px;padding:0 8px;border:0;border-radius:var(--dsw-radius-sm);background:none;color:var(--dsw-alias-state-business-primary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
.dsn-toast-action:hover{background:var(--dsw-alias-interactive-bg-hover)}
.dsn-toast-action:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer}
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
.dsn-page{display:flex;flex-direction:column;gap:28px;max-width:560px}
.dsn-lede{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
.dsn-group{display:flex;flex-direction:column;gap:2px}
.dsn-group-title{margin:0 0 8px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px}
.dsn-group-note{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
.dsn-list{display:flex;flex-direction:column}
.dsn-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0}
.dsn-row+.dsn-row{border-top:.5px solid var(--dsw-alias-border-l2)}
.dsn-row.is-action{justify-content:flex-end;padding:12px 0 0}
.dsn-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
.dsn-row-title{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
.dsn-chips{display:flex;align-items:center;gap:6px;flex:none}
.dsn-chip{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;height:28px;min-width:46px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12.5px;line-height:18px;cursor:pointer}
.dsn-chip:hover:not([aria-checked='true']){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
.dsn-chip[aria-checked='true']{border-color:transparent;background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary-foreground);cursor:default}
.dsn-chip:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
.dsn-row-sub{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
.dsn-state{display:inline-flex;align-items:center;gap:8px;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
.dsn-dot{flex:none;width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-success-primary)}
.dsn-dot.is-warn{background:var(--dsw-alias-state-warn-label)}
.dsn-dot.is-error{background:var(--dsw-alias-state-error-primary)}
.dsn-dot.is-muted{background:var(--dsw-alias-border-l3)}
.dsn-facts{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px 14px;margin:0;min-width:0}
.dsn-fact-key{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}
.dsn-fact{display:flex;flex-direction:column;gap:2px;margin:0;min-width:0}
.dsn-fact-value{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
.dsn-fact-value.is-error{color:var(--dsw-alias-state-error-primary)}
.dsn-fact-title{margin:0;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
.dsn-switch{box-sizing:border-box;position:relative;flex:0 0 auto;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}
.dsn-switch[aria-checked='true']{background:var(--dsw-alias-brand-primary)}
.dsn-switch:disabled{cursor:default;opacity:.5}
.dsn-switch:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
.dsn-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform 120ms ease}
.dsn-switch[aria-checked='false'] .dsn-thumb{background:var(--dsw-alias-switch-thumb)}
.dsn-switch[aria-checked='true'] .dsn-thumb{transform:translateX(16px)}
.dsn-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);cursor:pointer;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);background:transparent;flex:none}
.dsn-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
.dsn-button:disabled{cursor:not-allowed;opacity:.4}
.dsn-button:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
.dsn-button.is-primary{border-color:transparent;background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary-foreground)}
.dsn-button.is-primary:hover:not(:disabled){background:var(--dsw-alias-brand-primary);opacity:.88}
`;
// src/client/ui/ToastLayer.js
var h2 = React3.createElement;
function ToastLayer(props) {
const snapshot = useOwnStore(props.store);
const toasts = snapshot.toasts;
const tr = seatTranslator(props, props.fallbackT);
React3.useEffect(() => {
if (toasts.length === 0) return void 0;
const onKeyDown = (event) => {
if (event.key !== "Escape" || event.defaultPrevented) return;
const top = toasts[0];
if (top === void 0) return;
event.preventDefault();
props.dismiss(top.id);
};
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
};
}, [toasts, props]);
if (toasts.length === 0) return null;
return h2(
React3.Fragment,
null,
h2("style", null, CSS),
h2(
"div",
{ className: "dsn-stack", role: "region", "aria-live": "polite" },
toasts.map((toast) => h2(
"div",
{
key: toast.id,
className: "dsn-toast",
role: toast.kind === "completion" ? "status" : "alert",
onMouseEnter: () => props.hold(toast.id),
onMouseLeave: () => props.arm(toast.id)
},
h2("span", { className: `dsn-toast-icon is-${toast.kind}` }, h2(KindIcon, { kind: toast.kind, size: 16 })),
h2(
"div",
{ className: "dsn-toast-text" },
h2("div", { className: "dsn-toast-title" }, toast.title),
h2("div", { className: "dsn-toast-desc" }, toast.body)
),
h2(
"div",
{ className: "dsn-toast-actions" },
h2("button", {
type: "button",
className: "dsn-toast-action",
onClick: () => {
props.dismiss(toast.id);
props.open(toast.sessionId);
}
}, tr("toast.view")),
h2("button", {
type: "button",
className: "dsn-toast-close",
"aria-label": tr("toast.dismiss"),
onClick: () => props.dismiss(toast.id)
}, h2(CloseIcon, { size: 14 }))
)
))
)
);
}
// src/client/ui/ConfigSection.js
var React6 = __toESM(require("react"), 1);
// src/client/ui/DismissRow.js
var React4 = __toESM(require("react"), 1);
var h3 = React4.createElement;
function DismissRow(props) {
return h3(
"div",
{ className: "dsn-row" },
h3(
"div",
{ className: "dsn-row-text" },
h3("div", { className: "dsn-row-title" }, props.label)
),
h3(
"div",
{ className: "dsn-chips", role: "radiogroup", "aria-label": props.label },
props.options.map((option) => h3("button", {
key: String(option.value),
type: "button",
role: "radio",
className: "dsn-chip",
"aria-checked": option.value === props.value,
onClick: () => props.onChange(option.value)
}, option.label))
)
);
}
// src/client/ui/KindRow.js
var React5 = __toESM(require("react"), 1);
var h4 = React5.createElement;
function KindRow(props) {
return h4(
"div",
{ className: "dsn-row" },
h4(
"div",
{ className: "dsn-row-text" },
h4("div", { className: "dsn-row-title" }, props.label)
),
h4("button", {
type: "button",
role: "switch",
className: "dsn-switch",
"aria-checked": props.checked,
"aria-label": props.label,
disabled: props.disabled,
onClick: props.onToggle
}, h4("span", { className: "dsn-thumb" }))
);
}
// src/client/ui/ConfigSection.js
var h5 = React6.createElement;
function ConfigSection(props) {
const snapshot = useOwnStore(props.store);
const tr = seatTranslator(props, props.fallbackT);
if (props.view === "summary") return h5(React6.Fragment, null, tr("settings.description"));
const permission = snapshot.permission;
const supported = permission !== "unsupported";
const failed = permission === "denied" || snapshot.promptError !== "";
const statusKey = permission === "granted" ? "settings.permission.granted" : permission === "denied" ? "settings.permission.denied" : supported ? "settings.permission.default" : "settings.permission.unsupported";
const promptFailed = snapshot.promptError !== "";
const permissionText = promptFailed ? snapshot.promptError : tr(statusKey);
const dotClass = permission === "granted" ? "" : failed ? " is-error" : supported ? " is-warn" : " is-muted";
const permissionNote = promptFailed ? "" : permission === "denied" || !supported ? tr(`settings.permission.hint.${props.platform}`) : permission === "granted" ? tr("config.testHint") : tr("settings.permission.hint.plain");
const health = snapshot.health;
const healthText = health.state === "watching" ? tr("settings.health.watching") : health.state === "noHooks" ? tr("settings.health.noHooks") : health.state === "error" ? tr("settings.health.error", { message: health.message }) : tr("settings.health.starting");
const healthFailed = health.state !== "watching" && health.state !== "starting";
const delivery = props.copy.deliveryFacts(snapshot.lastDelivery, tr);
return h5(
React6.Fragment,
null,
h5("style", null, CSS),
h5(
"div",
{ className: "dsn-page" },
// The page header already shows this bundle's title and description, so this
// lede says what the header cannot: how an alert travels, and that the
// channel is chosen at delivery time.
h5("p", { className: "dsn-lede" }, tr("config.intro")),
h5(
"section",
{ className: "dsn-group" },
h5("h3", { className: "dsn-group-title" }, tr("config.section.kinds")),
h5(
"div",
{ className: "dsn-list" },
KINDS.map((kind) => h5(KindRow, {
key: kind,
label: tr(`settings.kind.${kind}`),
checked: snapshot.kinds[kind] === true,
disabled: false,
onToggle: () => props.setKind(kind, snapshot.kinds[kind] !== true)
}))
),
h5("p", { className: "dsn-group-note" }, tr("config.saveNote"))
),
h5(
"section",
{ className: "dsn-group" },
h5("h3", { className: "dsn-group-title" }, tr("config.section.popup")),
h5(
"div",
{ className: "dsn-list" },
h5(DismissRow, {
label: tr("settings.dismiss.label"),
options: DISMISS_CHOICES.map((value) => ({ value, label: tr(`settings.dismiss.${value}`) })),
value: snapshot.dismissAfterMs,
onChange: props.setDismiss
})
),
h5("p", { className: "dsn-group-note" }, tr("config.dismiss.note"))
),
h5(
"section",
{ className: "dsn-group" },
h5("h3", { className: "dsn-group-title" }, tr("config.section.permission")),
h5(
"div",
{ className: "dsn-list" },
h5(
"div",
{ className: "dsn-row" },
h5(
"div",
{ className: "dsn-row-text" },
h5(
"span",
{
className: "dsn-state",
role: failed ? "alert" : "status"
},
h5("span", { className: `dsn-dot${dotClass}` }),
permissionText
),
h5("p", { className: "dsn-row-sub" }, permissionNote)
),
h5("button", {
type: "button",
className: `dsn-button${supported && permission !== "granted" ? " is-primary" : ""}`,
disabled: !supported,
onClick: permission === "granted" ? props.testSystem : props.ask
}, tr(!supported ? "settings.action.unsupported" : permission === "granted" ? "settings.action.testSystem" : "settings.action.allow"))
)
)
),
h5(
"section",
{ className: "dsn-group" },
h5("h3", { className: "dsn-group-title" }, tr("config.section.status")),
h5(
"dl",
{ className: "dsn-facts" },
h5("dt", { className: "dsn-fact-key" }, tr("config.diag.window")),
h5(
"dd",
{ className: "dsn-fact" },
h5("p", { className: "dsn-fact-value" }, tr(windowIsAway() ? "config.diag.away" : "config.diag.inFront"))
),
h5("dt", { className: "dsn-fact-key" }, tr("config.diag.watch")),
h5(
"dd",
{ className: "dsn-fact" },
h5("p", {
className: `dsn-fact-value${healthFailed ? " is-error" : ""}`,
role: healthFailed ? "alert" : "status"
}, healthText)
),
h5("dt", { className: "dsn-fact-key" }, tr("config.diag.last")),
h5(
"dd",
{ className: "dsn-fact" },
delivery === null ? h5("p", { className: "dsn-fact-value" }, tr("config.diag.none")) : [
h5("p", { key: "line", className: "dsn-fact-value" }, `${delivery.at} · ${delivery.outcome}`),
delivery.title === "" ? null : h5("p", { key: "title", className: "dsn-fact-title" }, delivery.title)
]
),
h5("dt", { className: "dsn-fact-key" }, tr("config.diag.replay")),
h5(
"dd",
{ className: "dsn-fact" },
h5("p", { className: "dsn-fact-value" }, props.copy.replayValue(props.replayCount(), tr))
)
),
h5(
"div",
{ className: "dsn-list" },
h5(
"div",
{ className: "dsn-row is-action" },
h5("button", {
type: "button",
className: "dsn-button",
onClick: props.testAny
}, tr("settings.action.testAny"))
)
)
)
)
);
}
// src/client/ui/NotifyObserver.js
var React7 = __toESM(require("react"), 1);
function NotifyObserver(props) {
useWindowState();
const useSessions = props.useSessions;
const useSessionStatus = props.useSessionStatus;
const select = React7.useCallback((snapshot) => snapshot, []);
const list = typeof useSessions === "function" ? useSessions(select) : void 0;
const status = typeof useSessionStatus === "function" ? useSessionStatus(select) : void 0;
React7.useEffect(() => {
try {
if (typeof props.useSessions !== "function" || typeof props.useSessionStatus !== "function") {
props.observation.reportHealth({ state: "noHooks", message: "" });
return;
}
if (list === void 0 || status === void 0) {
props.observation.reportHealth({ state: "starting", message: "" });
return;
}
props.observation.observe(list, status);
props.observation.reportHealth({ state: "watching", message: "" });
} catch (error) {
props.observation.reportHealth({ state: "error", message: text(error) });
}
});
return null;
}
// src/client/plugin.js
var inject = ["slots", "locale"];
function apply(ctx) {
const locale = ctx.get("locale");
const t = createTranslator(locale);
const store = createStore({
permission: notificationPermission(),
kinds: readStoredKinds(),
dismissAfterMs: readStoredDismissMs(),
toasts: [],
promptError: "",
health: { state: "starting", message: "" },
lastDelivery: null
});
const live = { list: void 0, status: void 0 };
const runtime = {};
let disposed = false;
const publish = () => {
if (!disposed) store.set({ ...store.getSnapshot() });
};
const openSession = (sessionId) => {
try {
ctx.get("uiWorkspace")?.openSession?.(sessionId);
} catch (error) {
report(`could not open "${sessionId}": ${text(error)}`);
}
};
const copy = createCopy(t);
const toasts = createToastStack({ store, copy });
const replay = createReplay({
store,
publish,
showToast: toasts.showToast,
recordDelivery: (outcome, candidate, monitor) => runtime.delivery?.recordDelivery(outcome, candidate, monitor)
});
const system = createSystemChannel({
store,
copy,
notify: report,
openSession,
dropReplay: replay.dropReplay
});
const delivery = createDelivery({ store, copy, live, toasts, system, replay });
runtime.delivery = delivery;
const observation = createObservation({ store, live, queue: delivery.queue });
const actions = createActions({ store, t, delivery, system, toasts, publish });
const installBridges = () => {
const onBlur = () => {
delivery.resetThrottle();
};
const onReturn = () => {
delivery.resetThrottle();
replay.flushReplay();
};
const onVisibilityChange = () => {
if (!windowIsAway()) onReturn();
};
try {
window.addEventListener("focus", onReturn);
window.addEventListener("blur", onBlur);
document.addEventListener("visibilitychange", onVisibilityChange);
} catch {
}
return () => {
try {
window.removeEventListener("focus", onReturn);
window.removeEventListener("blur", onBlur);
document.removeEventListener("visibilitychange", onVisibilityChange);
} catch {
}
delivery.dispose();
toasts.dispose();
system.dispose();
replay.dispose();
};
};
ctx.effect(() => registerDictionaries(locale), "session-notify: dictionaries");
ctx.effect(() => installBridges(), "session-notify: window focus");
ctx.effect(() => () => {
disposed = true;
}, "session-notify: disposal");
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
name: "shell.overlay",
id: `${NS}-toasts`,
order: 100,
locale: NS,
inject: () => ({
store,
fallbackT: t,
dismiss: toasts.closeToast,
arm: toasts.armToast,
hold: toasts.holdToast,
open: (sessionId) => {
focusWindow();
openSession(sessionId);
}
})
}, ToastLayer));
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
name: "plugins.bundle.config",
key: PACKAGE_NAME,
locale: NS,
inject: () => ({
store,
fallbackT: t,
copy,
platform: platformKey(),
replayCount: replay.size,
ask: actions.requestPermission,
testAny: actions.sendTest,
testSystem: actions.sendTestSystem,
setKind: actions.setKindEnabled,
setDismiss: actions.setDismissAfter
})
}, ConfigSection));
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
name: "shell.overlay",
id: `${NS}-watch`,
order: 101,
inject: () => ({ observation })
}, NotifyObserver));
}
return module.exports
},
})