dsh-messager 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +5 -4
- package/README.md +5 -4
- package/lib/channels/dingtalk.d.ts +3 -5
- package/lib/channels/dingtalk.d.ts.map +1 -1
- package/lib/channels/dingtalk.js +3 -6
- package/lib/channels/dingtalk.js.map +1 -1
- package/lib/channels/discord.d.ts +2 -3
- package/lib/channels/discord.d.ts.map +1 -1
- package/lib/channels/discord.js +2 -3
- package/lib/channels/discord.js.map +1 -1
- package/lib/channels/feishu.d.ts +3 -14
- package/lib/channels/feishu.d.ts.map +1 -1
- package/lib/channels/feishu.js +6 -15
- package/lib/channels/feishu.js.map +1 -1
- package/lib/channels/telegram.d.ts.map +1 -1
- package/lib/channels/telegram.js +0 -1
- package/lib/channels/telegram.js.map +1 -1
- package/lib/channels/wecom.d.ts +1 -1
- package/lib/channels/wecom.d.ts.map +1 -1
- package/lib/channels/wecom.js +1 -2
- package/lib/channels/wecom.js.map +1 -1
- package/lib/client.js +188 -35
- package/lib/client.js.map +1 -1
- package/lib/config-route.d.ts +18 -7
- package/lib/config-route.d.ts.map +1 -1
- package/lib/config-route.js +59 -16
- package/lib/config-route.js.map +1 -1
- package/lib/config-shared.d.ts +1 -1
- package/lib/config-shared.d.ts.map +1 -1
- package/lib/config-shared.js +1 -1
- package/lib/config-shared.js.map +1 -1
- package/lib/config.d.ts +0 -6
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +0 -1
- package/lib/config.js.map +1 -1
- package/lib/environment.d.ts +68 -0
- package/lib/environment.d.ts.map +1 -0
- package/lib/environment.js +87 -0
- package/lib/environment.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +69 -14
- package/lib/index.js.map +1 -1
- package/lib/templates.d.ts +1 -3
- package/lib/templates.d.ts.map +1 -1
- package/lib/templates.js +1 -3
- package/lib/templates.js.map +1 -1
- package/lib/types/client/card-controller.d.ts +51 -1
- package/lib/types/client/card-controller.d.ts.map +1 -1
- package/lib/types/client/desktop.d.ts +30 -0
- package/lib/types/client/desktop.d.ts.map +1 -0
- package/lib/types/client/diff.d.ts +20 -2
- package/lib/types/client/diff.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +10 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/section.d.ts.map +1 -1
- package/lib/types/client/settings-form.d.ts +8 -1
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/config-shared.d.ts +1 -1
- package/lib/types/config-shared.d.ts.map +1 -1
- package/lib/types/config.d.ts +0 -6
- package/lib/types/config.d.ts.map +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -972,8 +972,7 @@ window.__ModuleLoader__.load({
|
|
|
972
972
|
}),
|
|
973
973
|
message: Schema.object({
|
|
974
974
|
titlePrefix: Schema.string().volatile(),
|
|
975
|
-
includeSessionTitle: Schema.boolean().default(true).volatile()
|
|
976
|
-
guiUrl: Schema.string().default("http://127.0.0.1:3080").volatile()
|
|
975
|
+
includeSessionTitle: Schema.boolean().default(true).volatile()
|
|
977
976
|
})
|
|
978
977
|
});
|
|
979
978
|
/**
|
|
@@ -1004,7 +1003,7 @@ window.__ModuleLoader__.load({
|
|
|
1004
1003
|
* 配置路由的跨端共享类型(host 与浏览器 client 共用,不得 import 任何
|
|
1005
1004
|
* Node/浏览器专属模块)。
|
|
1006
1005
|
*/
|
|
1007
|
-
/** 配置路由路径(挂在 DSH webServer
|
|
1006
|
+
/** 配置路由路径(挂在 DSH webServer 上;访问经宿主鉴权,见 src/config-route.ts)。 */
|
|
1008
1007
|
const CONFIG_PATH = "/dsh-messager/config";
|
|
1009
1008
|
//#endregion
|
|
1010
1009
|
//#region src/client/config.ts
|
|
@@ -1055,15 +1054,21 @@ window.__ModuleLoader__.load({
|
|
|
1055
1054
|
//#region src/client/diff.ts
|
|
1056
1055
|
/**
|
|
1057
1056
|
* 对比两次列表快照。首次出现的会话只建立基线、不通知;被移除的会话不通知。
|
|
1057
|
+
*
|
|
1058
|
+
* @param options.ignoreMainView - 忽略「会话是否正被主视图持有」这条过滤(v0.3.6 起):
|
|
1059
|
+
* 窗口/minimized 到后台(用户没在看)时,**当前打开的那个会话**完成了也应该提醒 ——
|
|
1060
|
+
* 否则「跑一个任务、切走」这个最常见用法永远收不到完成通知。
|
|
1061
|
+
* 页面可见但仍要通知(`onlyWhenHidden=false`)时保持过滤:那时用户正看着它。
|
|
1058
1062
|
*/
|
|
1059
|
-
function diffSessionSummaries(previous, next) {
|
|
1063
|
+
function diffSessionSummaries(previous, next, options = {}) {
|
|
1064
|
+
const ignoreMainView = options.ignoreMainView === true;
|
|
1060
1065
|
const notices = [];
|
|
1061
1066
|
for (const id of Object.keys(next)) {
|
|
1062
1067
|
const summary = next[id];
|
|
1063
1068
|
if (summary === void 0) continue;
|
|
1064
1069
|
const before = previous[id];
|
|
1065
1070
|
if (before === void 0) continue;
|
|
1066
|
-
if (before.running && !summary.running && (summary.retainedBy.mainView ?? 0) === 0) notices.push({
|
|
1071
|
+
if (before.running && !summary.running && (ignoreMainView || (summary.retainedBy.mainView ?? 0) === 0)) notices.push({
|
|
1067
1072
|
kind: "completed",
|
|
1068
1073
|
sessionId: id,
|
|
1069
1074
|
title: summary.displayTitle
|
|
@@ -1087,7 +1092,11 @@ window.__ModuleLoader__.load({
|
|
|
1087
1092
|
* 用户关掉后仍会收到(Windows 把浏览器通知渲染成系统 toast,难以分辨来源)。
|
|
1088
1093
|
*/
|
|
1089
1094
|
function triggerAllows(notice, triggers) {
|
|
1090
|
-
|
|
1095
|
+
switch (notice.kind) {
|
|
1096
|
+
case "interaction": return triggers.interaction;
|
|
1097
|
+
case "completed": return triggers.completed;
|
|
1098
|
+
case "error": return triggers.error;
|
|
1099
|
+
}
|
|
1091
1100
|
}
|
|
1092
1101
|
/** 对比两次 uiSession 统一状态快照。 */
|
|
1093
1102
|
function diffPendingInteractions(previous, next, summaries) {
|
|
@@ -1107,6 +1116,21 @@ window.__ModuleLoader__.load({
|
|
|
1107
1116
|
}
|
|
1108
1117
|
return notices;
|
|
1109
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* `api-session/error`(宿主 `agent/error` 的客户端转发事件)→ 任务出错通知。
|
|
1121
|
+
*
|
|
1122
|
+
* 会话标题从当前列表摘要里取;取不到就只发错误摘要。
|
|
1123
|
+
* 桌面版默认只用渲染端通道,这条是「关掉 system 通道后仍能收到错误提醒」的前提。
|
|
1124
|
+
*/
|
|
1125
|
+
function errorNotice(sessionId, message, summaries) {
|
|
1126
|
+
const title = summaries[sessionId]?.displayTitle;
|
|
1127
|
+
return {
|
|
1128
|
+
kind: "error",
|
|
1129
|
+
sessionId,
|
|
1130
|
+
message,
|
|
1131
|
+
...title === void 0 ? {} : { title }
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1110
1134
|
//#endregion
|
|
1111
1135
|
//#region src/client/card-controller.ts
|
|
1112
1136
|
/** 字段键:`group.field`。 */
|
|
@@ -1122,6 +1146,36 @@ window.__ModuleLoader__.load({
|
|
|
1122
1146
|
if (gate === void 0) return false;
|
|
1123
1147
|
return fields[fieldKey(gate.group, gate.field)]?.text !== "true";
|
|
1124
1148
|
}
|
|
1149
|
+
/** 用户层是否写过该组(哪怕写的是空对象)。 */
|
|
1150
|
+
function userHasGroup(user, group) {
|
|
1151
|
+
return typeof user === "object" && user !== null && Object.hasOwn(user, group);
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* 计算当前环境下**可见**的字段清单(纯函数,可单测)。
|
|
1155
|
+
*
|
|
1156
|
+
* 标记了 `desktopHidden` 的字段在桌面版会隐藏(用户层写过该组时除外)。
|
|
1157
|
+
* 📌 当前 CARD_FIELDS 里**没有**这样的字段 —— v0.3.6 实测发现渲染端 toast 可能被系统丢弃
|
|
1158
|
+
* (Windows 未登记应用),隐藏 host 通道开关会让用户无兜底可用,故改为「显示 + 说明」。
|
|
1159
|
+
* 机制保留:将来若有「开了只会重复」的字段,标记即可。
|
|
1160
|
+
*/
|
|
1161
|
+
function visibleFields(fields, options) {
|
|
1162
|
+
if (!options.desktop) return [...fields];
|
|
1163
|
+
return fields.filter((spec) => spec.desktopHidden !== true || userHasGroup(options.user, spec.group));
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* 字段的渲染文案键:桌面版有专用文案时优先。
|
|
1167
|
+
*
|
|
1168
|
+
* 用途:桌面版里 `browser` 通道的产物是**应用原生通知**(Windows toast / macOS
|
|
1169
|
+
* 通知中心,署名是应用本体),不是「浏览器通知」,沿用浏览器文案会误导用户。
|
|
1170
|
+
*/
|
|
1171
|
+
function labelKeyFor(spec, desktop) {
|
|
1172
|
+
return desktop && spec.desktopLabel !== void 0 ? spec.desktopLabel : spec.label;
|
|
1173
|
+
}
|
|
1174
|
+
/** 字段的 hint 键(桌面版优先 desktopHint);无 hint 返回 undefined。 */
|
|
1175
|
+
function hintKeyFor(spec, desktop) {
|
|
1176
|
+
if (desktop && spec.desktopHint !== void 0) return spec.desktopHint;
|
|
1177
|
+
return spec.hint;
|
|
1178
|
+
}
|
|
1125
1179
|
/** 把草稿解析为组内值;undefined 表示非法(阻止保存)。 */
|
|
1126
1180
|
function parseDraft(spec, text) {
|
|
1127
1181
|
const trimmed = text.trim();
|
|
@@ -1150,9 +1204,12 @@ window.__ModuleLoader__.load({
|
|
|
1150
1204
|
saving = false;
|
|
1151
1205
|
failed = false;
|
|
1152
1206
|
cache = null;
|
|
1153
|
-
|
|
1207
|
+
/** 是否运行在桌面版窗口(决定 desktopHidden 字段是否隐藏)。 */
|
|
1208
|
+
desktop;
|
|
1209
|
+
constructor(scope, fields, options = {}) {
|
|
1154
1210
|
this.scope = scope;
|
|
1155
1211
|
this.fields = fields;
|
|
1212
|
+
this.desktop = options.desktop === true;
|
|
1156
1213
|
scope.subscribe(() => this.invalidate());
|
|
1157
1214
|
}
|
|
1158
1215
|
/** 槽位注入面。 */
|
|
@@ -1187,7 +1244,8 @@ window.__ModuleLoader__.load({
|
|
|
1187
1244
|
this.failed = false;
|
|
1188
1245
|
this.invalidate();
|
|
1189
1246
|
},
|
|
1190
|
-
t: (key) => key
|
|
1247
|
+
t: (key) => key,
|
|
1248
|
+
desktop: this.desktop
|
|
1191
1249
|
};
|
|
1192
1250
|
}
|
|
1193
1251
|
/**
|
|
@@ -1203,7 +1261,7 @@ window.__ModuleLoader__.load({
|
|
|
1203
1261
|
const snapshot = this.scope.getSnapshot();
|
|
1204
1262
|
const available = snapshot.status === "ready";
|
|
1205
1263
|
const fields = {};
|
|
1206
|
-
for (const spec of this.
|
|
1264
|
+
for (const spec of this.visibleSpecs()) {
|
|
1207
1265
|
const key = fieldKey(spec.group, spec.field);
|
|
1208
1266
|
const staged = this.staged.get(key);
|
|
1209
1267
|
const effective = this.effectiveValue(spec);
|
|
@@ -1240,6 +1298,18 @@ window.__ModuleLoader__.load({
|
|
|
1240
1298
|
if (group === void 0) return void 0;
|
|
1241
1299
|
return group[spec.field];
|
|
1242
1300
|
}
|
|
1301
|
+
/** 当前环境下可见的字段(桌面版隐藏 system 组等,见 visibleFields)。 */
|
|
1302
|
+
visibleSpecs() {
|
|
1303
|
+
return visibleFields(this.fields, {
|
|
1304
|
+
desktop: this.desktop,
|
|
1305
|
+
user: this.scope.getSnapshot().user
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
/** 单个字段在当前环境下是否可见(同一批 spec 对象,恒等比较即可)。 */
|
|
1309
|
+
isVisible(spec) {
|
|
1310
|
+
if (spec.desktopHidden !== true) return true;
|
|
1311
|
+
return this.visibleSpecs().some((candidate) => candidate === spec);
|
|
1312
|
+
}
|
|
1243
1313
|
groupValue(group) {
|
|
1244
1314
|
const value = this.scope.getSnapshot().value;
|
|
1245
1315
|
if (typeof value !== "object" || value === null) return void 0;
|
|
@@ -1279,6 +1349,7 @@ window.__ModuleLoader__.load({
|
|
|
1279
1349
|
for (const [key, staged] of this.staged) {
|
|
1280
1350
|
const [group, field] = key.split(".");
|
|
1281
1351
|
const spec = this.spec(group, field);
|
|
1352
|
+
if (!this.isVisible(spec)) continue;
|
|
1282
1353
|
if (this.gatedOff(spec)) continue;
|
|
1283
1354
|
if (spec.secret === true && staged.text.trim() === "" && !staged.clear) continue;
|
|
1284
1355
|
if (staged.clear) {
|
|
@@ -1423,7 +1494,8 @@ window.__ModuleLoader__.load({
|
|
|
1423
1494
|
group: "browser",
|
|
1424
1495
|
field: "enabled",
|
|
1425
1496
|
kind: "toggle",
|
|
1426
|
-
label: "field.browser.enabled"
|
|
1497
|
+
label: "field.browser.enabled",
|
|
1498
|
+
desktopLabel: "field.browser.enabled.desktop"
|
|
1427
1499
|
},
|
|
1428
1500
|
{
|
|
1429
1501
|
group: "browser",
|
|
@@ -1433,7 +1505,8 @@ window.__ModuleLoader__.load({
|
|
|
1433
1505
|
hiddenUnless: {
|
|
1434
1506
|
group: "browser",
|
|
1435
1507
|
field: "enabled"
|
|
1436
|
-
}
|
|
1508
|
+
},
|
|
1509
|
+
desktopLabel: "field.browser.onlyWhenHidden.desktop"
|
|
1437
1510
|
},
|
|
1438
1511
|
{
|
|
1439
1512
|
group: "browser",
|
|
@@ -1725,15 +1798,41 @@ window.__ModuleLoader__.load({
|
|
|
1725
1798
|
field: "includeSessionTitle",
|
|
1726
1799
|
kind: "toggle",
|
|
1727
1800
|
label: "field.message.includeSessionTitle"
|
|
1728
|
-
},
|
|
1729
|
-
{
|
|
1730
|
-
group: "message",
|
|
1731
|
-
field: "guiUrl",
|
|
1732
|
-
kind: "text",
|
|
1733
|
-
label: "field.message.guiUrl"
|
|
1734
1801
|
}
|
|
1735
1802
|
];
|
|
1736
1803
|
//#endregion
|
|
1804
|
+
//#region src/client/desktop.ts
|
|
1805
|
+
/**
|
|
1806
|
+
* 桌面版(DSH Desktop)窗口判定。
|
|
1807
|
+
*
|
|
1808
|
+
* 官方标记:桌面壳的 preload 在窗口里 `contextBridge.exposeInMainWorld('dshDesktopBoot', …)`
|
|
1809
|
+
* (asar 内 `lib/preload-app.cjs`);DSH 自己的前端也用
|
|
1810
|
+
* `globalThis.dshDesktopBoot !== undefined` 决定 boot 通道(桌面走 IPC 取启动清单,
|
|
1811
|
+
* 浏览器读 HTML 里的 `__DSH_BOOT__`)。所以这是「我在桌面窗口里」的权威信号。
|
|
1812
|
+
*
|
|
1813
|
+
* ⚠️ 仅 client 端可用(host 端没有 window;host 侧的等价判定见 src/environment.ts)。
|
|
1814
|
+
*/
|
|
1815
|
+
/** 判断给定全局对象是否处于桌面版窗口(可传替身便于单测)。 */
|
|
1816
|
+
function isDesktopWindow(target = globalThis) {
|
|
1817
|
+
return typeof target === "object" && target !== null && "dshDesktopBoot" in target;
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* 「用户现在没在看这个窗口」判定 —— 决定 `onlyWhenHidden` 是否放行通知。
|
|
1821
|
+
*
|
|
1822
|
+
* - 浏览器(`dsh web`):页面 `hidden`(切走标签页 / 最小化)即为没在看;
|
|
1823
|
+
* - **桌面版:`visibilityState` 只在窗口最小化或被隐藏时才变 `hidden`**,
|
|
1824
|
+
* 窗口被别的窗口盖住、失去焦点时它仍是 `visible`。若只看 `visibilityState`,
|
|
1825
|
+
* 用户切到别的应用去干活时永远不会收到通知 —— 所以桌面版把**失去焦点**也算没在看。
|
|
1826
|
+
*
|
|
1827
|
+
* @param options.desktop - 是否桌面版窗口(见 isDesktopWindow)。
|
|
1828
|
+
* @param options.visibilityState - `document.visibilityState`。
|
|
1829
|
+
* @param options.hasFocus - `document.hasFocus()`。
|
|
1830
|
+
*/
|
|
1831
|
+
function isWindowBackgrounded(options) {
|
|
1832
|
+
if (options.visibilityState === "hidden") return true;
|
|
1833
|
+
return options.desktop && !options.hasFocus;
|
|
1834
|
+
}
|
|
1835
|
+
//#endregion
|
|
1737
1836
|
//#region src/client/fetch-scope.ts
|
|
1738
1837
|
/** 创建 fetch 版 scope。 */
|
|
1739
1838
|
function createFetchScope(fetcher) {
|
|
@@ -1818,6 +1917,11 @@ window.__ModuleLoader__.load({
|
|
|
1818
1917
|
telegram: "group.telegram",
|
|
1819
1918
|
message: "group.message"
|
|
1820
1919
|
};
|
|
1920
|
+
/**
|
|
1921
|
+
* 桌面版专用分组标题(当前只有 browser):桌面版里那条通道产出的是**应用原生通知**,
|
|
1922
|
+
* 不是"浏览器通知"。键集必须与 locales.ts 一致(字典一致性测试覆盖)。
|
|
1923
|
+
*/
|
|
1924
|
+
const GROUP_TITLE_KEYS_DESKTOP = { browser: "group.browser.desktop" };
|
|
1821
1925
|
const FIELD_STYLE = {
|
|
1822
1926
|
display: "flex",
|
|
1823
1927
|
flexDirection: "column",
|
|
@@ -1994,9 +2098,10 @@ window.__ModuleLoader__.load({
|
|
|
1994
2098
|
});
|
|
1995
2099
|
}
|
|
1996
2100
|
/** 渲染一个字段行(DSH ValueField 同款;toggle 标签与开关同一行)。 */
|
|
1997
|
-
function FieldRow({ spec, state, actions, t, disabled }) {
|
|
2101
|
+
function FieldRow({ spec, state, actions, t, disabled, desktop }) {
|
|
1998
2102
|
const id = `dsh-messager-${spec.group}-${spec.field}`;
|
|
1999
|
-
const label = t(spec
|
|
2103
|
+
const label = t(labelKeyFor(spec, desktop));
|
|
2104
|
+
const hintKey = hintKeyFor(spec, desktop);
|
|
2000
2105
|
const overriddenBadge = state.overridden && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2001
2106
|
style: {
|
|
2002
2107
|
display: "inline-flex",
|
|
@@ -2096,7 +2201,7 @@ window.__ModuleLoader__.load({
|
|
|
2096
2201
|
control,
|
|
2097
2202
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2098
2203
|
style: state.invalid ? INVALID_STYLE : HINT_STYLE,
|
|
2099
|
-
children: state.invalid ? t("status.invalidField") :
|
|
2204
|
+
children: state.invalid ? t("status.invalidField") : hintKey === void 0 ? "" : t(hintKey)
|
|
2100
2205
|
})
|
|
2101
2206
|
]
|
|
2102
2207
|
});
|
|
@@ -2108,8 +2213,9 @@ window.__ModuleLoader__.load({
|
|
|
2108
2213
|
* @param state - 控制器快照(useMessagerCard 的返回)。
|
|
2109
2214
|
* @param actions - 控制器动作(edit/reset/save/discard)。
|
|
2110
2215
|
* @param t - 翻译函数(键 → 当前语言文案)。
|
|
2216
|
+
* @param desktop - 桌面版窗口:分组标题与字段文案走桌面变体(见 GROUP_TITLE_KEYS_DESKTOP)。
|
|
2111
2217
|
*/
|
|
2112
|
-
function MessagerSettingsForm({ state, actions, t }) {
|
|
2218
|
+
function MessagerSettingsForm({ state, actions, t, desktop = false }) {
|
|
2113
2219
|
const groups = [...new Set(Object.keys(state.fields).map((key) => key.split(".")[0]))];
|
|
2114
2220
|
const actionsDisabled = !state.writable || state.saving;
|
|
2115
2221
|
const blocked = !state.dirty || state.invalid || state.saving;
|
|
@@ -2141,7 +2247,7 @@ window.__ModuleLoader__.load({
|
|
|
2141
2247
|
}),
|
|
2142
2248
|
groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2143
2249
|
style: GROUP_HEADING_STYLE,
|
|
2144
|
-
children: t(GROUP_TITLE_KEYS[group] ?? group)
|
|
2250
|
+
children: t((desktop ? GROUP_TITLE_KEYS_DESKTOP[group] : void 0) ?? GROUP_TITLE_KEYS[group] ?? group)
|
|
2145
2251
|
}), Object.entries(state.fields).filter(([key]) => key.startsWith(`${group}.`)).map(([key, field]) => {
|
|
2146
2252
|
key.slice(key.indexOf(".") + 1);
|
|
2147
2253
|
const spec = FIELD_LOOKUP[key];
|
|
@@ -2152,7 +2258,8 @@ window.__ModuleLoader__.load({
|
|
|
2152
2258
|
state: field,
|
|
2153
2259
|
actions,
|
|
2154
2260
|
t,
|
|
2155
|
-
disabled: actionsDisabled
|
|
2261
|
+
disabled: actionsDisabled,
|
|
2262
|
+
desktop
|
|
2156
2263
|
}, key);
|
|
2157
2264
|
})] }, group)),
|
|
2158
2265
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2233,13 +2340,23 @@ window.__ModuleLoader__.load({
|
|
|
2233
2340
|
lineHeight: 1.6,
|
|
2234
2341
|
color: "var(--dsw-alias-label-tertiary)"
|
|
2235
2342
|
};
|
|
2343
|
+
/** 环境说明(如桌面版隐藏了「系统通知」开关)。 */
|
|
2344
|
+
const NOTE_STYLE = {
|
|
2345
|
+
margin: 0,
|
|
2346
|
+
padding: "8px 10px",
|
|
2347
|
+
borderRadius: 6,
|
|
2348
|
+
fontSize: 12,
|
|
2349
|
+
lineHeight: 1.6,
|
|
2350
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
2351
|
+
background: "var(--dsw-alias-bg-layer-3)"
|
|
2352
|
+
};
|
|
2236
2353
|
/**
|
|
2237
2354
|
* dsh-messager 设置分区(设置页「通知&信使」)。
|
|
2238
2355
|
* @param props - 槽位 owner props + 注入面(useMessagerCard + 动作 + t)。
|
|
2239
2356
|
*/
|
|
2240
2357
|
function MessagerSection(props) {
|
|
2241
2358
|
const state = props.useMessagerCard((snapshot) => snapshot);
|
|
2242
|
-
const { edit, reset, save, discard, t } = props;
|
|
2359
|
+
const { edit, reset, save, discard, t, desktop } = props;
|
|
2243
2360
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2244
2361
|
style: SECTION_STYLE,
|
|
2245
2362
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
@@ -2247,8 +2364,11 @@ window.__ModuleLoader__.load({
|
|
|
2247
2364
|
children: t("nav")
|
|
2248
2365
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2249
2366
|
style: DESCRIPTION_STYLE,
|
|
2250
|
-
children: t("section.description")
|
|
2251
|
-
})] }), state.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2367
|
+
children: t(desktop ? "section.description.desktop" : "section.description")
|
|
2368
|
+
})] }), state.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [desktop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2369
|
+
style: NOTE_STYLE,
|
|
2370
|
+
children: t("hint.desktopFallback")
|
|
2371
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessagerSettingsForm, {
|
|
2252
2372
|
state,
|
|
2253
2373
|
actions: {
|
|
2254
2374
|
edit,
|
|
@@ -2256,8 +2376,9 @@ window.__ModuleLoader__.load({
|
|
|
2256
2376
|
save,
|
|
2257
2377
|
discard
|
|
2258
2378
|
},
|
|
2259
|
-
t
|
|
2260
|
-
|
|
2379
|
+
t,
|
|
2380
|
+
desktop
|
|
2381
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2261
2382
|
style: UNAVAILABLE_STYLE,
|
|
2262
2383
|
role: "status",
|
|
2263
2384
|
children: t("status.unavailable")
|
|
@@ -2269,9 +2390,11 @@ window.__ModuleLoader__.load({
|
|
|
2269
2390
|
const zh = {
|
|
2270
2391
|
nav: "通知&信使",
|
|
2271
2392
|
"section.description": "会话交互 / 任务完成 / 出错时的通知推送:系统通知、浏览器通知、飞书/企业微信/Discord/钉钉/Telegram。",
|
|
2393
|
+
"section.description.desktop": "会话交互 / 任务完成 / 出错时的通知推送:应用通知(由本应用投递,点通知可把窗口提到前台)、飞书/企业微信/Discord/钉钉/Telegram。",
|
|
2272
2394
|
"group.triggers": "触发时机",
|
|
2273
2395
|
"group.system": "系统通知",
|
|
2274
2396
|
"group.browser": "浏览器通知",
|
|
2397
|
+
"group.browser.desktop": "应用通知",
|
|
2275
2398
|
"group.feishu": "飞书机器人",
|
|
2276
2399
|
"group.wecom": "企业微信",
|
|
2277
2400
|
"group.discord": "Discord",
|
|
@@ -2285,8 +2408,10 @@ window.__ModuleLoader__.load({
|
|
|
2285
2408
|
"field.system.icon": "图标路径",
|
|
2286
2409
|
"field.system.verbosity": "内容繁复度",
|
|
2287
2410
|
"field.browser.enabled": "启用浏览器通知",
|
|
2411
|
+
"field.browser.enabled.desktop": "启用应用通知",
|
|
2288
2412
|
"field.browser.icon": "图标 URL",
|
|
2289
2413
|
"field.browser.onlyWhenHidden": "仅页面隐藏时通知",
|
|
2414
|
+
"field.browser.onlyWhenHidden.desktop": "仅窗口不在前台时通知",
|
|
2290
2415
|
"field.browser.verbosity": "内容繁复度",
|
|
2291
2416
|
"field.feishu.enabled": "飞书机器人",
|
|
2292
2417
|
"field.feishu.webhookUrl": "Webhook 地址",
|
|
@@ -2314,10 +2439,10 @@ window.__ModuleLoader__.load({
|
|
|
2314
2439
|
"field.telegram.verbosity": "内容繁复度",
|
|
2315
2440
|
"field.message.titlePrefix": "标题前缀",
|
|
2316
2441
|
"field.message.includeSessionTitle": "正文包含会话标题",
|
|
2317
|
-
"field.message.guiUrl": "打开链接地址",
|
|
2318
2442
|
"hint.system.icon": "node-notifier 需要文件绝对路径",
|
|
2319
2443
|
"hint.secret": "留空不修改;重置可清除已存密钥",
|
|
2320
2444
|
"hint.message.titlePrefix": "如 [DSH]",
|
|
2445
|
+
"hint.desktopFallback": "桌面版的通知由应用本体投递。若一直收不到(例如 Windows 未登记本应用),可打开上面的「系统通知」用 host 通道兜底 —— 代价是通知的署名不是本应用。",
|
|
2321
2446
|
"action.save": "保存",
|
|
2322
2447
|
"action.saving": "保存中…",
|
|
2323
2448
|
"action.discard": "放弃修改",
|
|
@@ -2333,9 +2458,11 @@ window.__ModuleLoader__.load({
|
|
|
2333
2458
|
const en = {
|
|
2334
2459
|
nav: "Messenger",
|
|
2335
2460
|
"section.description": "Notifications for interaction, task completion and errors: system toast, browser notification, Feishu/WeCom/Discord/DingTalk/Telegram.",
|
|
2461
|
+
"section.description.desktop": "Notifications for interaction, task completion and errors: app notifications (delivered by this app; clicking one brings the window forward), Feishu/WeCom/Discord/DingTalk/Telegram.",
|
|
2336
2462
|
"group.triggers": "Triggers",
|
|
2337
2463
|
"group.system": "System",
|
|
2338
2464
|
"group.browser": "Browser",
|
|
2465
|
+
"group.browser.desktop": "App notification",
|
|
2339
2466
|
"group.feishu": "Feishu bot",
|
|
2340
2467
|
"group.wecom": "WeCom",
|
|
2341
2468
|
"group.discord": "Discord",
|
|
@@ -2349,8 +2476,10 @@ window.__ModuleLoader__.load({
|
|
|
2349
2476
|
"field.system.icon": "Icon path",
|
|
2350
2477
|
"field.system.verbosity": "Verbosity",
|
|
2351
2478
|
"field.browser.enabled": "Enable browser notifications",
|
|
2479
|
+
"field.browser.enabled.desktop": "Enable app notifications",
|
|
2352
2480
|
"field.browser.icon": "Icon URL",
|
|
2353
2481
|
"field.browser.onlyWhenHidden": "Notify only when the page is hidden",
|
|
2482
|
+
"field.browser.onlyWhenHidden.desktop": "Notify only when the app is in the background",
|
|
2354
2483
|
"field.browser.verbosity": "Verbosity",
|
|
2355
2484
|
"field.feishu.enabled": "Feishu bot",
|
|
2356
2485
|
"field.feishu.webhookUrl": "Webhook URL",
|
|
@@ -2378,10 +2507,10 @@ window.__ModuleLoader__.load({
|
|
|
2378
2507
|
"field.telegram.verbosity": "Verbosity",
|
|
2379
2508
|
"field.message.titlePrefix": "Title prefix",
|
|
2380
2509
|
"field.message.includeSessionTitle": "Include session title in the body",
|
|
2381
|
-
"field.message.guiUrl": "Open link URL",
|
|
2382
2510
|
"hint.system.icon": "node-notifier requires an absolute file path",
|
|
2383
2511
|
"hint.secret": "Leave blank to keep the stored secret; Reset clears it",
|
|
2384
2512
|
"hint.message.titlePrefix": "e.g. [DSH]",
|
|
2513
|
+
"hint.desktopFallback": "On the desktop app, notifications are delivered by the app itself. If none ever appear (for example Windows has not registered the app), turn on \"System\" above as a fallback — those are delivered by the host, so they are not attributed to the app.",
|
|
2385
2514
|
"action.save": "Save",
|
|
2386
2515
|
"action.saving": "Saving…",
|
|
2387
2516
|
"action.discard": "Discard",
|
|
@@ -2410,7 +2539,7 @@ window.__ModuleLoader__.load({
|
|
|
2410
2539
|
/** localStorage 跨标签页去重键前缀。 */
|
|
2411
2540
|
const STORAGE_PREFIX = "dsh-messager:notified:";
|
|
2412
2541
|
/** 浏览器通知投递器。 */
|
|
2413
|
-
var BrowserNotifier = class {
|
|
2542
|
+
var BrowserNotifier = class BrowserNotifier {
|
|
2414
2543
|
config;
|
|
2415
2544
|
/** `${kind}:${sessionId}` → 上次投递时间戳(内存冷却)。 */
|
|
2416
2545
|
cooldowns = /* @__PURE__ */ new Map();
|
|
@@ -2422,18 +2551,37 @@ window.__ModuleLoader__.load({
|
|
|
2422
2551
|
}
|
|
2423
2552
|
/** 列表快照变化 → 通知。 */
|
|
2424
2553
|
onListChange(previous, next) {
|
|
2425
|
-
this.onNotices(diffSessionSummaries(previous.byId, next.byId));
|
|
2554
|
+
this.onNotices(diffSessionSummaries(previous.byId, next.byId, { ignoreMainView: BrowserNotifier.backgrounded() }));
|
|
2426
2555
|
}
|
|
2427
2556
|
/** 待交互状态变化 → 通知。 */
|
|
2428
2557
|
onPendingChange(previous, next, sessions) {
|
|
2429
2558
|
this.onNotices(diffPendingInteractions(previous, next, sessions.byId));
|
|
2430
2559
|
}
|
|
2560
|
+
/** 任务出错(宿主 `agent/error` 经 `api-session/error` 转发)→ 通知。 */
|
|
2561
|
+
onError(sessionId, message, sessions) {
|
|
2562
|
+
this.onNotices([errorNotice(sessionId, message, sessions.byId)]);
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* 用户现在是否「没在看这个窗口」。
|
|
2566
|
+
*
|
|
2567
|
+
* 浏览器:页面 hidden(切走标签页 / 最小化);桌面版:再补上「失去焦点」——
|
|
2568
|
+
* 桌面窗口被别的窗口盖住时 `visibilityState` 仍是 `visible`,
|
|
2569
|
+
* 只看它会导致切到别的应用就永远不提醒(见 src/client/desktop.ts)。
|
|
2570
|
+
*/
|
|
2571
|
+
static backgrounded() {
|
|
2572
|
+
if (typeof document === "undefined") return false;
|
|
2573
|
+
return isWindowBackgrounded({
|
|
2574
|
+
desktop: isDesktopWindow(),
|
|
2575
|
+
visibilityState: document.visibilityState,
|
|
2576
|
+
hasFocus: document.hasFocus()
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2431
2579
|
onNotices(notices) {
|
|
2432
2580
|
const config = this.config.get();
|
|
2433
2581
|
if (!config.browser.enabled) return;
|
|
2434
2582
|
if (typeof Notification === "undefined") return;
|
|
2435
2583
|
if (Notification.permission !== "granted") return;
|
|
2436
|
-
if (config.browser.onlyWhenHidden &&
|
|
2584
|
+
if (config.browser.onlyWhenHidden && !BrowserNotifier.backgrounded()) return;
|
|
2437
2585
|
for (const notice of notices) {
|
|
2438
2586
|
if (!triggerAllows(notice, config.triggers)) continue;
|
|
2439
2587
|
if (!this.allow(notice, config)) continue;
|
|
@@ -2471,6 +2619,7 @@ window.__ModuleLoader__.load({
|
|
|
2471
2619
|
titleOf(notice, config) {
|
|
2472
2620
|
let base;
|
|
2473
2621
|
if (notice.kind === "interaction") base = notice.interaction === "approval" ? "需要交互:等待审批" : notice.interaction === "plan-review" ? "需要交互:计划待审" : "需要交互:等待回答";
|
|
2622
|
+
else if (notice.kind === "error") base = "任务出错";
|
|
2474
2623
|
else base = "任务完成";
|
|
2475
2624
|
const prefix = config.message.titlePrefix;
|
|
2476
2625
|
return prefix === void 0 || prefix === "" ? base : `${prefix} ${base}`;
|
|
@@ -2478,8 +2627,8 @@ window.__ModuleLoader__.load({
|
|
|
2478
2627
|
bodyOf(notice, config, verbosity) {
|
|
2479
2628
|
const lines = [];
|
|
2480
2629
|
if (verbosity === "minimal") return "";
|
|
2630
|
+
if (notice.kind === "error" && notice.message !== void 0 && notice.message !== "") lines.push(`错误:${notice.message}`);
|
|
2481
2631
|
if (config.message.includeSessionTitle && notice.title !== void 0) lines.push(`会话:${notice.title}`);
|
|
2482
|
-
if (verbosity === "detailed") lines.push(`打开:${config.message.guiUrl}`);
|
|
2483
2632
|
return lines.join("\n");
|
|
2484
2633
|
}
|
|
2485
2634
|
};
|
|
@@ -2541,7 +2690,11 @@ window.__ModuleLoader__.load({
|
|
|
2541
2690
|
previousStatus = next;
|
|
2542
2691
|
});
|
|
2543
2692
|
ctx.effect(() => () => offStatus(), "dsh-messager: session status subscription");
|
|
2544
|
-
const
|
|
2693
|
+
const offError = ctx.remote.$on("api-session/error", (sessionId, message) => {
|
|
2694
|
+
notifier.onError(sessionId, message, ctx.sessions.list.getSnapshot());
|
|
2695
|
+
});
|
|
2696
|
+
ctx.effect(() => () => offError(), "dsh-messager: error subscription");
|
|
2697
|
+
const controller = new MessagerCardController(fetchScope.scope, CARD_FIELDS, { desktop: isDesktopWindow() });
|
|
2545
2698
|
ctx.effect(() => ctx.locale.register(LOCALE_NS, {
|
|
2546
2699
|
zh,
|
|
2547
2700
|
en
|