dsh-session-attention 0.1.1 → 0.2.0
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/lib/client.js +97 -4
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -799,6 +799,8 @@ html[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view])
|
|
|
799
799
|
[data-dsh-att-entry] {
|
|
800
800
|
display: flex;
|
|
801
801
|
align-items: center;
|
|
802
|
+
justify-content: flex-start; /* 明确左对齐:外壳若覆盖了 flex,也不会变成居中按钮 */
|
|
803
|
+
text-align: left;
|
|
802
804
|
gap: 8px;
|
|
803
805
|
width: 100%;
|
|
804
806
|
height: 32px;
|
|
@@ -816,8 +818,9 @@ html[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view])
|
|
|
816
818
|
background: var(--dsw-specific-sidebar-nav-item-hover);
|
|
817
819
|
color: var(--dsw-alias-label-primary);
|
|
818
820
|
}
|
|
821
|
+
/* 选中态只做「更亮 + 加粗」,不画背景块:面板状态万一过期,也不会在侧边栏留下一块灰底。 */
|
|
819
822
|
[data-dsh-att-entry][data-active] {
|
|
820
|
-
background:
|
|
823
|
+
background: transparent;
|
|
821
824
|
color: var(--dsw-alias-label-primary);
|
|
822
825
|
font-weight: 600;
|
|
823
826
|
}
|
|
@@ -830,6 +833,9 @@ html[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view])
|
|
|
830
833
|
[data-dsh-frame][data-sidebar-collapsed] [data-dsh-att-entry] .todo-entry-label,
|
|
831
834
|
[data-sidebar-collapsed] [data-dsh-att-entry] .todo-entry-label { display: none; }
|
|
832
835
|
|
|
836
|
+
/* --- 官方 main 座位里的页面外层(外壳只在选中本面板时挂载它) ----------- */
|
|
837
|
+
.todo-panel-page { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }
|
|
838
|
+
|
|
833
839
|
/* --- 面板主体 ------------------------------------------------------------ */
|
|
834
840
|
.todo-panel {
|
|
835
841
|
display: flex;
|
|
@@ -1150,7 +1156,7 @@ html[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view])
|
|
|
1150
1156
|
`;
|
|
1151
1157
|
/** 注入样式(幂等)。 */
|
|
1152
1158
|
function injectStyles() {
|
|
1153
|
-
|
|
1159
|
+
document.querySelectorAll("style[data-dsh-att-css]").forEach((el) => el.remove());
|
|
1154
1160
|
const style = document.createElement("style");
|
|
1155
1161
|
style.dataset.dshAttCss = "";
|
|
1156
1162
|
style.textContent = ATTENTION_CSS;
|
|
@@ -1425,6 +1431,88 @@ html[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view])
|
|
|
1425
1431
|
});
|
|
1426
1432
|
}
|
|
1427
1433
|
//#endregion
|
|
1434
|
+
//#region src/client/panel-seat.tsx
|
|
1435
|
+
/** 面板 id:侧边栏行与主视图页面共用它,也是 layout.selectPanel 的取值。 */
|
|
1436
|
+
const PANEL_ID = "session-attention";
|
|
1437
|
+
/** 行序(外壳:插件页 0、定时页 10、任务看板 20)。 */
|
|
1438
|
+
const PANEL_ORDER = 30;
|
|
1439
|
+
/** 外壳是否提供官方座位(旧版外壳没有,于是退回 DOM 注入)。 */
|
|
1440
|
+
function seatsAvailable(ctx) {
|
|
1441
|
+
const slots = ctx.slots;
|
|
1442
|
+
return slots !== void 0 && typeof slots.inject === "function" && typeof slots.register === "function";
|
|
1443
|
+
}
|
|
1444
|
+
/** 行图标:外壳给尺寸与选中态,本组件只画 glyph。 */
|
|
1445
|
+
function PanelIcon({ size }) {
|
|
1446
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1447
|
+
"data-dsh-panel-entry": PANEL_ID,
|
|
1448
|
+
width: size,
|
|
1449
|
+
height: size,
|
|
1450
|
+
viewBox: "0 0 16 16",
|
|
1451
|
+
fill: "none",
|
|
1452
|
+
stroke: "currentColor",
|
|
1453
|
+
strokeWidth: "1.3",
|
|
1454
|
+
strokeLinecap: "round",
|
|
1455
|
+
strokeLinejoin: "round",
|
|
1456
|
+
"aria-hidden": "true",
|
|
1457
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 2.2a4 4 0 0 0-4 4v2.4L3 11h10L11.9 8.6V6.2a4 4 0 0 0-4-4Z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6.6 12.8a1.5 1.5 0 0 0 2.8 0" })]
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
/** 主视图页面:外壳只在本面板被选中时挂载它。 */
|
|
1461
|
+
function PanelPage() {
|
|
1462
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1463
|
+
className: "todo-panel-page",
|
|
1464
|
+
"data-dsh-att-view": "",
|
|
1465
|
+
"data-dsh-plugin": "session-attention",
|
|
1466
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttentionPanel, {})
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
/** 注册侧边栏行 + 主视图页面。 */
|
|
1470
|
+
function registerPanel(ctx) {
|
|
1471
|
+
const slots = ctx.slots;
|
|
1472
|
+
const disposers = [];
|
|
1473
|
+
disposers.push(slots.inject("sidebar.panellist", () => slots.register({
|
|
1474
|
+
name: "sidebar.panellist",
|
|
1475
|
+
id: PANEL_ID,
|
|
1476
|
+
order: PANEL_ORDER,
|
|
1477
|
+
label: () => "提醒"
|
|
1478
|
+
}, PanelIcon)));
|
|
1479
|
+
disposers.push(slots.inject("main", () => slots.register({
|
|
1480
|
+
name: "main",
|
|
1481
|
+
key: PANEL_ID
|
|
1482
|
+
}, PanelPage)));
|
|
1483
|
+
return () => {
|
|
1484
|
+
for (const dispose of disposers.splice(0)) try {
|
|
1485
|
+
dispose();
|
|
1486
|
+
} catch {}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* 把本插件的面板开关与外壳「当前选中的面板」双向对齐:
|
|
1491
|
+
* 外壳那边切换 → 同步进 store(✕ / Esc 才有意义);
|
|
1492
|
+
* store 这边关闭(点 ✕)→ 让外壳切回对话。两边都做等值判断,不会互相触发成环。
|
|
1493
|
+
*/
|
|
1494
|
+
function syncPanelWithLayout(ctx) {
|
|
1495
|
+
const layout = ctx.get?.("layout");
|
|
1496
|
+
const info = layout?.panelInfo;
|
|
1497
|
+
if (info === void 0) return () => {};
|
|
1498
|
+
const pull = () => {
|
|
1499
|
+
const open = info.getSnapshot().activePanelId === PANEL_ID;
|
|
1500
|
+
if (attentionStore.getSnapshot().panelOpen !== open) attentionStore.setPanel(open);
|
|
1501
|
+
};
|
|
1502
|
+
const unsubscribe = info.subscribe(pull);
|
|
1503
|
+
const push = attentionStore.subscribe(() => {
|
|
1504
|
+
const open = attentionStore.getSnapshot().panelOpen;
|
|
1505
|
+
const active = info.getSnapshot().activePanelId === PANEL_ID;
|
|
1506
|
+
if (open && !active) layout?.selectPanel?.(PANEL_ID);
|
|
1507
|
+
else if (!open && active) layout?.selectPanel?.(null);
|
|
1508
|
+
});
|
|
1509
|
+
pull();
|
|
1510
|
+
return () => {
|
|
1511
|
+
unsubscribe();
|
|
1512
|
+
push();
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
//#endregion
|
|
1428
1516
|
//#region src/client/sidebar-entry.ts
|
|
1429
1517
|
/**
|
|
1430
1518
|
* 侧边栏入口「提醒」:DOM 注入(shell 无对外侧边栏 slot),MutationObserver 自愈。
|
|
@@ -1743,8 +1831,13 @@ html[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view])
|
|
|
1743
1831
|
})();
|
|
1744
1832
|
}));
|
|
1745
1833
|
try {
|
|
1746
|
-
|
|
1747
|
-
|
|
1834
|
+
if (seatsAvailable(ctx)) {
|
|
1835
|
+
disposers.push(registerPanel(ctx));
|
|
1836
|
+
disposers.push(syncPanelWithLayout(ctx));
|
|
1837
|
+
} else {
|
|
1838
|
+
disposers.push(mountSidebarEntry());
|
|
1839
|
+
disposers.push(mountPanel());
|
|
1840
|
+
}
|
|
1748
1841
|
disposers.push(mountLayer());
|
|
1749
1842
|
} catch (error) {
|
|
1750
1843
|
console.error("[dsh-session-attention] mount failed:", error);
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useRef","useCallback","useSyncExternalStore","useState","createRoot"],"sources":["../src/client/api.ts","../src/client/attention-store.ts","../src/client/session-nav.ts","../src/types.ts","../src/client/ActivityBall.tsx","../src/client/sound.ts","../src/client/AttentionLayer.tsx","../src/client/styles.ts","../src/client/AttentionPanel.tsx","../src/client/sidebar-entry.ts","../src/client/index.tsx"],"sourcesContent":["/**\n * 浏览器 → host 的同源 JSON API 封装(与 dsh-pet 的 /api/pet/* 同模式)。\n * @module dsh-session-attention/client/api\n */\n\nimport type { AttentionSettings, AttentionState, BallSettings } from '../types.ts'\n\nasync function attentionFetch<T>(path: string, body?: unknown): Promise<T> {\n const response = await fetch(path, body === undefined\n ? {}\n : {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!response.ok) {\n throw new Error(`attention ${path} failed: ${response.status}`)\n }\n return (await response.json()) as T\n}\n\nexport const attentionApi = {\n /** 在文件管理器里打开数据目录(自定义音效放这里)。 */\n openDir: () => attentionFetch<{ ok: boolean, dir?: string }>('/api/attention/open-dir', {}),\n state: () => attentionFetch<AttentionState>('/api/attention/state'),\n setSettings: (settings: {\n attention?: Partial<AttentionSettings>\n ball?: Partial<BallSettings>\n }) => attentionFetch<{ ok: boolean }>('/api/attention/set-settings', { settings }),\n}\n","/**\n * 浏览器端 store:单一实例,持有 host 状态快照(设置)+ DSH 活跃会话列表 +\n * 最近一条提醒 + 面板开合。React 组件经 useSyncExternalStore 订阅。\n * @module dsh-session-attention/client/attention-store\n */\n\nimport { attentionApi } from './api.ts'\nimport type { AttentionEvent, AttentionSettings, AttentionState, BallSettings, SessionActivity } from '../types.ts'\n\nexport interface AttentionClientState {\n data: AttentionState | null\n error: string | null\n /** DSH 活跃会话列表(活动悬浮球展示)。 */\n activity: SessionActivity[]\n /** 最近一条「需要你操作」提醒;用于悬浮球/面板闪示与「查看最近」。 */\n latest: AttentionEvent | null\n /** 设置面板是否展开(侧边栏「提醒」入口控制)。 */\n panelOpen: boolean\n}\n\ntype Listener = () => void\n\nclass AttentionStore {\n private state: AttentionClientState = {\n data: null,\n error: null,\n activity: [],\n latest: null,\n panelOpen: false,\n }\n\n private readonly listeners = new Set<Listener>()\n\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getSnapshot = (): AttentionClientState => this.state\n\n private set(patch: Partial<AttentionClientState>): void {\n this.state = { ...this.state, ...patch }\n for (const listener of this.listeners) listener()\n }\n\n /** host 状态(设置)推送。 */\n applyState(data: AttentionState): void {\n this.set({ data, error: null })\n }\n\n /** DSH 活跃会话列表推送。 */\n applyActivity(activity: SessionActivity[]): void {\n this.set({ activity })\n }\n\n /** 一条「需要你操作」提醒到达。 */\n applyAttention(latest: AttentionEvent): void {\n this.set({ latest })\n }\n\n /** 拉取 host 状态(首次加载 / 回到前台兜底)。 */\n async refresh(): Promise<void> {\n try {\n this.applyState(await attentionApi.state())\n } catch (error) {\n this.set({ error: error instanceof Error ? error.message : String(error) })\n }\n }\n\n /** 在文件管理器里打开数据目录(音效文件放这里)。失败静默。 */\n async openSoundDir(): Promise<void> {\n try {\n await attentionApi.openDir()\n } catch { /* 忽略 */ }\n }\n\n togglePanel(): void {\n this.set({ panelOpen: !this.state.panelOpen })\n }\n\n setPanel(open: boolean): void {\n if (this.state.panelOpen !== open) this.set({ panelOpen: open })\n }\n\n async setAttention(patch: Partial<AttentionSettings>): Promise<void> {\n await attentionApi.setSettings({ attention: patch })\n await this.refresh()\n }\n\n async setBall(patch: Partial<BallSettings>): Promise<void> {\n await attentionApi.setSettings({ ball: patch })\n await this.refresh()\n }\n}\n\n/** 应用级单例(入口 / 面板 / 提醒层共用)。 */\nexport const attentionStore = new AttentionStore()\n","/**\n * 会话跳转桥:client 半经 cordis 注入拿到 sessions 服务后把「打开会话」登记在这里,\n * 悬浮球等纯 React 组件(不持有 ctx)通过它跳进对应会话。服务缺席时优雅降级——\n * canOpenSession() 返回 false,UI 就不显示可点击的样子。\n *\n * DSH 0.1.6 起 `sessions` 客户端服务的 `open(id)` 已被移除(新公开接口是\n * `retain` / `using` / `retainInfo` / `subagentAddress`),旧调用会抛 TypeError\n * 并被静默吞掉,表现为「点会话行没反应」。而 `sessions.retain` 只保留引用、\n * 不切换主视图(切视图由 ui-workspace 内部 `replaceMain` 负责并管理引用生命周期),\n * 外部插件直接 retain 会泄漏引用且界面不动。\n *\n * 因此跳转改为委托官方组件:在侧边栏定位该会话行并触发它的原生点击,\n * 由 ui-workspace 自己完成主视图切换。无法定位时降级为不展示可点击态。\n * @module dsh-todo-reminder/client/session-nav\n */\n\n/**\n * 跳转目标描述。\n * @property sessionId - 会话 id\n * @property title - 会话标题,用于在侧边栏匹配对应行;缺失时退化为用 id 匹配\n */\nexport interface SessionTarget {\n readonly sessionId: string\n readonly title?: string\n}\n\ntype Opener = (target: SessionTarget) => Promise<boolean> | boolean\n\nlet opener: Opener | null = null\n\n/**\n * 标题解析桥:host 推来的会话标题在 DSH 0.2 里已经拿不到(session/event 不再带\n * session/title),真正的标题源是**客户端的会话列表**(侧边栏显示的就是它)。\n * client 半在 apply 时把解析器登记进来,纯 React 组件经此取标题。\n */\nlet titleResolver: ((sessionId: string) => string | undefined) | null = null\n\n/** 登记标题解析器;返回注销 disposer。 */\nexport function setSessionTitleResolver(next: (sessionId: string) => string | undefined): () => void {\n titleResolver = next\n return () => {\n if (titleResolver === next) titleResolver = null\n }\n}\n\n/** 取某个会话的标题;解析器缺席或查不到时返回 undefined。 */\nexport function sessionTitleOf(sessionId: string): string | undefined {\n if (titleResolver === null) return undefined\n try {\n return titleResolver(sessionId)\n } catch {\n return undefined\n }\n}\n\n/**\n * 登记打开会话的实现。\n * @returns 注销该实现的 disposer(热重载 / 卸载时调用)。\n */\nexport function setSessionOpener(next: Opener): () => void {\n opener = next\n return () => {\n if (opener === next) opener = null\n }\n}\n\n/**\n * 当前是否具备跳转会话的能力。\n * 只反映「桥已登记」,不代表具体某个会话此刻一定能在侧边栏找到。\n */\nexport function canOpenSession(): boolean {\n return opener !== null\n}\n\n/** 会话行文本的归一化:折叠空白,便于跨渲染比对。 */\nfunction normalizeLabel(text: string | null | undefined): string {\n return (text ?? '').replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * 在侧边栏里找到目标会话行。\n *\n * 只认官方组件使用的 CSS-module 类名(`sessionRow` / `searchResultRow`),\n * 不依赖 hash 前缀,因此跨版本稳定;找不到返回 undefined。\n */\nfunction findSessionRow(target: SessionTarget): HTMLElement | undefined {\n const rows = Array.from(\n document.querySelectorAll<HTMLElement>('[class*=\"sessionRow\"], [class*=\"searchResultRow\"]'),\n )\n if (rows.length === 0) return undefined\n\n const wanted = normalizeLabel(target.title)\n if (wanted !== '') {\n for (const row of rows) {\n const label = normalizeLabel(row.textContent)\n // 行内还含状态/时间等附属文本,故用「包含」而非全等。\n if (label === wanted || label.includes(wanted)) return row\n }\n }\n // 标题缺失或匹配不上:退化为匹配行上的 id 痕迹(部分版本会挂在属性里)。\n for (const row of rows) {\n const marker = row.getAttribute('data-session-id') ?? row.getAttribute('data-id')\n if (marker !== null && marker === target.sessionId) return row\n }\n return undefined\n}\n\n/**\n * 跳到指定会话。\n *\n * 成功与否取决于该会话此刻是否能在侧边栏定位到(例如所属工作区被收起时无法定位)。\n * @returns 是否成功发起跳转\n */\nexport function openSession(target: SessionTarget): boolean {\n const current = opener\n if (current === null) return false\n try {\n // 登记的实现可能是异步的(DOM 定位失败会重试一帧);这里不阻塞点击。\n void Promise.resolve(current(target)).catch(() => false)\n return true\n } catch {\n return false\n }\n}\n\n/** 供登记方使用的 DOM 定位能力(与 openSession 同源策略)。 */\nexport function resolveSessionRow(target: SessionTarget): HTMLElement | undefined {\n return findSessionRow(target)\n}\n","/**\n * dsh-session-attention 共享类型(host 与 client 都引用;纯类型 + 少量纯函数)。\n * 本包只管「DSH 会话」一件事:agent 完成一轮 / 提问 / 需要审批时提醒,\n * 外加一个只显示活跃会话的活动悬浮球。不含任何待办概念。\n * @module dsh-session-attention/types\n */\n\n/** 提醒音色(合成音用)与通知配色共用的样式。 */\nexport type ReminderStyle = 'info' | 'success' | 'warning' | 'danger'\n\nexport const REMINDER_STYLES: readonly ReminderStyle[] = ['info', 'success', 'warning', 'danger']\n\nexport function isReminderStyle(value: unknown): value is ReminderStyle {\n return typeof value === 'string' && (REMINDER_STYLES as readonly string[]).includes(value)\n}\n\n/** 夹紧数值到 [min,max];非有限数回退 fallback。 */\nexport function clampNumber(value: unknown, min: number, max: number, fallback: number): number {\n const n = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(n)) return fallback\n return Math.min(max, Math.max(min, n))\n}\n\n/** 「需要你操作」的提醒设置。 */\nexport interface AttentionSettings {\n /** agent 完成一轮回复时提醒。 */\n onTurnEnd: boolean\n /** agent 用 ask_user_question 提问时提醒。 */\n onQuestion: boolean\n /** 有审批请求(approve/deny)时提醒。 */\n onApproval: boolean\n /** 这类提醒是否响铃(页面内音效)。 */\n sound: boolean\n /** 音量 0..1。 */\n volume: number\n /** 合成音音色。 */\n tone: ReminderStyle\n}\n\n/** 活动悬浮球设置。 */\nexport interface BallSettings {\n /** 是否显示活动悬浮球。 */\n enabled: boolean\n /** 拖动松手后吸附到最近的左右边缘。 */\n snapToEdge: boolean\n}\n\n/** 本模块持久化的全部设置。 */\nexport interface AttentionModuleSettings {\n attention: AttentionSettings\n ball: BallSettings\n}\n\nexport const DEFAULT_SETTINGS: AttentionModuleSettings = {\n attention: { onTurnEnd: true, onQuestion: true, onApproval: true, sound: true, volume: 0.5, tone: 'success' },\n ball: { enabled: true, snapToEdge: true },\n}\n\n/** 一条「需要用户操作」的会话提醒(host → 浏览器 SSE)。 */\nexport interface AttentionEvent {\n kind: 'turn-end' | 'question' | 'approval'\n title: string\n body: string\n at: number\n /** 触发它的会话 id;点击提醒可直接跳回该会话。 */\n sessionId?: string\n /** 会话标题(通知里显示是哪个会话)。 */\n sessionTitle?: string\n}\n\n/** 单个会话的活动阶段(不在活动列表里 = 空闲)。 */\nexport type ActivityPhase = 'thinking' | 'tool' | 'done'\n\n/** 单个会话的活动快照(多会话追踪)。 */\nexport interface SessionActivity {\n sessionId: string\n sessionTitle?: string\n phase: ActivityPhase\n /** phase === 'tool' 时的当前工具名。 */\n tool?: string\n changedAt: number\n}\n\n/** host 返回给浏览器的完整状态。 */\nexport interface AttentionState {\n settings: AttentionModuleSettings\n now: number\n}\n\n/** SSE 推送信封。 */\nexport type PushMessage =\n | { type: 'state'; state: AttentionState }\n | { type: 'attention'; attention: AttentionEvent }\n | { type: 'activity'; activity: SessionActivity[] }\n","/**\n * DSH 会话活动悬浮球:浮在页面上的可拖动小球,只展示 DSH 当前活跃会话\n * (思考中 / 执行工具 / 等待你,最多 5 个、按最近更新排序);有活动时发光脉动。\n * 位置随窗口缩放自动夹回视口,松手可吸附边缘;Esc 或点击外部收起面板。\n *\n * 刻意**不提供画中画置顶小窗**:Chromium 的 Document PiP 窗口是操作系统级始终置顶,\n * 会干扰独占全屏游戏(LOL / 守望先锋等切全屏失败或被弹回窗口化)。球只活在 DSH 窗口内。\n * @module dsh-session-attention/client/ActivityBall\n */\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore, type CSSProperties, type PointerEvent as ReactPointerEvent, type ReactElement } from 'react'\nimport { attentionStore } from './attention-store.ts'\nimport { canOpenSession, openSession, sessionTitleOf } from './session-nav.ts'\nimport { DEFAULT_SETTINGS, type ActivityPhase, type SessionActivity } from '../types.ts'\n\nconst POS_KEY = 'dsh.todoReminder.ballPos'\nconst BALL_SIZE = 44\n/** 与 styles.ts `.todo-ball-panel` 的 width / max-height 保持一致,否则贴边时会算错位置。 */\nconst PANEL_WIDTH = 300\nconst PANEL_MAX_HEIGHT = 380\nconst EDGE = 8\nconst GAP = 12\n\ninterface BallPos { x: number, y: number }\n\nconst PHASE_META: Record<ActivityPhase, { icon: string, label: string }> = {\n thinking: { icon: '💭', label: '思考中' },\n tool: { icon: '⚙️', label: '执行中' },\n done: { icon: '🔔', label: '等待你' },\n}\n\nconst IDLE_META = { icon: '🌀', label: '空闲' }\n\n/** 汇总阶段:done > tool > thinking > idle。 */\nfunction summaryPhase(sessions: SessionActivity[]): ActivityPhase | 'idle' {\n if (sessions.length === 0) return 'idle'\n if (sessions.some((s) => s.phase === 'done')) return 'done'\n if (sessions.some((s) => s.phase === 'tool')) return 'tool'\n return 'thinking'\n}\n\nfunction sessionLabel(session: SessionActivity): string {\n // host 推来的标题在 DSH 0.2 里拿不到,回退问客户端会话列表(侧边栏同源)。\n const raw = session.sessionTitle !== undefined && session.sessionTitle !== ''\n ? session.sessionTitle\n : sessionTitleOf(session.sessionId)\n if (raw !== undefined && raw !== '') {\n return raw.length > 18 ? `${raw.slice(0, 18)}…` : raw\n }\n return `会话 ${session.sessionId.slice(0, 8)}`\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(min, max), Math.max(min, value))\n}\n\n/** 把位置夹回当前视口内。 */\nfunction clampPos(pos: BallPos): BallPos {\n return {\n x: clamp(pos.x, EDGE, window.innerWidth - BALL_SIZE - EDGE),\n y: clamp(pos.y, EDGE, window.innerHeight - BALL_SIZE - EDGE),\n }\n}\n\nfunction loadPos(): BallPos {\n try {\n const raw = localStorage.getItem(POS_KEY)\n if (raw !== null) {\n const parsed = JSON.parse(raw) as Partial<BallPos>\n if (typeof parsed.x === 'number' && typeof parsed.y === 'number') return clampPos({ x: parsed.x, y: parsed.y })\n }\n } catch { /* 忽略损坏的本地记录 */ }\n return { x: EDGE, y: window.innerHeight - 160 }\n}\n\nfunction savePos(pos: BallPos): void {\n try {\n localStorage.setItem(POS_KEY, JSON.stringify(pos))\n } catch { /* 隐私模式下写不进去,忽略 */ }\n}\n\nexport function ActivityBall(): ReactElement | null {\n const { activity, data } = useSyncExternalStore(attentionStore.subscribe, attentionStore.getSnapshot)\n const [expanded, setExpanded] = useState(false)\n const [pos, setPos] = useState<BallPos>(loadPos)\n const posRef = useRef(pos)\n const drag = useRef<{ startX: number, startY: number, moved: boolean } | null>(null)\n const ballRef = useRef<HTMLDivElement | null>(null)\n const panelRef = useRef<HTMLDivElement | null>(null)\n\n const ball = data?.settings.ball ?? DEFAULT_SETTINGS.ball\n const phase = summaryPhase(activity)\n const meta = phase === 'idle' ? IDLE_META : PHASE_META[phase]\n const count = activity.length\n\n const updatePos = useCallback((next: BallPos): void => {\n posRef.current = next\n setPos(next)\n }, [])\n\n // 窗口缩放后把球夹回视口:否则缩小窗口会把球永久留在视口外,再也点不到。\n useEffect(() => {\n const onResize = (): void => {\n const next = clampPos(posRef.current)\n if (next.x !== posRef.current.x || next.y !== posRef.current.y) {\n updatePos(next)\n savePos(next)\n } else {\n // 面板定位依赖视口尺寸,缩放后需要重算一次。\n setPos({ ...posRef.current })\n }\n }\n window.addEventListener('resize', onResize)\n return () => { window.removeEventListener('resize', onResize) }\n }, [updatePos])\n\n // Esc / 点击面板外部收起。\n useEffect(() => {\n if (!expanded) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') setExpanded(false)\n }\n const onPointerDown = (event: PointerEvent): void => {\n const target = event.target as Node | null\n if (target === null) return\n if (ballRef.current?.contains(target) === true) return\n if (panelRef.current?.contains(target) === true) return\n setExpanded(false)\n }\n document.addEventListener('keydown', onKeyDown)\n document.addEventListener('pointerdown', onPointerDown, true)\n return () => {\n document.removeEventListener('keydown', onKeyDown)\n document.removeEventListener('pointerdown', onPointerDown, true)\n }\n }, [expanded])\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>): boolean => {\n const state = drag.current\n drag.current = null\n try {\n event.currentTarget.releasePointerCapture(event.pointerId)\n } catch { /* 指针已释放,忽略 */ }\n return state !== null && state.moved\n }\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>): void => {\n drag.current = { startX: event.clientX, startY: event.clientY, moved: false }\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (drag.current === null) return\n const dx = event.clientX - drag.current.startX\n const dy = event.clientY - drag.current.startY\n if (Math.abs(dx) + Math.abs(dy) > 4) drag.current.moved = true\n if (drag.current.moved) {\n updatePos({ x: posRef.current.x + dx, y: posRef.current.y + dy })\n drag.current.startX = event.clientX\n drag.current.startY = event.clientY\n }\n }\n\n const onPointerUp = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (!endDrag(event)) {\n setExpanded((value) => !value)\n return\n }\n let next = clampPos(posRef.current)\n if (ball.snapToEdge) {\n const toLeft = next.x + BALL_SIZE / 2 < window.innerWidth / 2\n next = { ...next, x: toLeft ? EDGE : window.innerWidth - BALL_SIZE - EDGE }\n }\n updatePos(next)\n savePos(next)\n }\n\n // 指针被系统手势 / 触屏中断:必须复位,否则球会一直黏着指针。\n const onPointerCancel = (event: ReactPointerEvent<HTMLDivElement>): void => {\n endDrag(event)\n const next = clampPos(posRef.current)\n updatePos(next)\n savePos(next)\n }\n\n const panelStyle: CSSProperties = {\n left: pos.x - PANEL_WIDTH - GAP >= EDGE\n ? pos.x - PANEL_WIDTH - GAP\n : clamp(pos.x + BALL_SIZE + GAP, EDGE, window.innerWidth - PANEL_WIDTH - EDGE),\n top: clamp(pos.y, EDGE, window.innerHeight - PANEL_MAX_HEIGHT - EDGE),\n maxHeight: Math.min(PANEL_MAX_HEIGHT, window.innerHeight - EDGE * 2),\n }\n\n /**\n * 点一条会话就跳进去;桥缺席时降级为纯展示。\n * 标题一并传下去:0.1.6 的跳转是在侧边栏按标题定位该会话行再委托点击。\n */\n const goToSession = (session: SessionActivity): void => {\n if (openSession({ sessionId: session.sessionId, title: session.sessionTitle })) setExpanded(false)\n }\n\n const renderSession = (session: SessionActivity): ReactElement => {\n const sessionMeta = PHASE_META[session.phase]\n const navigable = canOpenSession()\n const task = session.phase === 'tool' && session.tool !== undefined ? session.tool : sessionMeta.label\n return (\n <div\n className=\"todo-ball-row\"\n key={session.sessionId}\n data-navigable={navigable ? 'true' : undefined}\n role={navigable ? 'button' : undefined}\n tabIndex={navigable ? 0 : undefined}\n title={navigable ? `${sessionLabel(session)} · ${task}\\n点击进入这个会话` : `${sessionLabel(session)} · ${task}`}\n onClick={navigable ? () => goToSession(session) : undefined}\n onKeyDown={navigable\n ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n goToSession(session)\n }\n }\n : undefined}\n >\n <span className=\"todo-ball-row-icon\">{sessionMeta.icon}</span>\n <span className=\"todo-ball-row-title\">{sessionLabel(session)}</span>\n <span className=\"todo-ball-row-state\">{task}</span>\n {navigable ? <span className=\"todo-ball-row-go\" aria-hidden=\"true\">›</span> : null}\n </div>\n )\n }\n\n const tooltip = count > 0\n ? `DSH:${activity.map((s) => `${PHASE_META[s.phase].label}·${sessionLabel(s)}`).join(';')}`\n : 'DSH:空闲'\n\n // 所有 hook 之上不做条件返回:关闭悬浮球在这里才短路。\n if (!ball.enabled) return null\n\n return (\n <>\n <div\n ref={ballRef}\n className=\"todo-ball\"\n role=\"button\"\n tabIndex={0}\n aria-label={tooltip}\n aria-expanded={expanded}\n data-phase={phase}\n data-active={phase === 'idle' ? undefined : 'true'}\n style={{ left: pos.x, top: pos.y }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerCancel}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n setExpanded((value) => !value)\n }\n }}\n title={tooltip}\n >\n <span className=\"todo-ball-icon\">{meta.icon}</span>\n {count > 0 ? <span className=\"todo-ball-badge\">{count}</span> : null}\n </div>\n\n {expanded ? (\n <div className=\"todo-ball-panel\" ref={panelRef} style={panelStyle}>\n <div className=\"todo-ball-panel-head\">\n <h3 className=\"todo-ball-panel-title\">DSH 活跃{count > 0 ? ` (${count})` : ''}</h3>\n <button type=\"button\" className=\"todo-btn\" aria-label=\"关闭\" onClick={() => setExpanded(false)}>✕</button>\n </div>\n {count === 0 ? <div className=\"todo-ball-empty\">暂无进行中的对话</div> : activity.map(renderSession)}\n </div>\n ) : null}\n </>\n )\n}\n","/**\n * 会话提醒音引擎:在页面内播放提醒音,标签页在前台或后台都一致出声。\n *\n * 优先播放自定义音效文件 —— $DSH_HOME/session-attention/session.{mp3,wav,ogg,m4a},\n * 旧位置 $DSH_HOME/todo-reminder/session.* 仍然有效(拆分前的文件不用搬家);\n * 都没有时用 WebAudio 现场合成,因此零配置也有声。\n *\n * 浏览器自动播放策略要求先有用户手势才允许出声,所以这里在 document 上挂手势监听\n * 自动解锁:用户在 DSH 里点任何地方即完成解锁,不需要专门操作。\n * @module dsh-session-attention/client/sound\n */\n\nimport type { ReminderStyle } from '../types.ts'\n\n/** 每种音色的合成序列:[频率 Hz, 相对起始秒, 时长秒]。 */\nconst TONES: Record<ReminderStyle, ReadonlyArray<readonly [number, number, number]>> = {\n info: [[880, 0, 0.12], [1174.7, 0.13, 0.18]],\n success: [[659.3, 0, 0.1], [880, 0.1, 0.1], [1318.5, 0.2, 0.24]],\n warning: [[740, 0, 0.16], [587.3, 0.19, 0.26]],\n danger: [[932.3, 0, 0.1], [698.5, 0.12, 0.1], [932.3, 0.24, 0.1], [698.5, 0.36, 0.2]],\n}\n\n/** 自定义音效端点(host 侧先找本模块目录,再回退旧插件目录)。 */\nconst SOUND_URL = '/api/attention/sound'\n\n/** 两次重复播放之间的间隔(秒)。 */\nconst REPEAT_GAP = 0.14\n\ntype AudioContextCtor = new () => AudioContext\n\nlet audioContext: AudioContext | null = null\nlet customBuffer: AudioBuffer | null = null\nlet probe: Promise<void> | null = null\nlet unlocked = false\nlet gestureBound = false\n\nfunction contextCtor(): AudioContextCtor | undefined {\n const w = window as unknown as { AudioContext?: AudioContextCtor, webkitAudioContext?: AudioContextCtor }\n return w.AudioContext ?? w.webkitAudioContext\n}\n\nfunction ensureContext(): AudioContext | null {\n if (audioContext !== null) return audioContext\n const Ctor = contextCtor()\n if (Ctor === undefined) return null\n try {\n audioContext = new Ctor()\n } catch {\n audioContext = null\n }\n return audioContext\n}\n\n/** 探测自定义音效文件;404 / 解码失败则继续用合成音。 */\nasync function probeSound(): Promise<void> {\n if (probe !== null) return probe\n const ctx = ensureContext()\n if (ctx === null) return\n probe = (async () => {\n try {\n const response = await fetch(SOUND_URL, { cache: 'no-store' })\n if (!response.ok) return\n customBuffer = await ctx.decodeAudioData(await response.arrayBuffer())\n } catch {\n customBuffer = null\n }\n })()\n return probe\n}\n\nfunction unlock(): void {\n const ctx = ensureContext()\n if (ctx === null) return\n if (ctx.state === 'suspended') void ctx.resume().catch(() => { /* 用户尚未授权,下次手势再试 */ })\n unlocked = true\n void probeSound()\n}\n\n/**\n * 挂手势监听自动解锁音频(浏览器自动播放策略)。\n * @returns 解除监听的 disposer。\n */\nexport function armSoundUnlock(): () => void {\n if (gestureBound) return () => { /* 已挂载,交给首个调用方清理 */ }\n gestureBound = true\n const events: ReadonlyArray<keyof DocumentEventMap> = ['pointerdown', 'keydown', 'touchstart']\n const onGesture = (): void => { unlock() }\n for (const event of events) document.addEventListener(event, onGesture, { capture: true, passive: true })\n return () => {\n for (const event of events) document.removeEventListener(event, onGesture, { capture: true })\n gestureBound = false\n }\n}\n\n/** 调度一段合成音;返回这段音的总时长(秒)。 */\nfunction scheduleSynth(ctx: AudioContext, tone: ReminderStyle, volume: number, startAt: number): number {\n let span = 0\n for (const [frequency, offset, duration] of TONES[tone]) {\n const osc = ctx.createOscillator()\n const gain = ctx.createGain()\n osc.type = 'sine'\n osc.frequency.value = frequency\n const t0 = startAt + offset\n const t1 = t0 + duration\n // 指数包络:避免起停爆音(gain 不能取 0,故用极小值)。\n gain.gain.setValueAtTime(0.0001, t0)\n gain.gain.exponentialRampToValueAtTime(Math.max(0.0002, volume), t0 + 0.012)\n gain.gain.exponentialRampToValueAtTime(0.0001, t1)\n osc.connect(gain).connect(ctx.destination)\n osc.start(t0)\n osc.stop(t1 + 0.02)\n span = Math.max(span, offset + duration)\n }\n return span\n}\n\n/** 调度一次自定义音效播放;返回时长(秒)。 */\nfunction scheduleBuffer(ctx: AudioContext, buffer: AudioBuffer, volume: number, startAt: number): number {\n const source = ctx.createBufferSource()\n const gain = ctx.createGain()\n source.buffer = buffer\n gain.gain.value = volume\n source.connect(gain).connect(ctx.destination)\n source.start(startAt)\n return buffer.duration\n}\n\n/**\n * 播放一次会话提醒音。\n * @param tone - 音色(合成音时生效)。\n * @param volume - 音量 0..1。\n * @param repeat - 连响次数(1..5)。\n * @returns 是否已成功调度(音频未解锁 / 不支持时返回 false)。\n */\nexport function playSound(tone: ReminderStyle, volume: number, repeat = 1): boolean {\n const ctx = ensureContext()\n if (ctx === null) return false\n if (ctx.state === 'suspended') void ctx.resume().catch(() => { /* 未解锁,静默失败 */ })\n if (!unlocked && ctx.state !== 'running') return false\n const vol = Math.min(1, Math.max(0, volume))\n if (vol === 0) return false\n const times = Math.min(5, Math.max(1, Math.round(repeat)))\n const buffer = customBuffer\n let cursor = ctx.currentTime + 0.02\n for (let i = 0; i < times; i += 1) {\n const span = buffer !== null\n ? scheduleBuffer(ctx, buffer, vol, cursor)\n : scheduleSynth(ctx, tone, vol, cursor)\n cursor += span + REPEAT_GAP\n }\n return true\n}\n\n/** 试听:立即解锁(本次调用来自用户点击,必定允许出声)并播放一次。 */\nexport function previewSound(tone: ReminderStyle, volume: number, repeat = 1): void {\n unlock()\n // 自定义音效可能仍在探测中:探测完再播,保证试听到的就是实际提醒音。\n void probeSound().then(() => {\n playSound(tone, volume, repeat)\n })\n}\n\n/** 音频是否已解锁可用;未就绪时调用方应让系统通知自己出声兜底。 */\nexport function isSoundReady(): boolean {\n return unlocked\n}\n\n/** 当前音源说明,用于设置面板提示。 */\nexport function soundSourceLabel(): string {\n if (!unlocked) return '未解锁(在页面里点一下即可)'\n return customBuffer !== null ? '自定义音效文件' : '内置合成音'\n}\n","/**\n * 提醒层:body 上的全局 React 根。经 SSE 订阅 host 的会话提醒与活动快照;\n * 标签页切到后台时,把「需要你操作」的提醒转成系统原生通知 + 提醒音。\n *\n * 只在标签页**不可见**时触发 —— 你正盯着屏幕时不必每轮都被打扰。这里不做\n * 页面内 toast:真在看你不需要它,切走了你也看不见它。\n * @module dsh-session-attention/client/AttentionLayer\n */\n\nimport { useEffect, type ReactElement } from 'react'\nimport { ActivityBall } from './ActivityBall.tsx'\nimport { attentionStore } from './attention-store.ts'\nimport { openSession } from './session-nav.ts'\nimport { armSoundUnlock, playSound } from './sound.ts'\nimport { DEFAULT_SETTINGS, type AttentionEvent, type AttentionState, type SessionActivity } from '../types.ts'\n\n/**\n * 一条「需要你操作」提醒 → 提醒音 + 系统原生通知。\n * 开启提醒音后系统通知静音,由音效引擎单独出声,避免「系统叮 + 自定义音效」响两遍;\n * 音频未解锁时交回系统提示音兜底。\n */\nfunction fireAttention(event: AttentionEvent): void {\n const attention = (attentionStore.getSnapshot().data?.settings ?? DEFAULT_SETTINGS).attention\n const played = attention.sound ? playSound(attention.tone, attention.volume, 1) : false\n if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return\n const body = event.sessionTitle !== undefined && event.sessionTitle !== ''\n ? `${event.sessionTitle} —— ${event.body}`\n : event.body\n try {\n const notification = new Notification(event.title, {\n body,\n tag: `dsh-attention-${event.kind}-${event.at}`,\n silent: played ? true : !attention.sound,\n })\n notification.onclick = (): void => {\n window.focus()\n // 点通知直接跳到触发它的那个会话(官方 uiWorkspace 接口,见 session-nav)。\n if (event.sessionId !== undefined) openSession({ sessionId: event.sessionId, title: event.sessionTitle })\n notification.close()\n }\n } catch {\n // 个别环境不支持,忽略。\n }\n}\n\n/** SSE 消息信封(与 host 的 PushMessage 对应)。 */\ninterface PushEnvelope {\n type?: string\n state?: AttentionState\n attention?: AttentionEvent\n activity?: SessionActivity[]\n}\n\nexport function AttentionLayer(): ReactElement {\n // 音频解锁:浏览器要求先有用户手势才允许出声,这里在首个手势时自动完成。\n useEffect(() => armSoundUnlock(), [])\n\n // SSE 实时订阅 + 回到前台时兜底轮询。\n useEffect(() => {\n const source = new EventSource('/api/attention/events')\n source.onmessage = (message) => {\n try {\n const envelope = JSON.parse(message.data) as PushEnvelope\n if (envelope.type === 'state' && envelope.state !== undefined) {\n attentionStore.applyState(envelope.state)\n } else if (envelope.type === 'attention' && envelope.attention !== undefined) {\n attentionStore.applyAttention(envelope.attention)\n if (document.visibilityState === 'hidden') fireAttention(envelope.attention)\n } else if (envelope.type === 'activity' && envelope.activity !== undefined) {\n attentionStore.applyActivity(envelope.activity)\n }\n } catch {\n // 解析失败忽略,等待下一条或轮询兜底。\n }\n }\n const onVisibility = (): void => {\n if (document.visibilityState === 'visible') void attentionStore.refresh()\n }\n document.addEventListener('visibilitychange', onVisibility)\n return () => {\n source.close()\n document.removeEventListener('visibilitychange', onVisibility)\n }\n }, [])\n\n // 首次请求系统通知权限(用户点「允许」后,切出也能收到通知)。\n useEffect(() => {\n if (typeof Notification === 'undefined') return\n if (Notification.permission === 'default') {\n Notification.requestPermission().catch(() => { /* 拒绝静默 */ })\n }\n }, [])\n\n return <ActivityBall />\n}\n","/**\n * 客户端样式:一段 CSS 字符串(apply 时注入 <style>),配色走 dsh 的\n * --dsw-alias-* token,自动适配明暗主题与皮肤。数据属性作用域隔离,不污染 shell。\n * @module dsh-session-attention/client/styles\n */\n\nimport type { ReminderStyle } from '../types.ts'\n\nexport const STYLE_META: Record<ReminderStyle, { label: string; icon: string }> = {\n info: { label: '提醒', icon: 'ℹ️' },\n success: { label: '纪念', icon: '✅' },\n warning: { label: '注意', icon: '⚠️' },\n danger: { label: '紧急', icon: '🚨' },\n}\n\nexport const ATTENTION_CSS = `\n/* --- 面板接管中心列(独立页面式,与任务看板 / SSH 一致) ------------------ */\n/* DSH 0.1.6 移除了 data-pane 标记,中心列改用 CSS-module 类名(centerCol);\n 这里同时兼容旧标记与新类名,否则容器定位会失效。 */\n/* 只给自己挂载的宿主定位:全局匹配 centerCol 会连带影响其他插件的容器。 */\n[data-dsh-att-host] { position: relative; }\n\n[data-dsh-att-view] {\n position: absolute;\n inset: 0;\n display: none;\n z-index: 60;\n overflow: auto;\n background: var(--dsw-alias-bg-base);\n}\nhtml[data-dsh-attention-active] [data-dsh-att-view] { display: block; }\nhtml[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view]) {\n display: none !important;\n}\n/* --- 侧边栏入口 ---------------------------------------------------------- */\n[data-dsh-att-entry] {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n height: 32px;\n padding: 0 12px;\n background: transparent;\n border: none;\n border-radius: 8px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n font-family: var(--dsw-font-family);\n}\n[data-dsh-att-entry]:hover {\n background: var(--dsw-specific-sidebar-nav-item-hover);\n color: var(--dsw-alias-label-primary);\n}\n[data-dsh-att-entry][data-active] {\n background: var(--dsw-specific-sidebar-nav-item-active);\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n}\n[data-dsh-att-entry] .todo-entry-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; }\n[data-dsh-att-entry] .todo-entry-label { overflow: hidden; text-overflow: ellipsis; }\n/* 侧边栏收起态:0.1.6 把折叠标记从 [data-dsh-frame] 挪到了 [data-sidebar-collapsed],\n 两种祖先都匹配,避免收起后入口标签溢出。 */\n[data-dsh-frame][data-sidebar-collapsed] [data-dsh-att-entry],\n[data-sidebar-collapsed] [data-dsh-att-entry] { justify-content: center; padding: 0; }\n[data-dsh-frame][data-sidebar-collapsed] [data-dsh-att-entry] .todo-entry-label,\n[data-sidebar-collapsed] [data-dsh-att-entry] .todo-entry-label { display: none; }\n\n/* --- 面板主体 ------------------------------------------------------------ */\n.todo-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n min-height: 0;\n padding: 14px 16px 16px;\n gap: 12px;\n background: var(--dsw-alias-bg-base);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n}\n.todo-header { display: flex; align-items: center; gap: 10px; flex: none; }\n.todo-title { margin: 0; font-size: 16px; font-weight: 700; color: var(--dsw-alias-label-primary); white-space: nowrap; }\n.todo-count { flex: none; font-size: 12px; color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-interactive-bg-hover); border-radius: 999px; padding: 1px 8px; }\n\n.todo-form { display: flex; flex-direction: column; gap: 8px; flex: none; }\n.todo-form-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n.todo-input { flex: 1; min-width: 160px; padding: 7px 10px; font-size: 13px; color: var(--dsw-alias-label-primary); background: var(--dsw-specific-input-major); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; outline: none; font-family: inherit; }\n.todo-input:focus { border-color: var(--dsw-alias-state-business-primary); }\n.todo-input::placeholder { color: var(--dsw-alias-label-tertiary); }\n.todo-select { padding: 7px 8px; font-size: 12.5px; color: var(--dsw-alias-label-primary); background: var(--dsw-specific-input-major); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; outline: none; }\n.todo-select:focus { border-color: var(--dsw-alias-state-business-primary); }\n.todo-check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--dsw-alias-label-primary); cursor: pointer; user-select: none; }\n.todo-check input { accent-color: var(--dsw-alias-state-business-primary); margin: 0; }\n\n/* --- 设置行(响铃 / 稍后提醒分钟数) ------------------------------------ */\n.todo-settings { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: none; padding: 8px 10px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; }\n.todo-settings-label { font-size: 12px; font-weight: 700; color: var(--dsw-alias-label-tertiary); }\n.todo-settings-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n\n.todo-list { display: flex; flex-direction: column; gap: 8px; flex: 1; min-height: 0; overflow-y: auto; }\n.todo-section-title { margin: 4px 0 0; font-size: 11px; font-weight: 700; color: var(--dsw-alias-label-tertiary); text-transform: none; letter-spacing: .02em; }\n.todo-empty { padding: 24px 8px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n\n.todo-item {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 12px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n}\n.todo-item:hover { border-color: var(--dsw-alias-border-l3); }\n.todo-item[data-done] { opacity: .6; }\n.todo-item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }\n.todo-item-title { font-size: 13px; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }\n.todo-item[data-done] .todo-item-title { text-decoration: line-through; }\n.todo-item-note { font-size: 12px; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; white-space: pre-wrap; }\n.todo-item-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; color: var(--dsw-alias-label-tertiary); }\n.todo-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px; font-size: 11px; border: 1px solid var(--todo-accent, var(--dsw-alias-border-l2)); color: var(--todo-accent, var(--dsw-alias-label-secondary)); background: transparent; }\n.todo-badge[data-style='info'] { --todo-accent: var(--dsw-alias-state-business-primary); }\n.todo-badge[data-style='success'] { --todo-accent: var(--dsw-alias-state-success-primary); }\n.todo-badge[data-style='warning'] { --todo-accent: var(--dsw-alias-state-warn-primary); }\n.todo-badge[data-style='danger'] { --todo-accent: var(--dsw-alias-state-error-primary); }\n\n.todo-actions { display: flex; align-items: center; gap: 4px; flex: none; }\n.todo-btn {\n display: inline-flex; align-items: center; justify-content: center;\n height: 26px; padding: 0 10px;\n background: transparent; border: none; border-radius: 6px;\n color: var(--dsw-alias-label-secondary); cursor: pointer; font-size: 12px; white-space: nowrap;\n}\n.todo-btn:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.todo-btn.primary { color: #fff; background: var(--dsw-alias-button-info-fill); padding: 0 12px; font-weight: 600; }\n.todo-btn.primary:hover { background: var(--dsw-alias-button-info-hover); }\n.todo-btn.danger:hover { color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-state-error-secondary); }\n.todo-btn[disabled] { opacity: .5; cursor: default; }\n\n/* --- 提醒 toast ---------------------------------------------------------- */\n.todo-toast-stack {\n position: fixed;\n top: 16px;\n right: 16px;\n z-index: 1400;\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: min(360px, calc(100vw - 32px));\n pointer-events: none;\n}\n.todo-toast {\n pointer-events: auto;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--todo-accent, var(--dsw-alias-border-l2));\n border-left-width: 4px;\n border-radius: 10px;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n animation: todo-toast-in 220ms ease;\n}\n.todo-toast[data-style='info'] { --todo-accent: var(--dsw-alias-state-business-primary); }\n.todo-toast[data-style='success'] { --todo-accent: var(--dsw-alias-state-success-primary); }\n.todo-toast[data-style='warning'] { --todo-accent: var(--dsw-alias-state-warn-primary); }\n.todo-toast[data-style='danger'] { --todo-accent: var(--dsw-alias-state-error-primary); animation: todo-toast-in 220ms ease, todo-toast-pulse 1.6s ease-in-out infinite; }\n.todo-toast-head { display: flex; align-items: center; gap: 8px; }\n.todo-toast-icon { font-size: 16px; line-height: 1; }\n.todo-toast-title { flex: 1; min-width: 0; font-size: 13px; font-weight: 700; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n.todo-toast-sub { font-size: 11px; color: var(--todo-accent, var(--dsw-alias-label-tertiary)); }\n.todo-toast-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n@keyframes todo-toast-in { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: none; } }\n@keyframes todo-toast-pulse { 0%,100% { box-shadow: var(--dsw-shadow-lv3); } 50% { box-shadow: 0 0 0 3px var(--todo-accent, transparent); } }\n\n/* --- 确认弹窗 ------------------------------------------------------------ */\n.todo-modal-backdrop {\n position: fixed; inset: 0; z-index: 1500;\n display: flex; align-items: center; justify-content: center;\n background: var(--dsw-alias-bg-mask-1);\n}\n.todo-modal {\n display: flex; flex-direction: column; gap: 12px;\n width: min(440px, calc(100vw - 48px));\n padding: 18px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 14px;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n}\n.todo-modal-title { margin: 0; font-size: 15px; font-weight: 700; }\n.todo-modal-message { margin: 0; font-size: 13px; line-height: 1.5; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }\n.todo-modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }\n\n/* --- DSH 会话活动悬浮球(独立模块,不含待办内容) ------------------------ */\n.todo-ball {\n position: fixed;\n z-index: 1340;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n box-shadow: var(--dsw-shadow-lv2);\n cursor: grab;\n user-select: none;\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n touch-action: none;\n --ball-accent: var(--dsw-alias-label-tertiary);\n}\n.todo-ball:active { cursor: grabbing; }\n.todo-ball-icon { font-size: 18px; line-height: 1; pointer-events: none; }\n.todo-ball-badge {\n position: absolute;\n top: -4px; right: -4px;\n min-width: 16px; height: 16px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--dsw-alias-state-business-primary);\n color: #fff;\n font-size: 10px;\n line-height: 16px;\n text-align: center;\n font-weight: 700;\n pointer-events: none;\n}\n.todo-ball[data-phase='thinking'] { --ball-accent: var(--dsw-alias-state-business-primary); }\n.todo-ball[data-phase='tool'] { --ball-accent: var(--dsw-alias-state-warn-primary); }\n.todo-ball[data-phase='done'] { --ball-accent: var(--dsw-alias-state-success-primary); }\n.todo-ball[data-active] {\n border-color: var(--ball-accent);\n animation: todo-ball-glow 1.6s ease-in-out infinite;\n}\n@keyframes todo-ball-glow {\n 0%, 100% { box-shadow: var(--dsw-shadow-lv2); }\n 50% { box-shadow: 0 0 0 4px var(--ball-accent); }\n}\n.todo-ball-panel {\n position: fixed;\n z-index: 1340;\n width: 300px;\n max-height: 380px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n overflow-y: auto;\n /* 这是个控件面板而非文档:默认箭头指针,避免鼠标移到文字上变成输入光标。 */\n cursor: default;\n user-select: none;\n}\n.todo-ball-panel-head { display: flex; align-items: center; gap: 8px; }\n.todo-ball-panel-title { margin: 0; font-size: 14px; font-weight: 700; flex: 1; }\n.todo-ball-row { display: flex; align-items: center; gap: 6px; padding: 6px 0; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.todo-ball-row:last-child { border-bottom: none; }\n.todo-ball-row-icon { flex: none; font-size: 13px; }\n.todo-ball-row-title { flex: 1; min-width: 0; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-ball-row-state { flex: none; font-size: 11px; color: var(--dsw-alias-label-tertiary); max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-ball-row[data-navigable] {\n margin: 0 -6px;\n padding: 6px;\n border-radius: 7px;\n cursor: pointer;\n}\n.todo-ball-row[data-navigable]:hover { background: var(--dsw-alias-fill-secondary); }\n.todo-ball-row[data-navigable]:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: -2px; }\n.todo-ball-row-go { flex: none; font-size: 14px; color: var(--dsw-alias-label-tertiary); }\n.todo-ball-empty { padding: 16px 4px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n.todo-ball-actions { display: flex; gap: 6px; flex-wrap: wrap; padding-top: 4px; }\n.todo-ball-mini { flex: none; padding: 2px 8px; font-size: 11px; }\n\n/* --- 个人待办 Dock(右侧边缘吸附,可收起) ------------------------------- */\n.todo-dock {\n position: fixed;\n right: 0;\n z-index: 1350;\n display: flex;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n border-right: none;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n touch-action: none;\n}\n/* 收起态:贴边药丸,直接显示下一件要做的事。 */\n.todo-dock[data-collapsed='true'] {\n flex-direction: row;\n align-items: center;\n gap: 7px;\n max-width: 268px;\n padding: 7px 10px 7px 13px;\n border-radius: 999px 0 0 999px;\n box-shadow: var(--dsw-shadow-lv2);\n cursor: pointer;\n user-select: none;\n}\n.todo-dock[data-collapsed='true']:hover { background: var(--dsw-alias-fill-secondary); }\n/* 展开态:右侧待办面板。 */\n.todo-dock[data-collapsed='false'] {\n flex-direction: column;\n gap: 8px;\n width: 320px;\n padding: 12px;\n border-radius: 12px 0 0 12px;\n /* 同上:控件面板用箭头指针,文字不做选中。 */\n cursor: default;\n user-select: none;\n}\n/* 表单控件恢复自身的输入光标与可选中,不受面板的 user-select: none 影响。 */\n.todo-dock input, .todo-dock select, .todo-dock textarea { cursor: auto; user-select: auto; }\n.todo-dock[data-overdue='true'] { border-color: var(--dsw-alias-state-error-primary); }\n.todo-dock[data-collapsed='true'][data-overdue='true'] { animation: todo-dock-pulse 1.8s ease-in-out infinite; }\n@keyframes todo-dock-pulse {\n 0%, 100% { box-shadow: var(--dsw-shadow-lv2); }\n 50% { box-shadow: 0 0 0 4px var(--dsw-alias-state-error-primary); }\n}\n\n.todo-dock-icon { flex: none; font-size: 14px; line-height: 1; }\n.todo-dock-time { flex: none; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }\n.todo-dock-time[data-overdue='true'] { color: var(--dsw-alias-state-error-primary); }\n.todo-dock-next { flex: 1; min-width: 0; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-dock-idle { flex: none; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n\n.todo-dock-head { display: flex; align-items: center; gap: 8px; cursor: grab; user-select: none; }\n.todo-dock-head:active { cursor: grabbing; }\n.todo-dock-title { margin: 0; flex: 1; font-size: 14px; font-weight: 700; }\n.todo-dock-add { display: flex; gap: 6px; }\n.todo-dock-add .todo-input { flex: 1; min-width: 0; }\n.todo-dock-when { flex: none; width: 96px; }\n.todo-dock-body { display: flex; flex-direction: column; overflow-y: auto; max-height: 46vh; }\n.todo-dock-section { padding: 6px 2px 2px; font-size: 11px; font-weight: 700; letter-spacing: .02em; color: var(--dsw-alias-label-tertiary); }\n.todo-dock-row { display: flex; align-items: center; gap: 7px; padding: 6px 2px; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.todo-dock-row:last-child { border-bottom: none; }\n.todo-dock-check {\n flex: none;\n width: 17px; height: 17px;\n padding: 0;\n border: 1.5px solid var(--dsw-alias-border-l3);\n border-radius: 50%;\n background: transparent;\n color: transparent;\n font-size: 10px;\n line-height: 1;\n cursor: pointer;\n}\n.todo-dock-check:hover { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }\n.todo-dock-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }\n.todo-dock-row-title { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-dock-row-due { font-size: 11px; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }\n.todo-dock-row[data-overdue='true'] .todo-dock-row-due { color: var(--dsw-alias-state-error-primary); font-weight: 700; }\n.todo-dock-row-actions { flex: none; display: flex; gap: 4px; opacity: 0; transition: opacity .12s ease; }\n.todo-dock-row:hover .todo-dock-row-actions, .todo-dock-row:focus-within .todo-dock-row-actions { opacity: 1; }\n.todo-dock-mini { flex: none; padding: 2px 7px; font-size: 11px; }\n.todo-dock-empty { padding: 14px 4px; text-align: center; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary); }\n.todo-dock-foot { display: flex; gap: 6px; flex-wrap: wrap; }\n.todo-settings-group {\n padding: 10px 2px 0;\n font-size: 12px;\n font-weight: 700;\n color: var(--dsw-alias-label-secondary);\n}\n.todo-range { flex: none; width: 96px; accent-color: var(--dsw-alias-state-business-primary); cursor: pointer; }\n\n@media (prefers-reduced-motion: reduce) {\n .todo-toast { animation: none; }\n .todo-toast[data-style='danger'] { animation: none; }\n .todo-dock[data-collapsed='true'][data-overdue='true'] { animation: none; }\n .todo-ball[data-active] { animation: none; }\n}\n`\n\n/** 注入样式(幂等)。 */\nexport function injectStyles(): void {\n if (document.querySelector('style[data-dsh-att-css]') !== null) return\n const style = document.createElement('style')\n style.dataset.dshAttCss = ''\n style.textContent = ATTENTION_CSS\n document.head.appendChild(style)\n}\n","/**\n * 设置面板:挂在中心列(会话列)里,由侧边栏「提醒」入口开合。\n * 只管本模块 —— 三类提醒开关、提醒音(音量 / 音色 / 试听 / 音源)、活动悬浮球,\n * 外加系统通知权限状态与「最近一条提醒 / 活跃会话」的只读摘要。\n * @module dsh-session-attention/client/AttentionPanel\n */\n\nimport { useState, useSyncExternalStore, type ReactElement } from 'react'\nimport { attentionStore } from './attention-store.ts'\nimport { STYLE_META } from './styles.ts'\nimport { previewSound, soundSourceLabel } from './sound.ts'\nimport { DEFAULT_SETTINGS, REMINDER_STYLES, type ReminderStyle } from '../types.ts'\n\nconst PHASE_LABEL: Record<string, string> = { thinking: '思考中', tool: '执行工具', done: '等待你' }\n\nfunction formatTime(ms: number): string {\n const d = new Date(ms)\n const pad = (n: number): string => String(n).padStart(2, '0')\n return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`\n}\n\nexport function AttentionPanel(): ReactElement {\n const { data, error, activity, latest } = useSyncExternalStore(attentionStore.subscribe, attentionStore.getSnapshot)\n const settings = data?.settings ?? DEFAULT_SETTINGS\n const { attention, ball } = settings\n\n // 滑杆用本地态拖动、松手才落盘,避免每移动一格都打一次接口。\n const [volume, setVolume] = useState<number | null>(null)\n const shownVolume = volume ?? Math.round(attention.volume * 100)\n const commitVolume = (): void => {\n if (volume === null) return\n void attentionStore.setAttention({ volume: volume / 100 })\n setVolume(null)\n }\n\n const [permission, setPermission] = useState<string>(\n typeof Notification === 'undefined' ? 'unsupported' : Notification.permission,\n )\n const requestPermission = (): void => {\n if (typeof Notification === 'undefined') return\n void Notification.requestPermission().then((value) => setPermission(value)).catch(() => { /* 拒绝静默 */ })\n }\n\n const [sourceLabel, setSourceLabel] = useState<string>(soundSourceLabel())\n const refreshSource = (): void => { setSourceLabel(soundSourceLabel()) }\n\n return (\n <div className=\"todo-panel\">\n <div className=\"todo-header\">\n <span className=\"todo-title\">🔔 DSH 会话提醒</span>\n <span className=\"todo-count\">{activity.length > 0 ? `${activity.length} 个活跃会话` : '无活跃会话'}</span>\n <button\n type=\"button\"\n className=\"todo-btn\"\n title=\"关闭面板(Esc 也可以)\"\n aria-label=\"关闭\"\n onClick={() => attentionStore.setPanel(false)}\n >\n ✕\n </button>\n </div>\n\n {error !== null ? <div className=\"todo-empty\">连接 host 失败:{error}</div> : null}\n\n <div className=\"todo-settings-group\">💬 什么时候提醒你(只在切出 DSH 时弹)</div>\n <div className=\"todo-settings\">\n <label className=\"todo-check\" title=\"agent 完成一轮回复时提醒\">\n <input type=\"checkbox\" checked={attention.onTurnEnd} onChange={(e) => void attentionStore.setAttention({ onTurnEnd: e.target.checked })} />\n 完成一轮\n </label>\n <label className=\"todo-check\" title=\"agent 用 ask_user_question 提问时提醒\">\n <input type=\"checkbox\" checked={attention.onQuestion} onChange={(e) => void attentionStore.setAttention({ onQuestion: e.target.checked })} />\n 提问\n </label>\n <label className=\"todo-check\" title=\"有审批请求(approve/deny)时提醒\">\n <input type=\"checkbox\" checked={attention.onApproval} onChange={(e) => void attentionStore.setAttention({ onApproval: e.target.checked })} />\n 审批\n </label>\n </div>\n\n <div className=\"todo-settings-group\">🔊 提醒音</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">响铃</span>\n <label className=\"todo-check\" title=\"关掉后只有系统通知,没有页面内音效\">\n <input type=\"checkbox\" checked={attention.sound} onChange={(e) => void attentionStore.setAttention({ sound: e.target.checked })} />\n 出声\n </label>\n <span className=\"todo-settings-hint\">音量</span>\n <input\n className=\"todo-range\"\n type=\"range\"\n min={0}\n max={100}\n step={5}\n value={shownVolume}\n onChange={(e) => setVolume(Number(e.target.value))}\n onPointerUp={commitVolume}\n onKeyUp={commitVolume}\n onBlur={commitVolume}\n title=\"会话提醒音量\"\n />\n <span className=\"todo-settings-hint\">{shownVolume}%</span>\n <span className=\"todo-settings-hint\">音色</span>\n <select\n className=\"todo-select\"\n value={attention.tone}\n onChange={(e) => void attentionStore.setAttention({ tone: e.target.value as ReminderStyle })}\n title=\"用哪种音色\"\n >\n {REMINDER_STYLES.map((style) => (\n <option key={style} value={style}>{STYLE_META[style].icon} {STYLE_META[style].label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n className=\"todo-btn\"\n onClick={() => { previewSound(attention.tone, shownVolume / 100, 1); window.setTimeout(refreshSource, 400) }}\n title=\"试听提醒音\"\n >\n 🔈 试听\n </button>\n <button\n type=\"button\"\n className=\"todo-btn\"\n title=\"在文件管理器里打开音效文件所在目录\"\n onClick={() => void attentionStore.openSoundDir()}\n >\n 📂 打开文件夹\n </button>\n <span className=\"todo-settings-hint\">音源 {sourceLabel}</span>\n <span className=\"todo-settings-hint\">自定义音效:$DSH_HOME/session-attention/session.mp3(旧位置 todo-reminder/session.mp3 仍有效)</span>\n </div>\n\n <div className=\"todo-settings-group\">🖥 系统通知</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">权限</span>\n <span className=\"todo-settings-hint\">\n {permission === 'granted' ? '已授权' : permission === 'denied' ? '已被拒绝(需在浏览器/系统设置里放开)' : permission === 'unsupported' ? '当前环境不支持' : '未授权'}\n </span>\n {permission === 'default' ? (\n <button type=\"button\" className=\"todo-btn\" onClick={requestPermission}>开启系统通知</button>\n ) : null}\n </div>\n\n <div className=\"todo-settings-group\">🫧 活动悬浮球</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">悬浮球</span>\n <label className=\"todo-check\" title=\"显示 DSH 活跃会话的悬浮球;关闭后页面上不再出现\">\n <input type=\"checkbox\" checked={ball.enabled} onChange={(e) => void attentionStore.setBall({ enabled: e.target.checked })} />\n 显示\n </label>\n <label className=\"todo-check\" title=\"拖动松手后自动吸附到最近的左右边缘\">\n <input type=\"checkbox\" checked={ball.snapToEdge} onChange={(e) => void attentionStore.setBall({ snapToEdge: e.target.checked })} />\n 边缘吸附\n </label>\n </div>\n\n <div className=\"todo-settings-group\">📋 最近</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">最近提醒</span>\n <span className=\"todo-settings-hint\">\n {latest === null ? '还没有提醒过' : `${formatTime(latest.at)} ${latest.title}${latest.sessionTitle !== undefined ? ' · ' + latest.sessionTitle : ''}`}\n </span>\n </div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">活跃会话</span>\n <span className=\"todo-settings-hint\">\n {activity.length === 0\n ? '空闲'\n : activity.map((item) => `${item.sessionTitle ?? item.sessionId.slice(0, 8)}:${PHASE_LABEL[item.phase] ?? item.phase}${item.tool !== undefined ? '(' + item.tool + ')' : ''}`).join(' | ')}\n </span>\n </div>\n </div>\n )\n}\n","/**\n * 侧边栏入口「提醒」:DOM 注入(shell 无对外侧边栏 slot),MutationObserver 自愈。\n * 与 task-board / ssh / 待办 的入口共处同一家族块,位置稳定、互斥开合。\n * @module dsh-session-attention/client/sidebar-entry\n */\n\nimport { attentionStore } from './attention-store.ts'\n\n/** 入口行稳定标识。 */\nconst ENTRY_SELECTOR = '[data-dsh-att-entry]'\n\n/**\n * 当前存活实例的清理函数。\n *\n * 客户端被重复 apply(HMR / 双重注册)时,旧实例的 MutationObserver 会在新实例接管后\n * 把旧元素重新插回去 —— 表现就是侧边栏出现两个入口。这里让新实例先干掉旧实例。\n */\nlet activeDispose: (() => void) | undefined\n\nconst ICON = `<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M4.4 6.6a3.6 3.6 0 1 1 7.2 0c0 2.1.5 3.2 1 3.9H3.4c.5-.7 1-1.8 1-3.9Z\"/><path d=\"M6.7 12.6a1.4 1.4 0 0 0 2.6 0\"/></svg>`\n\nfunction sidebarRoot(): HTMLElement | undefined {\n const column = document.querySelector<HTMLElement>('[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]')\n if (column === null) return undefined\n const logoOwner = column.querySelector<HTMLElement>('[class*=\"logoRow\"]')?.parentElement\n return logoOwner ?? (column.firstElementChild as HTMLElement | undefined)\n}\n\n/**\n * 定位「新建会话」按钮(DSH 0.1.6 起它是 logoRow 的兄弟节点,不再是其子元素);\n * 三级回退:全局 → root 内 → root 的第一个 button。\n */\nfunction newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {\n const scopes: ParentNode[] = [document, root]\n for (const scope of scopes) {\n const found = scope.querySelector<HTMLButtonElement>('button[class*=\"newSession\"]')\n if (found !== null) return found\n }\n for (const child of root.children) {\n if (child.tagName === 'BUTTON') return child as HTMLButtonElement\n }\n return undefined\n}\n\n/** `newSession` 按钮的宿主父元素(入口行的实际插入容器)。 */\nfunction entryHost(button: HTMLButtonElement, fallback: HTMLElement): HTMLElement {\n const parent = button.parentElement\n return parent ?? button.closest<HTMLElement>('[class*=\"logoRow\"]')?.parentElement ?? fallback\n}\n\nfunction createEntry(): HTMLButtonElement {\n const entry = document.createElement('button')\n entry.type = 'button'\n entry.dataset.dshAttEntry = ''\n entry.setAttribute('aria-label', '提醒')\n entry.innerHTML = `<span class=\"todo-entry-icon\">${ICON}</span><span class=\"todo-entry-label\">提醒</span>`\n entry.addEventListener('click', () => {\n attentionStore.togglePanel()\n })\n return entry\n}\n\n/** 家族锚点:与这些兄弟入口排在同一处。 */\nconst FAMILY_SELECTOR = '[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-todo-entry], [data-dsh-att-entry]'\n\nfunction placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {\n // 过期实例绝不抢插。守卫必须在这里而不是只在 tryPlace:rootObserver 也直接调本函数,\n // 少了它,旧实例会把入口插回来 —— 表现就是侧边栏多出一个入口。\n const owners = document.querySelectorAll(ENTRY_SELECTOR)\n if (owners.length > 1 || (owners.length === 1 && owners[0] !== entry)) return false\n const button = newSessionButton(root)\n if (button === undefined) return false\n const host = entryHost(button, root)\n if (entry.parentElement !== host) {\n const family = Array.from(host.children).filter(\n (el): el is HTMLElement => el instanceof HTMLElement && el.matches(FAMILY_SELECTOR),\n )\n const anchor = family.length > 0 ? family[0] : button.nextElementSibling\n host.insertBefore(entry, anchor)\n }\n return true\n}\n\n/** 挂载入口行并绑定激活态高亮。 */\nexport function mountSidebarEntry(): () => void {\n // 先干掉上一个实例(连同它的 observer),否则两个入口会同时存在。\n activeDispose?.()\n activeDispose = undefined\n // 幂等:清理旧实例残留(客户端 bundle 被重复加载时)。\n document.querySelectorAll(ENTRY_SELECTOR).forEach((el) => el.remove())\n\n const entry = createEntry()\n let root: HTMLElement | undefined\n let placed = false\n\n const tryPlace = (): void => {\n // 若场上已有别的实例的入口(跨 bundle 重复加载),本实例作废,绝不抢插。\n const owners = document.querySelectorAll(ENTRY_SELECTOR)\n if (owners.length > 1 || (owners.length === 1 && owners[0] !== entry)) return\n if (root !== undefined && !root.isConnected) {\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n if (placed) {\n if (document.body.contains(entry)) return\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n root ??= sidebarRoot()\n if (root === undefined) return\n placed = placeEntry(root, entry)\n if (placed) rootObserver.observe(root, { childList: true, subtree: true })\n }\n\n /**\n * 清掉场上不属于本实例的入口。\n * 上限 20 次:万一遇到更老的、没有守卫的实例反复插回,也不会陷入无限循环。\n */\n let sweepBudget = 20\n const sweepForeign = (): void => {\n if (sweepBudget <= 0) return\n for (const el of document.querySelectorAll(ENTRY_SELECTOR)) {\n if (el === entry) continue\n el.remove()\n sweepBudget -= 1\n }\n }\n\n const waitObserver = new MutationObserver(() => { sweepForeign(); tryPlace() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const rootObserver = new MutationObserver(() => {\n if (root === undefined || !root.isConnected) {\n placed = false\n tryPlace()\n return\n }\n sweepForeign()\n if (!root.contains(entry)) placed = placeEntry(root, entry)\n })\n\n const syncActive = (): void => {\n if (attentionStore.getSnapshot().panelOpen) entry.dataset.active = 'true'\n else delete entry.dataset.active\n }\n const unsubscribe = attentionStore.subscribe(syncActive)\n syncActive()\n\n tryPlace()\n\n const dispose = (): void => {\n waitObserver.disconnect()\n rootObserver.disconnect()\n unsubscribe()\n entry.remove()\n if (activeDispose === dispose) activeDispose = undefined\n }\n activeDispose = dispose\n return dispose\n}\n","/**\n * dsh-session-attention 浏览器半:注入样式,挂载侧边栏「提醒」入口、设置面板、\n * 全局提醒层(含活动悬浮球)。失败策略:DOM 挂载异常只记录、不抛出\n * (外部插件不得拖垮 GUI 启动)。\n * @module dsh-session-attention/client\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { attentionStore } from './attention-store.ts'\nimport { AttentionLayer } from './AttentionLayer.tsx'\nimport { AttentionPanel } from './AttentionPanel.tsx'\nimport { resolveSessionRow, setSessionOpener, setSessionTitleResolver, type SessionTarget } from './session-nav.ts'\nimport { mountSidebarEntry } from './sidebar-entry.ts'\nimport { injectStyles } from './styles.ts'\n\n/** 跨插件面板互斥事件(与 task-board / ssh / 待办 同契约)。 */\nconst ACTIVATE_EVENT = 'dsh-panel-activate'\nconst PANEL_NAME = 'attention'\nconst ACTIVE_ATTR = 'data-dsh-attention-active'\nconst SIBLING_ATTRS = ['data-dsh-todo-active', 'data-dsh-ssh-active', 'data-dsh-taskboard-active']\n\n/**\n * 中心列(对话列)候选,**按优先级逐个试**。\n * 注意:querySelector 的逗号列表是「文档顺序优先」而不是「选择器顺序优先」——别的插件的外层\n * 容器只要类名带 centerCol 就会被选中,而 inset:0 的面板挂上去会盖满整个 UI。\n * 0.1.6 起 data-pane 标记被移除,回退到 centerCol / _centerCol 类名。\n */\nconst COLUMN_CANDIDATES = ['[data-pane=\"conversation\"]', '[class*=\"centerCol\"]', '[class*=\"_centerCol\"]']\n\n/** 宿主标记:定位与隐藏兄弟都只作用于自己挂载的这个元素,避免误伤其他插件的同名容器。 */\nconst HOST_ATTR = 'data-dsh-att-host'\n\n/** 应用外壳判定:含侧边栏(或本插件的侧边栏入口)的容器不是对话列,挂上去必然盖满整个 UI。 */\nfunction looksLikeAppShell(el: HTMLElement): boolean {\n if (el === document.body || el === document.documentElement) return true\n return el.querySelector('[data-pane=\"sidebar\"], [class*=\"sidebarCol\"], [data-dsh-todo-entry], [data-dsh-att-entry]') !== null\n}\n\n/** 找对话列:按优先级 + 外壳校验;找不到就返回 undefined(宁可不挂,也不盖满 UI)。 */\nfunction findConversationColumn(): HTMLElement | undefined {\n for (const selector of COLUMN_CANDIDATES) {\n for (const el of document.querySelectorAll<HTMLElement>(selector)) {\n if (!looksLikeAppShell(el)) return el\n }\n }\n return undefined\n}\n\n/**\n * 面板挂载:容器插进中心列,打开时占满它(独立页面式,与任务看板 / SSH 一致),\n * 中心列其余内容暂时隐藏;关闭后立刻回到对话。开合由 store 的 panelOpen 驱动。\n */\nfunction mountPanel(): () => void {\n document.querySelectorAll('[data-dsh-att-view]').forEach((el) => el.remove())\n document.querySelectorAll('[' + HOST_ATTR + ']').forEach((el) => el.removeAttribute(HOST_ATTR))\n\n let root: Root | undefined\n let container: HTMLDivElement | undefined\n\n /** 卸掉容器与宿主标记:宿主被换、面板关闭、插件卸载都会走这里。 */\n const teardown = (): void => {\n try { root?.unmount() } catch { /* 忽略清理异常 */ }\n root = undefined\n container?.remove()\n container = undefined\n document.querySelectorAll('[' + HOST_ATTR + ']').forEach((el) => el.removeAttribute(HOST_ATTR))\n }\n\n const ensure = (): void => {\n // 只在打开时挂载:关掉后 DOM 里不留残留,插件被停用时也就不会有一块东西盖住界面。\n if (!attentionStore.getSnapshot().panelOpen) { if (container !== undefined) teardown(); return }\n // 自愈:外壳/别的插件重渲染会把宿主换掉,此时容器已是游离节点,必须重建。\n if (container !== undefined && container.isConnected) return\n teardown()\n const column = findConversationColumn()\n if (column === undefined) return\n column.setAttribute(HOST_ATTR, '')\n container = document.createElement('div')\n container.dataset.dshAttView = ''\n column.appendChild(container)\n root = createRoot(container)\n root.render(<AttentionPanel />)\n }\n\n const waitObserver = new MutationObserver(() => { ensure() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const applyActive = (): void => {\n if (attentionStore.getSnapshot().panelOpen) {\n for (const attr of SIBLING_ATTRS) document.documentElement.removeAttribute(attr)\n document.documentElement.setAttribute(ACTIVE_ATTR, '')\n document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))\n } else {\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n }\n ensure()\n }\n const close = (): void => { if (attentionStore.getSnapshot().panelOpen) attentionStore.setPanel(false) }\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail !== PANEL_NAME) close()\n }\n const onKeyDown = (event: KeyboardEvent): void => { if (event.key === 'Escape') close() }\n const SIDEBAR_ROW_SELECTOR = '[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]'\n const onClickSidebarRow = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null\n if (target === null) return\n // 点自己的侧边栏入口不算「点了会话行」:本监听在捕获阶段先跑,若在这里 close(),\n // 紧接着入口自身的 click 又 togglePanel() 开回来 —— 表现就是「点了关不掉」。\n if (target.closest('[data-dsh-att-entry]') !== null) return\n if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) close()\n }\n\n document.addEventListener('keydown', onKeyDown)\n document.addEventListener('click', onClickSidebarRow, true)\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n const unsubscribe = attentionStore.subscribe(applyActive)\n applyActive()\n ensure()\n\n return () => {\n document.removeEventListener('keydown', onKeyDown)\n document.removeEventListener('click', onClickSidebarRow, true)\n document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)\n waitObserver.disconnect()\n unsubscribe()\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n teardown()\n }\n}\n/** 全局提醒层挂载(body 上的 React 根,跨会话常驻;内含活动悬浮球)。 */\nfunction mountLayer(): () => void {\n document.querySelectorAll('[data-dsh-att-layer]').forEach((el) => el.remove())\n\n const container = document.createElement('div')\n container.dataset.dshAttLayer = ''\n document.body.appendChild(container)\n const root = createRoot(container)\n root.render(<AttentionLayer />)\n return () => {\n root.unmount()\n container.remove()\n }\n}\n\n/**\n * 声明依赖 `sessions` 服务:它保证本插件在客户端会话控制器就绪之后才 apply,\n * 侧边栏/主视图此时已可被定位(跳转本身走 DOM 委托,不调用该服务)。\n */\nexport const inject = ['sessions', 'uiWorkspace']\n\n/** 客户端上下文最小接口。 */\nexport interface AttentionClientContext {\n readonly sessions?: unknown\n /**\n * ui-workspace 客户端服务(DSH 0.2 起的官方主视图切换入口)。\n * 缺席时退回「委托侧边栏行点击」的老办法。\n */\n readonly uiWorkspace?: { openSession?: (target: string) => void }\n}\n\nexport function apply(ctx: AttentionClientContext): () => void {\n injectStyles()\n\n const disposers: Array<() => void> = []\n\n // 会话标题:DSH 0.2 起 host 侧拿不到标题,真正的来源是客户端会话列表(侧边栏同源)。\n disposers.push(setSessionTitleResolver((sessionId: string): string | undefined => {\n const list = (ctx.sessions as {\n list?: { getSnapshot?: () => { byId?: Record<string, { title?: string, blank?: boolean }> } }\n } | undefined)?.list\n const row = list?.getSnapshot?.()?.byId?.[sessionId]\n if (row === undefined || row.blank === true) return undefined\n const title = typeof row.title === 'string' ? row.title.trim() : ''\n return title === '' ? undefined : title\n }))\n\n // 先登记跳转能力,再挂 UI,保证首帧就知道会话可点。\n disposers.push(setSessionOpener((target: SessionTarget): boolean | Promise<boolean> => {\n // 首选:官方 uiWorkspace.openSession(sessionId)。它内部走 replaceMain 并自行管理\n // 主视图引用,因此**不要求那个会话此刻能在侧边栏里被看见**(工作区收起也行)。\n // 服务未注册时读取本身也可能抛,所以连读取一起兜住。\n let service: { openSession?: (target: string) => void } | undefined\n try { service = ctx.uiWorkspace } catch { service = undefined }\n if (service !== undefined && typeof service.openSession === 'function') {\n try {\n service.openSession(target.sessionId)\n console.info('[dsh-session-attention] 已走官方接口跳转会话', target.sessionId)\n return true\n } catch (error) {\n console.warn('[dsh-session-attention] uiWorkspace.openSession 失败,退回侧边栏点击', error)\n }\n }\n // 兜底:委托官方侧边栏行点击(服务缺席 / 旧版本时)。\n return (async (): Promise<boolean> => {\n for (let attempt = 0; attempt < 2; attempt += 1) {\n const row = resolveSessionRow(target)\n if (row !== undefined) {\n const clickable = row.querySelector<HTMLElement>('button, [role=\"button\"]') ?? row\n clickable.click()\n return true\n }\n await new Promise((resolve) => { window.setTimeout(resolve, 16) })\n }\n console.warn('[dsh-session-attention] 侧边栏里定位不到该会话行,跳转失败', target.sessionId, target.title)\n return false\n })()\n }))\n try {\n disposers.push(mountSidebarEntry())\n disposers.push(mountPanel())\n disposers.push(mountLayer())\n } catch (error) {\n console.error('[dsh-session-attention] mount failed:', error)\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) {\n try { dispose() } catch { /* 忽略清理异常 */ }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useRef","useCallback","useSyncExternalStore","useState","createRoot"],"sources":["../src/client/api.ts","../src/client/attention-store.ts","../src/client/session-nav.ts","../src/types.ts","../src/client/ActivityBall.tsx","../src/client/sound.ts","../src/client/AttentionLayer.tsx","../src/client/styles.ts","../src/client/AttentionPanel.tsx","../src/client/panel-seat.tsx","../src/client/sidebar-entry.ts","../src/client/index.tsx"],"sourcesContent":["/**\n * 浏览器 → host 的同源 JSON API 封装(与 dsh-pet 的 /api/pet/* 同模式)。\n * @module dsh-session-attention/client/api\n */\n\nimport type { AttentionSettings, AttentionState, BallSettings } from '../types.ts'\n\nasync function attentionFetch<T>(path: string, body?: unknown): Promise<T> {\n const response = await fetch(path, body === undefined\n ? {}\n : {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!response.ok) {\n throw new Error(`attention ${path} failed: ${response.status}`)\n }\n return (await response.json()) as T\n}\n\nexport const attentionApi = {\n /** 在文件管理器里打开数据目录(自定义音效放这里)。 */\n openDir: () => attentionFetch<{ ok: boolean, dir?: string }>('/api/attention/open-dir', {}),\n state: () => attentionFetch<AttentionState>('/api/attention/state'),\n setSettings: (settings: {\n attention?: Partial<AttentionSettings>\n ball?: Partial<BallSettings>\n }) => attentionFetch<{ ok: boolean }>('/api/attention/set-settings', { settings }),\n}\n","/**\n * 浏览器端 store:单一实例,持有 host 状态快照(设置)+ DSH 活跃会话列表 +\n * 最近一条提醒 + 面板开合。React 组件经 useSyncExternalStore 订阅。\n * @module dsh-session-attention/client/attention-store\n */\n\nimport { attentionApi } from './api.ts'\nimport type { AttentionEvent, AttentionSettings, AttentionState, BallSettings, SessionActivity } from '../types.ts'\n\nexport interface AttentionClientState {\n data: AttentionState | null\n error: string | null\n /** DSH 活跃会话列表(活动悬浮球展示)。 */\n activity: SessionActivity[]\n /** 最近一条「需要你操作」提醒;用于悬浮球/面板闪示与「查看最近」。 */\n latest: AttentionEvent | null\n /** 设置面板是否展开(侧边栏「提醒」入口控制)。 */\n panelOpen: boolean\n}\n\ntype Listener = () => void\n\nclass AttentionStore {\n private state: AttentionClientState = {\n data: null,\n error: null,\n activity: [],\n latest: null,\n panelOpen: false,\n }\n\n private readonly listeners = new Set<Listener>()\n\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getSnapshot = (): AttentionClientState => this.state\n\n private set(patch: Partial<AttentionClientState>): void {\n this.state = { ...this.state, ...patch }\n for (const listener of this.listeners) listener()\n }\n\n /** host 状态(设置)推送。 */\n applyState(data: AttentionState): void {\n this.set({ data, error: null })\n }\n\n /** DSH 活跃会话列表推送。 */\n applyActivity(activity: SessionActivity[]): void {\n this.set({ activity })\n }\n\n /** 一条「需要你操作」提醒到达。 */\n applyAttention(latest: AttentionEvent): void {\n this.set({ latest })\n }\n\n /** 拉取 host 状态(首次加载 / 回到前台兜底)。 */\n async refresh(): Promise<void> {\n try {\n this.applyState(await attentionApi.state())\n } catch (error) {\n this.set({ error: error instanceof Error ? error.message : String(error) })\n }\n }\n\n /** 在文件管理器里打开数据目录(音效文件放这里)。失败静默。 */\n async openSoundDir(): Promise<void> {\n try {\n await attentionApi.openDir()\n } catch { /* 忽略 */ }\n }\n\n togglePanel(): void {\n this.set({ panelOpen: !this.state.panelOpen })\n }\n\n setPanel(open: boolean): void {\n if (this.state.panelOpen !== open) this.set({ panelOpen: open })\n }\n\n async setAttention(patch: Partial<AttentionSettings>): Promise<void> {\n await attentionApi.setSettings({ attention: patch })\n await this.refresh()\n }\n\n async setBall(patch: Partial<BallSettings>): Promise<void> {\n await attentionApi.setSettings({ ball: patch })\n await this.refresh()\n }\n}\n\n/** 应用级单例(入口 / 面板 / 提醒层共用)。 */\nexport const attentionStore = new AttentionStore()\n","/**\n * 会话跳转桥:client 半经 cordis 注入拿到 sessions 服务后把「打开会话」登记在这里,\n * 悬浮球等纯 React 组件(不持有 ctx)通过它跳进对应会话。服务缺席时优雅降级——\n * canOpenSession() 返回 false,UI 就不显示可点击的样子。\n *\n * DSH 0.1.6 起 `sessions` 客户端服务的 `open(id)` 已被移除(新公开接口是\n * `retain` / `using` / `retainInfo` / `subagentAddress`),旧调用会抛 TypeError\n * 并被静默吞掉,表现为「点会话行没反应」。而 `sessions.retain` 只保留引用、\n * 不切换主视图(切视图由 ui-workspace 内部 `replaceMain` 负责并管理引用生命周期),\n * 外部插件直接 retain 会泄漏引用且界面不动。\n *\n * 因此跳转改为委托官方组件:在侧边栏定位该会话行并触发它的原生点击,\n * 由 ui-workspace 自己完成主视图切换。无法定位时降级为不展示可点击态。\n * @module dsh-todo-reminder/client/session-nav\n */\n\n/**\n * 跳转目标描述。\n * @property sessionId - 会话 id\n * @property title - 会话标题,用于在侧边栏匹配对应行;缺失时退化为用 id 匹配\n */\nexport interface SessionTarget {\n readonly sessionId: string\n readonly title?: string\n}\n\ntype Opener = (target: SessionTarget) => Promise<boolean> | boolean\n\nlet opener: Opener | null = null\n\n/**\n * 标题解析桥:host 推来的会话标题在 DSH 0.2 里已经拿不到(session/event 不再带\n * session/title),真正的标题源是**客户端的会话列表**(侧边栏显示的就是它)。\n * client 半在 apply 时把解析器登记进来,纯 React 组件经此取标题。\n */\nlet titleResolver: ((sessionId: string) => string | undefined) | null = null\n\n/** 登记标题解析器;返回注销 disposer。 */\nexport function setSessionTitleResolver(next: (sessionId: string) => string | undefined): () => void {\n titleResolver = next\n return () => {\n if (titleResolver === next) titleResolver = null\n }\n}\n\n/** 取某个会话的标题;解析器缺席或查不到时返回 undefined。 */\nexport function sessionTitleOf(sessionId: string): string | undefined {\n if (titleResolver === null) return undefined\n try {\n return titleResolver(sessionId)\n } catch {\n return undefined\n }\n}\n\n/**\n * 登记打开会话的实现。\n * @returns 注销该实现的 disposer(热重载 / 卸载时调用)。\n */\nexport function setSessionOpener(next: Opener): () => void {\n opener = next\n return () => {\n if (opener === next) opener = null\n }\n}\n\n/**\n * 当前是否具备跳转会话的能力。\n * 只反映「桥已登记」,不代表具体某个会话此刻一定能在侧边栏找到。\n */\nexport function canOpenSession(): boolean {\n return opener !== null\n}\n\n/** 会话行文本的归一化:折叠空白,便于跨渲染比对。 */\nfunction normalizeLabel(text: string | null | undefined): string {\n return (text ?? '').replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * 在侧边栏里找到目标会话行。\n *\n * 只认官方组件使用的 CSS-module 类名(`sessionRow` / `searchResultRow`),\n * 不依赖 hash 前缀,因此跨版本稳定;找不到返回 undefined。\n */\nfunction findSessionRow(target: SessionTarget): HTMLElement | undefined {\n const rows = Array.from(\n document.querySelectorAll<HTMLElement>('[class*=\"sessionRow\"], [class*=\"searchResultRow\"]'),\n )\n if (rows.length === 0) return undefined\n\n const wanted = normalizeLabel(target.title)\n if (wanted !== '') {\n for (const row of rows) {\n const label = normalizeLabel(row.textContent)\n // 行内还含状态/时间等附属文本,故用「包含」而非全等。\n if (label === wanted || label.includes(wanted)) return row\n }\n }\n // 标题缺失或匹配不上:退化为匹配行上的 id 痕迹(部分版本会挂在属性里)。\n for (const row of rows) {\n const marker = row.getAttribute('data-session-id') ?? row.getAttribute('data-id')\n if (marker !== null && marker === target.sessionId) return row\n }\n return undefined\n}\n\n/**\n * 跳到指定会话。\n *\n * 成功与否取决于该会话此刻是否能在侧边栏定位到(例如所属工作区被收起时无法定位)。\n * @returns 是否成功发起跳转\n */\nexport function openSession(target: SessionTarget): boolean {\n const current = opener\n if (current === null) return false\n try {\n // 登记的实现可能是异步的(DOM 定位失败会重试一帧);这里不阻塞点击。\n void Promise.resolve(current(target)).catch(() => false)\n return true\n } catch {\n return false\n }\n}\n\n/** 供登记方使用的 DOM 定位能力(与 openSession 同源策略)。 */\nexport function resolveSessionRow(target: SessionTarget): HTMLElement | undefined {\n return findSessionRow(target)\n}\n","/**\n * dsh-session-attention 共享类型(host 与 client 都引用;纯类型 + 少量纯函数)。\n * 本包只管「DSH 会话」一件事:agent 完成一轮 / 提问 / 需要审批时提醒,\n * 外加一个只显示活跃会话的活动悬浮球。不含任何待办概念。\n * @module dsh-session-attention/types\n */\n\n/** 提醒音色(合成音用)与通知配色共用的样式。 */\nexport type ReminderStyle = 'info' | 'success' | 'warning' | 'danger'\n\nexport const REMINDER_STYLES: readonly ReminderStyle[] = ['info', 'success', 'warning', 'danger']\n\nexport function isReminderStyle(value: unknown): value is ReminderStyle {\n return typeof value === 'string' && (REMINDER_STYLES as readonly string[]).includes(value)\n}\n\n/** 夹紧数值到 [min,max];非有限数回退 fallback。 */\nexport function clampNumber(value: unknown, min: number, max: number, fallback: number): number {\n const n = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(n)) return fallback\n return Math.min(max, Math.max(min, n))\n}\n\n/** 「需要你操作」的提醒设置。 */\nexport interface AttentionSettings {\n /** agent 完成一轮回复时提醒。 */\n onTurnEnd: boolean\n /** agent 用 ask_user_question 提问时提醒。 */\n onQuestion: boolean\n /** 有审批请求(approve/deny)时提醒。 */\n onApproval: boolean\n /** 这类提醒是否响铃(页面内音效)。 */\n sound: boolean\n /** 音量 0..1。 */\n volume: number\n /** 合成音音色。 */\n tone: ReminderStyle\n}\n\n/** 活动悬浮球设置。 */\nexport interface BallSettings {\n /** 是否显示活动悬浮球。 */\n enabled: boolean\n /** 拖动松手后吸附到最近的左右边缘。 */\n snapToEdge: boolean\n}\n\n/** 本模块持久化的全部设置。 */\nexport interface AttentionModuleSettings {\n attention: AttentionSettings\n ball: BallSettings\n}\n\nexport const DEFAULT_SETTINGS: AttentionModuleSettings = {\n attention: { onTurnEnd: true, onQuestion: true, onApproval: true, sound: true, volume: 0.5, tone: 'success' },\n ball: { enabled: true, snapToEdge: true },\n}\n\n/** 一条「需要用户操作」的会话提醒(host → 浏览器 SSE)。 */\nexport interface AttentionEvent {\n kind: 'turn-end' | 'question' | 'approval'\n title: string\n body: string\n at: number\n /** 触发它的会话 id;点击提醒可直接跳回该会话。 */\n sessionId?: string\n /** 会话标题(通知里显示是哪个会话)。 */\n sessionTitle?: string\n}\n\n/** 单个会话的活动阶段(不在活动列表里 = 空闲)。 */\nexport type ActivityPhase = 'thinking' | 'tool' | 'done'\n\n/** 单个会话的活动快照(多会话追踪)。 */\nexport interface SessionActivity {\n sessionId: string\n sessionTitle?: string\n phase: ActivityPhase\n /** phase === 'tool' 时的当前工具名。 */\n tool?: string\n changedAt: number\n}\n\n/** host 返回给浏览器的完整状态。 */\nexport interface AttentionState {\n settings: AttentionModuleSettings\n now: number\n}\n\n/** SSE 推送信封。 */\nexport type PushMessage =\n | { type: 'state'; state: AttentionState }\n | { type: 'attention'; attention: AttentionEvent }\n | { type: 'activity'; activity: SessionActivity[] }\n","/**\n * DSH 会话活动悬浮球:浮在页面上的可拖动小球,只展示 DSH 当前活跃会话\n * (思考中 / 执行工具 / 等待你,最多 5 个、按最近更新排序);有活动时发光脉动。\n * 位置随窗口缩放自动夹回视口,松手可吸附边缘;Esc 或点击外部收起面板。\n *\n * 刻意**不提供画中画置顶小窗**:Chromium 的 Document PiP 窗口是操作系统级始终置顶,\n * 会干扰独占全屏游戏(LOL / 守望先锋等切全屏失败或被弹回窗口化)。球只活在 DSH 窗口内。\n * @module dsh-session-attention/client/ActivityBall\n */\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore, type CSSProperties, type PointerEvent as ReactPointerEvent, type ReactElement } from 'react'\nimport { attentionStore } from './attention-store.ts'\nimport { canOpenSession, openSession, sessionTitleOf } from './session-nav.ts'\nimport { DEFAULT_SETTINGS, type ActivityPhase, type SessionActivity } from '../types.ts'\n\nconst POS_KEY = 'dsh.todoReminder.ballPos'\nconst BALL_SIZE = 44\n/** 与 styles.ts `.todo-ball-panel` 的 width / max-height 保持一致,否则贴边时会算错位置。 */\nconst PANEL_WIDTH = 300\nconst PANEL_MAX_HEIGHT = 380\nconst EDGE = 8\nconst GAP = 12\n\ninterface BallPos { x: number, y: number }\n\nconst PHASE_META: Record<ActivityPhase, { icon: string, label: string }> = {\n thinking: { icon: '💭', label: '思考中' },\n tool: { icon: '⚙️', label: '执行中' },\n done: { icon: '🔔', label: '等待你' },\n}\n\nconst IDLE_META = { icon: '🌀', label: '空闲' }\n\n/** 汇总阶段:done > tool > thinking > idle。 */\nfunction summaryPhase(sessions: SessionActivity[]): ActivityPhase | 'idle' {\n if (sessions.length === 0) return 'idle'\n if (sessions.some((s) => s.phase === 'done')) return 'done'\n if (sessions.some((s) => s.phase === 'tool')) return 'tool'\n return 'thinking'\n}\n\nfunction sessionLabel(session: SessionActivity): string {\n // host 推来的标题在 DSH 0.2 里拿不到,回退问客户端会话列表(侧边栏同源)。\n const raw = session.sessionTitle !== undefined && session.sessionTitle !== ''\n ? session.sessionTitle\n : sessionTitleOf(session.sessionId)\n if (raw !== undefined && raw !== '') {\n return raw.length > 18 ? `${raw.slice(0, 18)}…` : raw\n }\n return `会话 ${session.sessionId.slice(0, 8)}`\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(min, max), Math.max(min, value))\n}\n\n/** 把位置夹回当前视口内。 */\nfunction clampPos(pos: BallPos): BallPos {\n return {\n x: clamp(pos.x, EDGE, window.innerWidth - BALL_SIZE - EDGE),\n y: clamp(pos.y, EDGE, window.innerHeight - BALL_SIZE - EDGE),\n }\n}\n\nfunction loadPos(): BallPos {\n try {\n const raw = localStorage.getItem(POS_KEY)\n if (raw !== null) {\n const parsed = JSON.parse(raw) as Partial<BallPos>\n if (typeof parsed.x === 'number' && typeof parsed.y === 'number') return clampPos({ x: parsed.x, y: parsed.y })\n }\n } catch { /* 忽略损坏的本地记录 */ }\n return { x: EDGE, y: window.innerHeight - 160 }\n}\n\nfunction savePos(pos: BallPos): void {\n try {\n localStorage.setItem(POS_KEY, JSON.stringify(pos))\n } catch { /* 隐私模式下写不进去,忽略 */ }\n}\n\nexport function ActivityBall(): ReactElement | null {\n const { activity, data } = useSyncExternalStore(attentionStore.subscribe, attentionStore.getSnapshot)\n const [expanded, setExpanded] = useState(false)\n const [pos, setPos] = useState<BallPos>(loadPos)\n const posRef = useRef(pos)\n const drag = useRef<{ startX: number, startY: number, moved: boolean } | null>(null)\n const ballRef = useRef<HTMLDivElement | null>(null)\n const panelRef = useRef<HTMLDivElement | null>(null)\n\n const ball = data?.settings.ball ?? DEFAULT_SETTINGS.ball\n const phase = summaryPhase(activity)\n const meta = phase === 'idle' ? IDLE_META : PHASE_META[phase]\n const count = activity.length\n\n const updatePos = useCallback((next: BallPos): void => {\n posRef.current = next\n setPos(next)\n }, [])\n\n // 窗口缩放后把球夹回视口:否则缩小窗口会把球永久留在视口外,再也点不到。\n useEffect(() => {\n const onResize = (): void => {\n const next = clampPos(posRef.current)\n if (next.x !== posRef.current.x || next.y !== posRef.current.y) {\n updatePos(next)\n savePos(next)\n } else {\n // 面板定位依赖视口尺寸,缩放后需要重算一次。\n setPos({ ...posRef.current })\n }\n }\n window.addEventListener('resize', onResize)\n return () => { window.removeEventListener('resize', onResize) }\n }, [updatePos])\n\n // Esc / 点击面板外部收起。\n useEffect(() => {\n if (!expanded) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') setExpanded(false)\n }\n const onPointerDown = (event: PointerEvent): void => {\n const target = event.target as Node | null\n if (target === null) return\n if (ballRef.current?.contains(target) === true) return\n if (panelRef.current?.contains(target) === true) return\n setExpanded(false)\n }\n document.addEventListener('keydown', onKeyDown)\n document.addEventListener('pointerdown', onPointerDown, true)\n return () => {\n document.removeEventListener('keydown', onKeyDown)\n document.removeEventListener('pointerdown', onPointerDown, true)\n }\n }, [expanded])\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>): boolean => {\n const state = drag.current\n drag.current = null\n try {\n event.currentTarget.releasePointerCapture(event.pointerId)\n } catch { /* 指针已释放,忽略 */ }\n return state !== null && state.moved\n }\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>): void => {\n drag.current = { startX: event.clientX, startY: event.clientY, moved: false }\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (drag.current === null) return\n const dx = event.clientX - drag.current.startX\n const dy = event.clientY - drag.current.startY\n if (Math.abs(dx) + Math.abs(dy) > 4) drag.current.moved = true\n if (drag.current.moved) {\n updatePos({ x: posRef.current.x + dx, y: posRef.current.y + dy })\n drag.current.startX = event.clientX\n drag.current.startY = event.clientY\n }\n }\n\n const onPointerUp = (event: ReactPointerEvent<HTMLDivElement>): void => {\n if (!endDrag(event)) {\n setExpanded((value) => !value)\n return\n }\n let next = clampPos(posRef.current)\n if (ball.snapToEdge) {\n const toLeft = next.x + BALL_SIZE / 2 < window.innerWidth / 2\n next = { ...next, x: toLeft ? EDGE : window.innerWidth - BALL_SIZE - EDGE }\n }\n updatePos(next)\n savePos(next)\n }\n\n // 指针被系统手势 / 触屏中断:必须复位,否则球会一直黏着指针。\n const onPointerCancel = (event: ReactPointerEvent<HTMLDivElement>): void => {\n endDrag(event)\n const next = clampPos(posRef.current)\n updatePos(next)\n savePos(next)\n }\n\n const panelStyle: CSSProperties = {\n left: pos.x - PANEL_WIDTH - GAP >= EDGE\n ? pos.x - PANEL_WIDTH - GAP\n : clamp(pos.x + BALL_SIZE + GAP, EDGE, window.innerWidth - PANEL_WIDTH - EDGE),\n top: clamp(pos.y, EDGE, window.innerHeight - PANEL_MAX_HEIGHT - EDGE),\n maxHeight: Math.min(PANEL_MAX_HEIGHT, window.innerHeight - EDGE * 2),\n }\n\n /**\n * 点一条会话就跳进去;桥缺席时降级为纯展示。\n * 标题一并传下去:0.1.6 的跳转是在侧边栏按标题定位该会话行再委托点击。\n */\n const goToSession = (session: SessionActivity): void => {\n if (openSession({ sessionId: session.sessionId, title: session.sessionTitle })) setExpanded(false)\n }\n\n const renderSession = (session: SessionActivity): ReactElement => {\n const sessionMeta = PHASE_META[session.phase]\n const navigable = canOpenSession()\n const task = session.phase === 'tool' && session.tool !== undefined ? session.tool : sessionMeta.label\n return (\n <div\n className=\"todo-ball-row\"\n key={session.sessionId}\n data-navigable={navigable ? 'true' : undefined}\n role={navigable ? 'button' : undefined}\n tabIndex={navigable ? 0 : undefined}\n title={navigable ? `${sessionLabel(session)} · ${task}\\n点击进入这个会话` : `${sessionLabel(session)} · ${task}`}\n onClick={navigable ? () => goToSession(session) : undefined}\n onKeyDown={navigable\n ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n goToSession(session)\n }\n }\n : undefined}\n >\n <span className=\"todo-ball-row-icon\">{sessionMeta.icon}</span>\n <span className=\"todo-ball-row-title\">{sessionLabel(session)}</span>\n <span className=\"todo-ball-row-state\">{task}</span>\n {navigable ? <span className=\"todo-ball-row-go\" aria-hidden=\"true\">›</span> : null}\n </div>\n )\n }\n\n const tooltip = count > 0\n ? `DSH:${activity.map((s) => `${PHASE_META[s.phase].label}·${sessionLabel(s)}`).join(';')}`\n : 'DSH:空闲'\n\n // 所有 hook 之上不做条件返回:关闭悬浮球在这里才短路。\n if (!ball.enabled) return null\n\n return (\n <>\n <div\n ref={ballRef}\n className=\"todo-ball\"\n role=\"button\"\n tabIndex={0}\n aria-label={tooltip}\n aria-expanded={expanded}\n data-phase={phase}\n data-active={phase === 'idle' ? undefined : 'true'}\n style={{ left: pos.x, top: pos.y }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerCancel}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n setExpanded((value) => !value)\n }\n }}\n title={tooltip}\n >\n <span className=\"todo-ball-icon\">{meta.icon}</span>\n {count > 0 ? <span className=\"todo-ball-badge\">{count}</span> : null}\n </div>\n\n {expanded ? (\n <div className=\"todo-ball-panel\" ref={panelRef} style={panelStyle}>\n <div className=\"todo-ball-panel-head\">\n <h3 className=\"todo-ball-panel-title\">DSH 活跃{count > 0 ? ` (${count})` : ''}</h3>\n <button type=\"button\" className=\"todo-btn\" aria-label=\"关闭\" onClick={() => setExpanded(false)}>✕</button>\n </div>\n {count === 0 ? <div className=\"todo-ball-empty\">暂无进行中的对话</div> : activity.map(renderSession)}\n </div>\n ) : null}\n </>\n )\n}\n","/**\n * 会话提醒音引擎:在页面内播放提醒音,标签页在前台或后台都一致出声。\n *\n * 优先播放自定义音效文件 —— $DSH_HOME/session-attention/session.{mp3,wav,ogg,m4a},\n * 旧位置 $DSH_HOME/todo-reminder/session.* 仍然有效(拆分前的文件不用搬家);\n * 都没有时用 WebAudio 现场合成,因此零配置也有声。\n *\n * 浏览器自动播放策略要求先有用户手势才允许出声,所以这里在 document 上挂手势监听\n * 自动解锁:用户在 DSH 里点任何地方即完成解锁,不需要专门操作。\n * @module dsh-session-attention/client/sound\n */\n\nimport type { ReminderStyle } from '../types.ts'\n\n/** 每种音色的合成序列:[频率 Hz, 相对起始秒, 时长秒]。 */\nconst TONES: Record<ReminderStyle, ReadonlyArray<readonly [number, number, number]>> = {\n info: [[880, 0, 0.12], [1174.7, 0.13, 0.18]],\n success: [[659.3, 0, 0.1], [880, 0.1, 0.1], [1318.5, 0.2, 0.24]],\n warning: [[740, 0, 0.16], [587.3, 0.19, 0.26]],\n danger: [[932.3, 0, 0.1], [698.5, 0.12, 0.1], [932.3, 0.24, 0.1], [698.5, 0.36, 0.2]],\n}\n\n/** 自定义音效端点(host 侧先找本模块目录,再回退旧插件目录)。 */\nconst SOUND_URL = '/api/attention/sound'\n\n/** 两次重复播放之间的间隔(秒)。 */\nconst REPEAT_GAP = 0.14\n\ntype AudioContextCtor = new () => AudioContext\n\nlet audioContext: AudioContext | null = null\nlet customBuffer: AudioBuffer | null = null\nlet probe: Promise<void> | null = null\nlet unlocked = false\nlet gestureBound = false\n\nfunction contextCtor(): AudioContextCtor | undefined {\n const w = window as unknown as { AudioContext?: AudioContextCtor, webkitAudioContext?: AudioContextCtor }\n return w.AudioContext ?? w.webkitAudioContext\n}\n\nfunction ensureContext(): AudioContext | null {\n if (audioContext !== null) return audioContext\n const Ctor = contextCtor()\n if (Ctor === undefined) return null\n try {\n audioContext = new Ctor()\n } catch {\n audioContext = null\n }\n return audioContext\n}\n\n/** 探测自定义音效文件;404 / 解码失败则继续用合成音。 */\nasync function probeSound(): Promise<void> {\n if (probe !== null) return probe\n const ctx = ensureContext()\n if (ctx === null) return\n probe = (async () => {\n try {\n const response = await fetch(SOUND_URL, { cache: 'no-store' })\n if (!response.ok) return\n customBuffer = await ctx.decodeAudioData(await response.arrayBuffer())\n } catch {\n customBuffer = null\n }\n })()\n return probe\n}\n\nfunction unlock(): void {\n const ctx = ensureContext()\n if (ctx === null) return\n if (ctx.state === 'suspended') void ctx.resume().catch(() => { /* 用户尚未授权,下次手势再试 */ })\n unlocked = true\n void probeSound()\n}\n\n/**\n * 挂手势监听自动解锁音频(浏览器自动播放策略)。\n * @returns 解除监听的 disposer。\n */\nexport function armSoundUnlock(): () => void {\n if (gestureBound) return () => { /* 已挂载,交给首个调用方清理 */ }\n gestureBound = true\n const events: ReadonlyArray<keyof DocumentEventMap> = ['pointerdown', 'keydown', 'touchstart']\n const onGesture = (): void => { unlock() }\n for (const event of events) document.addEventListener(event, onGesture, { capture: true, passive: true })\n return () => {\n for (const event of events) document.removeEventListener(event, onGesture, { capture: true })\n gestureBound = false\n }\n}\n\n/** 调度一段合成音;返回这段音的总时长(秒)。 */\nfunction scheduleSynth(ctx: AudioContext, tone: ReminderStyle, volume: number, startAt: number): number {\n let span = 0\n for (const [frequency, offset, duration] of TONES[tone]) {\n const osc = ctx.createOscillator()\n const gain = ctx.createGain()\n osc.type = 'sine'\n osc.frequency.value = frequency\n const t0 = startAt + offset\n const t1 = t0 + duration\n // 指数包络:避免起停爆音(gain 不能取 0,故用极小值)。\n gain.gain.setValueAtTime(0.0001, t0)\n gain.gain.exponentialRampToValueAtTime(Math.max(0.0002, volume), t0 + 0.012)\n gain.gain.exponentialRampToValueAtTime(0.0001, t1)\n osc.connect(gain).connect(ctx.destination)\n osc.start(t0)\n osc.stop(t1 + 0.02)\n span = Math.max(span, offset + duration)\n }\n return span\n}\n\n/** 调度一次自定义音效播放;返回时长(秒)。 */\nfunction scheduleBuffer(ctx: AudioContext, buffer: AudioBuffer, volume: number, startAt: number): number {\n const source = ctx.createBufferSource()\n const gain = ctx.createGain()\n source.buffer = buffer\n gain.gain.value = volume\n source.connect(gain).connect(ctx.destination)\n source.start(startAt)\n return buffer.duration\n}\n\n/**\n * 播放一次会话提醒音。\n * @param tone - 音色(合成音时生效)。\n * @param volume - 音量 0..1。\n * @param repeat - 连响次数(1..5)。\n * @returns 是否已成功调度(音频未解锁 / 不支持时返回 false)。\n */\nexport function playSound(tone: ReminderStyle, volume: number, repeat = 1): boolean {\n const ctx = ensureContext()\n if (ctx === null) return false\n if (ctx.state === 'suspended') void ctx.resume().catch(() => { /* 未解锁,静默失败 */ })\n if (!unlocked && ctx.state !== 'running') return false\n const vol = Math.min(1, Math.max(0, volume))\n if (vol === 0) return false\n const times = Math.min(5, Math.max(1, Math.round(repeat)))\n const buffer = customBuffer\n let cursor = ctx.currentTime + 0.02\n for (let i = 0; i < times; i += 1) {\n const span = buffer !== null\n ? scheduleBuffer(ctx, buffer, vol, cursor)\n : scheduleSynth(ctx, tone, vol, cursor)\n cursor += span + REPEAT_GAP\n }\n return true\n}\n\n/** 试听:立即解锁(本次调用来自用户点击,必定允许出声)并播放一次。 */\nexport function previewSound(tone: ReminderStyle, volume: number, repeat = 1): void {\n unlock()\n // 自定义音效可能仍在探测中:探测完再播,保证试听到的就是实际提醒音。\n void probeSound().then(() => {\n playSound(tone, volume, repeat)\n })\n}\n\n/** 音频是否已解锁可用;未就绪时调用方应让系统通知自己出声兜底。 */\nexport function isSoundReady(): boolean {\n return unlocked\n}\n\n/** 当前音源说明,用于设置面板提示。 */\nexport function soundSourceLabel(): string {\n if (!unlocked) return '未解锁(在页面里点一下即可)'\n return customBuffer !== null ? '自定义音效文件' : '内置合成音'\n}\n","/**\n * 提醒层:body 上的全局 React 根。经 SSE 订阅 host 的会话提醒与活动快照;\n * 标签页切到后台时,把「需要你操作」的提醒转成系统原生通知 + 提醒音。\n *\n * 只在标签页**不可见**时触发 —— 你正盯着屏幕时不必每轮都被打扰。这里不做\n * 页面内 toast:真在看你不需要它,切走了你也看不见它。\n * @module dsh-session-attention/client/AttentionLayer\n */\n\nimport { useEffect, type ReactElement } from 'react'\nimport { ActivityBall } from './ActivityBall.tsx'\nimport { attentionStore } from './attention-store.ts'\nimport { openSession } from './session-nav.ts'\nimport { armSoundUnlock, playSound } from './sound.ts'\nimport { DEFAULT_SETTINGS, type AttentionEvent, type AttentionState, type SessionActivity } from '../types.ts'\n\n/**\n * 一条「需要你操作」提醒 → 提醒音 + 系统原生通知。\n * 开启提醒音后系统通知静音,由音效引擎单独出声,避免「系统叮 + 自定义音效」响两遍;\n * 音频未解锁时交回系统提示音兜底。\n */\nfunction fireAttention(event: AttentionEvent): void {\n const attention = (attentionStore.getSnapshot().data?.settings ?? DEFAULT_SETTINGS).attention\n const played = attention.sound ? playSound(attention.tone, attention.volume, 1) : false\n if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return\n const body = event.sessionTitle !== undefined && event.sessionTitle !== ''\n ? `${event.sessionTitle} —— ${event.body}`\n : event.body\n try {\n const notification = new Notification(event.title, {\n body,\n tag: `dsh-attention-${event.kind}-${event.at}`,\n silent: played ? true : !attention.sound,\n })\n notification.onclick = (): void => {\n window.focus()\n // 点通知直接跳到触发它的那个会话(官方 uiWorkspace 接口,见 session-nav)。\n if (event.sessionId !== undefined) openSession({ sessionId: event.sessionId, title: event.sessionTitle })\n notification.close()\n }\n } catch {\n // 个别环境不支持,忽略。\n }\n}\n\n/** SSE 消息信封(与 host 的 PushMessage 对应)。 */\ninterface PushEnvelope {\n type?: string\n state?: AttentionState\n attention?: AttentionEvent\n activity?: SessionActivity[]\n}\n\nexport function AttentionLayer(): ReactElement {\n // 音频解锁:浏览器要求先有用户手势才允许出声,这里在首个手势时自动完成。\n useEffect(() => armSoundUnlock(), [])\n\n // SSE 实时订阅 + 回到前台时兜底轮询。\n useEffect(() => {\n const source = new EventSource('/api/attention/events')\n source.onmessage = (message) => {\n try {\n const envelope = JSON.parse(message.data) as PushEnvelope\n if (envelope.type === 'state' && envelope.state !== undefined) {\n attentionStore.applyState(envelope.state)\n } else if (envelope.type === 'attention' && envelope.attention !== undefined) {\n attentionStore.applyAttention(envelope.attention)\n if (document.visibilityState === 'hidden') fireAttention(envelope.attention)\n } else if (envelope.type === 'activity' && envelope.activity !== undefined) {\n attentionStore.applyActivity(envelope.activity)\n }\n } catch {\n // 解析失败忽略,等待下一条或轮询兜底。\n }\n }\n const onVisibility = (): void => {\n if (document.visibilityState === 'visible') void attentionStore.refresh()\n }\n document.addEventListener('visibilitychange', onVisibility)\n return () => {\n source.close()\n document.removeEventListener('visibilitychange', onVisibility)\n }\n }, [])\n\n // 首次请求系统通知权限(用户点「允许」后,切出也能收到通知)。\n useEffect(() => {\n if (typeof Notification === 'undefined') return\n if (Notification.permission === 'default') {\n Notification.requestPermission().catch(() => { /* 拒绝静默 */ })\n }\n }, [])\n\n return <ActivityBall />\n}\n","/**\n * 客户端样式:一段 CSS 字符串(apply 时注入 <style>),配色走 dsh 的\n * --dsw-alias-* token,自动适配明暗主题与皮肤。数据属性作用域隔离,不污染 shell。\n * @module dsh-session-attention/client/styles\n */\n\nimport type { ReminderStyle } from '../types.ts'\n\nexport const STYLE_META: Record<ReminderStyle, { label: string; icon: string }> = {\n info: { label: '提醒', icon: 'ℹ️' },\n success: { label: '纪念', icon: '✅' },\n warning: { label: '注意', icon: '⚠️' },\n danger: { label: '紧急', icon: '🚨' },\n}\n\nexport const ATTENTION_CSS = `\n/* --- 面板接管中心列(独立页面式,与任务看板 / SSH 一致) ------------------ */\n/* DSH 0.1.6 移除了 data-pane 标记,中心列改用 CSS-module 类名(centerCol);\n 这里同时兼容旧标记与新类名,否则容器定位会失效。 */\n/* 只给自己挂载的宿主定位:全局匹配 centerCol 会连带影响其他插件的容器。 */\n[data-dsh-att-host] { position: relative; }\n\n[data-dsh-att-view] {\n position: absolute;\n inset: 0;\n display: none;\n z-index: 60;\n overflow: auto;\n background: var(--dsw-alias-bg-base);\n}\nhtml[data-dsh-attention-active] [data-dsh-att-view] { display: block; }\nhtml[data-dsh-attention-active] [data-dsh-att-host] > :not([data-dsh-att-view]) {\n display: none !important;\n}\n/* --- 侧边栏入口 ---------------------------------------------------------- */\n[data-dsh-att-entry] {\n display: flex;\n align-items: center;\n justify-content: flex-start; /* 明确左对齐:外壳若覆盖了 flex,也不会变成居中按钮 */\n text-align: left;\n gap: 8px;\n width: 100%;\n height: 32px;\n padding: 0 12px;\n background: transparent;\n border: none;\n border-radius: 8px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n font-family: var(--dsw-font-family);\n}\n[data-dsh-att-entry]:hover {\n background: var(--dsw-specific-sidebar-nav-item-hover);\n color: var(--dsw-alias-label-primary);\n}\n/* 选中态只做「更亮 + 加粗」,不画背景块:面板状态万一过期,也不会在侧边栏留下一块灰底。 */\n[data-dsh-att-entry][data-active] {\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-weight: 600;\n}\n[data-dsh-att-entry] .todo-entry-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; }\n[data-dsh-att-entry] .todo-entry-label { overflow: hidden; text-overflow: ellipsis; }\n/* 侧边栏收起态:0.1.6 把折叠标记从 [data-dsh-frame] 挪到了 [data-sidebar-collapsed],\n 两种祖先都匹配,避免收起后入口标签溢出。 */\n[data-dsh-frame][data-sidebar-collapsed] [data-dsh-att-entry],\n[data-sidebar-collapsed] [data-dsh-att-entry] { justify-content: center; padding: 0; }\n[data-dsh-frame][data-sidebar-collapsed] [data-dsh-att-entry] .todo-entry-label,\n[data-sidebar-collapsed] [data-dsh-att-entry] .todo-entry-label { display: none; }\n\n/* --- 官方 main 座位里的页面外层(外壳只在选中本面板时挂载它) ----------- */\n.todo-panel-page { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }\n\n/* --- 面板主体 ------------------------------------------------------------ */\n.todo-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-width: 0;\n min-height: 0;\n padding: 14px 16px 16px;\n gap: 12px;\n background: var(--dsw-alias-bg-base);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n}\n.todo-header { display: flex; align-items: center; gap: 10px; flex: none; }\n.todo-title { margin: 0; font-size: 16px; font-weight: 700; color: var(--dsw-alias-label-primary); white-space: nowrap; }\n.todo-count { flex: none; font-size: 12px; color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-interactive-bg-hover); border-radius: 999px; padding: 1px 8px; }\n\n.todo-form { display: flex; flex-direction: column; gap: 8px; flex: none; }\n.todo-form-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n.todo-input { flex: 1; min-width: 160px; padding: 7px 10px; font-size: 13px; color: var(--dsw-alias-label-primary); background: var(--dsw-specific-input-major); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; outline: none; font-family: inherit; }\n.todo-input:focus { border-color: var(--dsw-alias-state-business-primary); }\n.todo-input::placeholder { color: var(--dsw-alias-label-tertiary); }\n.todo-select { padding: 7px 8px; font-size: 12.5px; color: var(--dsw-alias-label-primary); background: var(--dsw-specific-input-major); border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; outline: none; }\n.todo-select:focus { border-color: var(--dsw-alias-state-business-primary); }\n.todo-check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--dsw-alias-label-primary); cursor: pointer; user-select: none; }\n.todo-check input { accent-color: var(--dsw-alias-state-business-primary); margin: 0; }\n\n/* --- 设置行(响铃 / 稍后提醒分钟数) ------------------------------------ */\n.todo-settings { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: none; padding: 8px 10px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; }\n.todo-settings-label { font-size: 12px; font-weight: 700; color: var(--dsw-alias-label-tertiary); }\n.todo-settings-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n\n.todo-list { display: flex; flex-direction: column; gap: 8px; flex: 1; min-height: 0; overflow-y: auto; }\n.todo-section-title { margin: 4px 0 0; font-size: 11px; font-weight: 700; color: var(--dsw-alias-label-tertiary); text-transform: none; letter-spacing: .02em; }\n.todo-empty { padding: 24px 8px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n\n.todo-item {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 12px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n}\n.todo-item:hover { border-color: var(--dsw-alias-border-l3); }\n.todo-item[data-done] { opacity: .6; }\n.todo-item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }\n.todo-item-title { font-size: 13px; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }\n.todo-item[data-done] .todo-item-title { text-decoration: line-through; }\n.todo-item-note { font-size: 12px; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; white-space: pre-wrap; }\n.todo-item-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11px; color: var(--dsw-alias-label-tertiary); }\n.todo-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px; font-size: 11px; border: 1px solid var(--todo-accent, var(--dsw-alias-border-l2)); color: var(--todo-accent, var(--dsw-alias-label-secondary)); background: transparent; }\n.todo-badge[data-style='info'] { --todo-accent: var(--dsw-alias-state-business-primary); }\n.todo-badge[data-style='success'] { --todo-accent: var(--dsw-alias-state-success-primary); }\n.todo-badge[data-style='warning'] { --todo-accent: var(--dsw-alias-state-warn-primary); }\n.todo-badge[data-style='danger'] { --todo-accent: var(--dsw-alias-state-error-primary); }\n\n.todo-actions { display: flex; align-items: center; gap: 4px; flex: none; }\n.todo-btn {\n display: inline-flex; align-items: center; justify-content: center;\n height: 26px; padding: 0 10px;\n background: transparent; border: none; border-radius: 6px;\n color: var(--dsw-alias-label-secondary); cursor: pointer; font-size: 12px; white-space: nowrap;\n}\n.todo-btn:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n.todo-btn.primary { color: #fff; background: var(--dsw-alias-button-info-fill); padding: 0 12px; font-weight: 600; }\n.todo-btn.primary:hover { background: var(--dsw-alias-button-info-hover); }\n.todo-btn.danger:hover { color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-state-error-secondary); }\n.todo-btn[disabled] { opacity: .5; cursor: default; }\n\n/* --- 提醒 toast ---------------------------------------------------------- */\n.todo-toast-stack {\n position: fixed;\n top: 16px;\n right: 16px;\n z-index: 1400;\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: min(360px, calc(100vw - 32px));\n pointer-events: none;\n}\n.todo-toast {\n pointer-events: auto;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--todo-accent, var(--dsw-alias-border-l2));\n border-left-width: 4px;\n border-radius: 10px;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n animation: todo-toast-in 220ms ease;\n}\n.todo-toast[data-style='info'] { --todo-accent: var(--dsw-alias-state-business-primary); }\n.todo-toast[data-style='success'] { --todo-accent: var(--dsw-alias-state-success-primary); }\n.todo-toast[data-style='warning'] { --todo-accent: var(--dsw-alias-state-warn-primary); }\n.todo-toast[data-style='danger'] { --todo-accent: var(--dsw-alias-state-error-primary); animation: todo-toast-in 220ms ease, todo-toast-pulse 1.6s ease-in-out infinite; }\n.todo-toast-head { display: flex; align-items: center; gap: 8px; }\n.todo-toast-icon { font-size: 16px; line-height: 1; }\n.todo-toast-title { flex: 1; min-width: 0; font-size: 13px; font-weight: 700; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n.todo-toast-sub { font-size: 11px; color: var(--todo-accent, var(--dsw-alias-label-tertiary)); }\n.todo-toast-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n@keyframes todo-toast-in { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: none; } }\n@keyframes todo-toast-pulse { 0%,100% { box-shadow: var(--dsw-shadow-lv3); } 50% { box-shadow: 0 0 0 3px var(--todo-accent, transparent); } }\n\n/* --- 确认弹窗 ------------------------------------------------------------ */\n.todo-modal-backdrop {\n position: fixed; inset: 0; z-index: 1500;\n display: flex; align-items: center; justify-content: center;\n background: var(--dsw-alias-bg-mask-1);\n}\n.todo-modal {\n display: flex; flex-direction: column; gap: 12px;\n width: min(440px, calc(100vw - 48px));\n padding: 18px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 14px;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n}\n.todo-modal-title { margin: 0; font-size: 15px; font-weight: 700; }\n.todo-modal-message { margin: 0; font-size: 13px; line-height: 1.5; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }\n.todo-modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }\n\n/* --- DSH 会话活动悬浮球(独立模块,不含待办内容) ------------------------ */\n.todo-ball {\n position: fixed;\n z-index: 1340;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n box-shadow: var(--dsw-shadow-lv2);\n cursor: grab;\n user-select: none;\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n touch-action: none;\n --ball-accent: var(--dsw-alias-label-tertiary);\n}\n.todo-ball:active { cursor: grabbing; }\n.todo-ball-icon { font-size: 18px; line-height: 1; pointer-events: none; }\n.todo-ball-badge {\n position: absolute;\n top: -4px; right: -4px;\n min-width: 16px; height: 16px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--dsw-alias-state-business-primary);\n color: #fff;\n font-size: 10px;\n line-height: 16px;\n text-align: center;\n font-weight: 700;\n pointer-events: none;\n}\n.todo-ball[data-phase='thinking'] { --ball-accent: var(--dsw-alias-state-business-primary); }\n.todo-ball[data-phase='tool'] { --ball-accent: var(--dsw-alias-state-warn-primary); }\n.todo-ball[data-phase='done'] { --ball-accent: var(--dsw-alias-state-success-primary); }\n.todo-ball[data-active] {\n border-color: var(--ball-accent);\n animation: todo-ball-glow 1.6s ease-in-out infinite;\n}\n@keyframes todo-ball-glow {\n 0%, 100% { box-shadow: var(--dsw-shadow-lv2); }\n 50% { box-shadow: 0 0 0 4px var(--ball-accent); }\n}\n.todo-ball-panel {\n position: fixed;\n z-index: 1340;\n width: 300px;\n max-height: 380px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n overflow-y: auto;\n /* 这是个控件面板而非文档:默认箭头指针,避免鼠标移到文字上变成输入光标。 */\n cursor: default;\n user-select: none;\n}\n.todo-ball-panel-head { display: flex; align-items: center; gap: 8px; }\n.todo-ball-panel-title { margin: 0; font-size: 14px; font-weight: 700; flex: 1; }\n.todo-ball-row { display: flex; align-items: center; gap: 6px; padding: 6px 0; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.todo-ball-row:last-child { border-bottom: none; }\n.todo-ball-row-icon { flex: none; font-size: 13px; }\n.todo-ball-row-title { flex: 1; min-width: 0; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-ball-row-state { flex: none; font-size: 11px; color: var(--dsw-alias-label-tertiary); max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-ball-row[data-navigable] {\n margin: 0 -6px;\n padding: 6px;\n border-radius: 7px;\n cursor: pointer;\n}\n.todo-ball-row[data-navigable]:hover { background: var(--dsw-alias-fill-secondary); }\n.todo-ball-row[data-navigable]:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: -2px; }\n.todo-ball-row-go { flex: none; font-size: 14px; color: var(--dsw-alias-label-tertiary); }\n.todo-ball-empty { padding: 16px 4px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n.todo-ball-actions { display: flex; gap: 6px; flex-wrap: wrap; padding-top: 4px; }\n.todo-ball-mini { flex: none; padding: 2px 8px; font-size: 11px; }\n\n/* --- 个人待办 Dock(右侧边缘吸附,可收起) ------------------------------- */\n.todo-dock {\n position: fixed;\n right: 0;\n z-index: 1350;\n display: flex;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l2);\n border-right: none;\n box-shadow: var(--dsw-shadow-lv3);\n color: var(--dsw-alias-label-primary);\n font-family: var(--dsw-font-family);\n touch-action: none;\n}\n/* 收起态:贴边药丸,直接显示下一件要做的事。 */\n.todo-dock[data-collapsed='true'] {\n flex-direction: row;\n align-items: center;\n gap: 7px;\n max-width: 268px;\n padding: 7px 10px 7px 13px;\n border-radius: 999px 0 0 999px;\n box-shadow: var(--dsw-shadow-lv2);\n cursor: pointer;\n user-select: none;\n}\n.todo-dock[data-collapsed='true']:hover { background: var(--dsw-alias-fill-secondary); }\n/* 展开态:右侧待办面板。 */\n.todo-dock[data-collapsed='false'] {\n flex-direction: column;\n gap: 8px;\n width: 320px;\n padding: 12px;\n border-radius: 12px 0 0 12px;\n /* 同上:控件面板用箭头指针,文字不做选中。 */\n cursor: default;\n user-select: none;\n}\n/* 表单控件恢复自身的输入光标与可选中,不受面板的 user-select: none 影响。 */\n.todo-dock input, .todo-dock select, .todo-dock textarea { cursor: auto; user-select: auto; }\n.todo-dock[data-overdue='true'] { border-color: var(--dsw-alias-state-error-primary); }\n.todo-dock[data-collapsed='true'][data-overdue='true'] { animation: todo-dock-pulse 1.8s ease-in-out infinite; }\n@keyframes todo-dock-pulse {\n 0%, 100% { box-shadow: var(--dsw-shadow-lv2); }\n 50% { box-shadow: 0 0 0 4px var(--dsw-alias-state-error-primary); }\n}\n\n.todo-dock-icon { flex: none; font-size: 14px; line-height: 1; }\n.todo-dock-time { flex: none; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }\n.todo-dock-time[data-overdue='true'] { color: var(--dsw-alias-state-error-primary); }\n.todo-dock-next { flex: 1; min-width: 0; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-dock-idle { flex: none; font-size: 12px; color: var(--dsw-alias-label-tertiary); }\n\n.todo-dock-head { display: flex; align-items: center; gap: 8px; cursor: grab; user-select: none; }\n.todo-dock-head:active { cursor: grabbing; }\n.todo-dock-title { margin: 0; flex: 1; font-size: 14px; font-weight: 700; }\n.todo-dock-add { display: flex; gap: 6px; }\n.todo-dock-add .todo-input { flex: 1; min-width: 0; }\n.todo-dock-when { flex: none; width: 96px; }\n.todo-dock-body { display: flex; flex-direction: column; overflow-y: auto; max-height: 46vh; }\n.todo-dock-section { padding: 6px 2px 2px; font-size: 11px; font-weight: 700; letter-spacing: .02em; color: var(--dsw-alias-label-tertiary); }\n.todo-dock-row { display: flex; align-items: center; gap: 7px; padding: 6px 2px; border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.todo-dock-row:last-child { border-bottom: none; }\n.todo-dock-check {\n flex: none;\n width: 17px; height: 17px;\n padding: 0;\n border: 1.5px solid var(--dsw-alias-border-l3);\n border-radius: 50%;\n background: transparent;\n color: transparent;\n font-size: 10px;\n line-height: 1;\n cursor: pointer;\n}\n.todo-dock-check:hover { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }\n.todo-dock-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }\n.todo-dock-row-title { font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.todo-dock-row-due { font-size: 11px; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }\n.todo-dock-row[data-overdue='true'] .todo-dock-row-due { color: var(--dsw-alias-state-error-primary); font-weight: 700; }\n.todo-dock-row-actions { flex: none; display: flex; gap: 4px; opacity: 0; transition: opacity .12s ease; }\n.todo-dock-row:hover .todo-dock-row-actions, .todo-dock-row:focus-within .todo-dock-row-actions { opacity: 1; }\n.todo-dock-mini { flex: none; padding: 2px 7px; font-size: 11px; }\n.todo-dock-empty { padding: 14px 4px; text-align: center; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary); }\n.todo-dock-foot { display: flex; gap: 6px; flex-wrap: wrap; }\n.todo-settings-group {\n padding: 10px 2px 0;\n font-size: 12px;\n font-weight: 700;\n color: var(--dsw-alias-label-secondary);\n}\n.todo-range { flex: none; width: 96px; accent-color: var(--dsw-alias-state-business-primary); cursor: pointer; }\n\n@media (prefers-reduced-motion: reduce) {\n .todo-toast { animation: none; }\n .todo-toast[data-style='danger'] { animation: none; }\n .todo-dock[data-collapsed='true'][data-overdue='true'] { animation: none; }\n .todo-ball[data-active] { animation: none; }\n}\n`\n\n/** 注入样式(幂等)。 */\nexport function injectStyles(): void {\n // 旧节点必须换掉:早期写成「已存在就 return」,升级后新 CSS 永远注入不进去,\n // 入口便退回浏览器默认 button 样式(侧边栏灰底方块就是这个原因)。\n document.querySelectorAll('style[data-dsh-att-css]').forEach((el) => el.remove())\n const style = document.createElement('style')\n style.dataset.dshAttCss = ''\n style.textContent = ATTENTION_CSS\n document.head.appendChild(style)\n}\n","/**\n * 设置面板:挂在中心列(会话列)里,由侧边栏「提醒」入口开合。\n * 只管本模块 —— 三类提醒开关、提醒音(音量 / 音色 / 试听 / 音源)、活动悬浮球,\n * 外加系统通知权限状态与「最近一条提醒 / 活跃会话」的只读摘要。\n * @module dsh-session-attention/client/AttentionPanel\n */\n\nimport { useState, useSyncExternalStore, type ReactElement } from 'react'\nimport { attentionStore } from './attention-store.ts'\nimport { STYLE_META } from './styles.ts'\nimport { previewSound, soundSourceLabel } from './sound.ts'\nimport { DEFAULT_SETTINGS, REMINDER_STYLES, type ReminderStyle } from '../types.ts'\n\nconst PHASE_LABEL: Record<string, string> = { thinking: '思考中', tool: '执行工具', done: '等待你' }\n\nfunction formatTime(ms: number): string {\n const d = new Date(ms)\n const pad = (n: number): string => String(n).padStart(2, '0')\n return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`\n}\n\nexport function AttentionPanel(): ReactElement {\n const { data, error, activity, latest } = useSyncExternalStore(attentionStore.subscribe, attentionStore.getSnapshot)\n const settings = data?.settings ?? DEFAULT_SETTINGS\n const { attention, ball } = settings\n\n // 滑杆用本地态拖动、松手才落盘,避免每移动一格都打一次接口。\n const [volume, setVolume] = useState<number | null>(null)\n const shownVolume = volume ?? Math.round(attention.volume * 100)\n const commitVolume = (): void => {\n if (volume === null) return\n void attentionStore.setAttention({ volume: volume / 100 })\n setVolume(null)\n }\n\n const [permission, setPermission] = useState<string>(\n typeof Notification === 'undefined' ? 'unsupported' : Notification.permission,\n )\n const requestPermission = (): void => {\n if (typeof Notification === 'undefined') return\n void Notification.requestPermission().then((value) => setPermission(value)).catch(() => { /* 拒绝静默 */ })\n }\n\n const [sourceLabel, setSourceLabel] = useState<string>(soundSourceLabel())\n const refreshSource = (): void => { setSourceLabel(soundSourceLabel()) }\n\n return (\n <div className=\"todo-panel\">\n <div className=\"todo-header\">\n <span className=\"todo-title\">🔔 DSH 会话提醒</span>\n <span className=\"todo-count\">{activity.length > 0 ? `${activity.length} 个活跃会话` : '无活跃会话'}</span>\n <button\n type=\"button\"\n className=\"todo-btn\"\n title=\"关闭面板(Esc 也可以)\"\n aria-label=\"关闭\"\n onClick={() => attentionStore.setPanel(false)}\n >\n ✕\n </button>\n </div>\n\n {error !== null ? <div className=\"todo-empty\">连接 host 失败:{error}</div> : null}\n\n <div className=\"todo-settings-group\">💬 什么时候提醒你(只在切出 DSH 时弹)</div>\n <div className=\"todo-settings\">\n <label className=\"todo-check\" title=\"agent 完成一轮回复时提醒\">\n <input type=\"checkbox\" checked={attention.onTurnEnd} onChange={(e) => void attentionStore.setAttention({ onTurnEnd: e.target.checked })} />\n 完成一轮\n </label>\n <label className=\"todo-check\" title=\"agent 用 ask_user_question 提问时提醒\">\n <input type=\"checkbox\" checked={attention.onQuestion} onChange={(e) => void attentionStore.setAttention({ onQuestion: e.target.checked })} />\n 提问\n </label>\n <label className=\"todo-check\" title=\"有审批请求(approve/deny)时提醒\">\n <input type=\"checkbox\" checked={attention.onApproval} onChange={(e) => void attentionStore.setAttention({ onApproval: e.target.checked })} />\n 审批\n </label>\n </div>\n\n <div className=\"todo-settings-group\">🔊 提醒音</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">响铃</span>\n <label className=\"todo-check\" title=\"关掉后只有系统通知,没有页面内音效\">\n <input type=\"checkbox\" checked={attention.sound} onChange={(e) => void attentionStore.setAttention({ sound: e.target.checked })} />\n 出声\n </label>\n <span className=\"todo-settings-hint\">音量</span>\n <input\n className=\"todo-range\"\n type=\"range\"\n min={0}\n max={100}\n step={5}\n value={shownVolume}\n onChange={(e) => setVolume(Number(e.target.value))}\n onPointerUp={commitVolume}\n onKeyUp={commitVolume}\n onBlur={commitVolume}\n title=\"会话提醒音量\"\n />\n <span className=\"todo-settings-hint\">{shownVolume}%</span>\n <span className=\"todo-settings-hint\">音色</span>\n <select\n className=\"todo-select\"\n value={attention.tone}\n onChange={(e) => void attentionStore.setAttention({ tone: e.target.value as ReminderStyle })}\n title=\"用哪种音色\"\n >\n {REMINDER_STYLES.map((style) => (\n <option key={style} value={style}>{STYLE_META[style].icon} {STYLE_META[style].label}</option>\n ))}\n </select>\n <button\n type=\"button\"\n className=\"todo-btn\"\n onClick={() => { previewSound(attention.tone, shownVolume / 100, 1); window.setTimeout(refreshSource, 400) }}\n title=\"试听提醒音\"\n >\n 🔈 试听\n </button>\n <button\n type=\"button\"\n className=\"todo-btn\"\n title=\"在文件管理器里打开音效文件所在目录\"\n onClick={() => void attentionStore.openSoundDir()}\n >\n 📂 打开文件夹\n </button>\n <span className=\"todo-settings-hint\">音源 {sourceLabel}</span>\n <span className=\"todo-settings-hint\">自定义音效:$DSH_HOME/session-attention/session.mp3(旧位置 todo-reminder/session.mp3 仍有效)</span>\n </div>\n\n <div className=\"todo-settings-group\">🖥 系统通知</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">权限</span>\n <span className=\"todo-settings-hint\">\n {permission === 'granted' ? '已授权' : permission === 'denied' ? '已被拒绝(需在浏览器/系统设置里放开)' : permission === 'unsupported' ? '当前环境不支持' : '未授权'}\n </span>\n {permission === 'default' ? (\n <button type=\"button\" className=\"todo-btn\" onClick={requestPermission}>开启系统通知</button>\n ) : null}\n </div>\n\n <div className=\"todo-settings-group\">🫧 活动悬浮球</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">悬浮球</span>\n <label className=\"todo-check\" title=\"显示 DSH 活跃会话的悬浮球;关闭后页面上不再出现\">\n <input type=\"checkbox\" checked={ball.enabled} onChange={(e) => void attentionStore.setBall({ enabled: e.target.checked })} />\n 显示\n </label>\n <label className=\"todo-check\" title=\"拖动松手后自动吸附到最近的左右边缘\">\n <input type=\"checkbox\" checked={ball.snapToEdge} onChange={(e) => void attentionStore.setBall({ snapToEdge: e.target.checked })} />\n 边缘吸附\n </label>\n </div>\n\n <div className=\"todo-settings-group\">📋 最近</div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">最近提醒</span>\n <span className=\"todo-settings-hint\">\n {latest === null ? '还没有提醒过' : `${formatTime(latest.at)} ${latest.title}${latest.sessionTitle !== undefined ? ' · ' + latest.sessionTitle : ''}`}\n </span>\n </div>\n <div className=\"todo-settings\">\n <span className=\"todo-settings-label\">活跃会话</span>\n <span className=\"todo-settings-hint\">\n {activity.length === 0\n ? '空闲'\n : activity.map((item) => `${item.sessionTitle ?? item.sessionId.slice(0, 8)}:${PHASE_LABEL[item.phase] ?? item.phase}${item.tool !== undefined ? '(' + item.tool + ')' : ''}`).join(' | ')}\n </span>\n </div>\n </div>\n )\n}\n","/**\n * 官方面板座位注册(DSH 0.2 起)。\n *\n * 以前是 DOM 注入:自己造一个 <button> 塞进侧边栏、再造一个绝对定位的浮层盖住对话列。\n * 那样行框、图标、高亮、折叠态、面板切换全得自己模仿,别插件一动布局就会错位甚至盖满界面。\n *\n * 现在改成往外壳自己的座位注册:\n * sidebar.panellist —— 只画一个图标,行框/标签/高亮/折叠/点击切换都归外壳;\n * main —— 页面只在「本面板被选中」时才由外壳挂载,没选中时对话列完全不受影响。\n * 两者都走 slots.inject:外壳没声明该座位就什么都不做(不报错、不拖垮启动)。\n * @module dsh-session-attention/client\n */\n\nimport type { ReactElement } from 'react'\nimport { AttentionPanel } from './AttentionPanel.tsx'\nimport { attentionStore } from './attention-store.ts'\n\n/** 面板 id:侧边栏行与主视图页面共用它,也是 layout.selectPanel 的取值。 */\nexport const PANEL_ID = 'session-attention'\n\n/** 行序(外壳:插件页 0、定时页 10、任务看板 20)。 */\nconst PANEL_ORDER = 30\n\n/** 座位接口(外部插件只能按约定结构调用,故这里用最小结构类型)。 */\ninterface SlotService {\n inject(key: string, callback: () => () => void): () => void\n register(options: Record<string, unknown>, component: unknown): () => void\n}\n\n/** 布局服务:选中哪个面板 + 当前选中项的可订阅快照。 */\ninterface LayoutService {\n selectPanel?: (id: string | null) => void\n panelInfo?: {\n subscribe(listener: () => void): () => void\n getSnapshot(): { activePanelId: unknown }\n }\n}\n\n/** 客户端上下文里本插件用到的两样东西。 */\nexport interface SeatContext {\n slots?: SlotService\n get?: (name: string) => unknown\n}\n\n/** 外壳是否提供官方座位(旧版外壳没有,于是退回 DOM 注入)。 */\nexport function seatsAvailable(ctx: SeatContext): boolean {\n const slots = ctx.slots\n return slots !== undefined && typeof slots.inject === 'function' && typeof slots.register === 'function'\n}\n\n/** 行图标:外壳给尺寸与选中态,本组件只画 glyph。 */\nfunction PanelIcon({ size }: { size: number, active: boolean }): ReactElement {\n return (\n <svg data-dsh-panel-entry={PANEL_ID} width={size} height={size} viewBox=\"0 0 16 16\" fill=\"none\"\n stroke=\"currentColor\" strokeWidth=\"1.3\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 2.2a4 4 0 0 0-4 4v2.4L3 11h10L11.9 8.6V6.2a4 4 0 0 0-4-4Z\" /><path d=\"M6.6 12.8a1.5 1.5 0 0 0 2.8 0\" />\n </svg>\n )\n}\n\n/** 主视图页面:外壳只在本面板被选中时挂载它。 */\nexport function PanelPage(): ReactElement {\n return (\n <div className=\"todo-panel-page\" data-dsh-att-view=\"\" data-dsh-plugin=\"session-attention\">\n <AttentionPanel />\n </div>\n )\n}\n\n/** 注册侧边栏行 + 主视图页面。 */\nexport function registerPanel(ctx: SeatContext): () => void {\n const slots = ctx.slots!\n const disposers: Array<() => void> = []\n disposers.push(slots.inject('sidebar.panellist', () => slots.register({\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: PANEL_ORDER,\n label: () => '提醒',\n }, PanelIcon)))\n disposers.push(slots.inject('main', () => slots.register({ name: 'main', key: PANEL_ID }, PanelPage as never)))\n return () => { for (const dispose of disposers.splice(0)) { try { dispose() } catch { /* 忽略清理异常 */ } } }\n}\n\n/**\n * 把本插件的面板开关与外壳「当前选中的面板」双向对齐:\n * 外壳那边切换 → 同步进 store(✕ / Esc 才有意义);\n * store 这边关闭(点 ✕)→ 让外壳切回对话。两边都做等值判断,不会互相触发成环。\n */\nexport function syncPanelWithLayout(ctx: SeatContext): () => void {\n const layout = ctx.get?.('layout') as LayoutService | undefined\n const info = layout?.panelInfo\n if (info === undefined) return () => { /* 没有布局服务:面板开关只影响自身 */ }\n const pull = (): void => {\n const open = info.getSnapshot().activePanelId === PANEL_ID\n if (attentionStore.getSnapshot().panelOpen !== open) attentionStore.setPanel(open)\n }\n const unsubscribe = info.subscribe(pull)\n const push = attentionStore.subscribe(() => {\n const open = attentionStore.getSnapshot().panelOpen\n const active = info.getSnapshot().activePanelId === PANEL_ID\n if (open && !active) layout?.selectPanel?.(PANEL_ID)\n else if (!open && active) layout?.selectPanel?.(null)\n })\n pull()\n return () => { unsubscribe(); push() }\n}\n","/**\n * 侧边栏入口「提醒」:DOM 注入(shell 无对外侧边栏 slot),MutationObserver 自愈。\n * 与 task-board / ssh / 待办 的入口共处同一家族块,位置稳定、互斥开合。\n * @module dsh-session-attention/client/sidebar-entry\n */\n\nimport { attentionStore } from './attention-store.ts'\n\n/** 入口行稳定标识。 */\nconst ENTRY_SELECTOR = '[data-dsh-att-entry]'\n\n/**\n * 当前存活实例的清理函数。\n *\n * 客户端被重复 apply(HMR / 双重注册)时,旧实例的 MutationObserver 会在新实例接管后\n * 把旧元素重新插回去 —— 表现就是侧边栏出现两个入口。这里让新实例先干掉旧实例。\n */\nlet activeDispose: (() => void) | undefined\n\nconst ICON = `<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M4.4 6.6a3.6 3.6 0 1 1 7.2 0c0 2.1.5 3.2 1 3.9H3.4c.5-.7 1-1.8 1-3.9Z\"/><path d=\"M6.7 12.6a1.4 1.4 0 0 0 2.6 0\"/></svg>`\n\nfunction sidebarRoot(): HTMLElement | undefined {\n const column = document.querySelector<HTMLElement>('[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]')\n if (column === null) return undefined\n const logoOwner = column.querySelector<HTMLElement>('[class*=\"logoRow\"]')?.parentElement\n return logoOwner ?? (column.firstElementChild as HTMLElement | undefined)\n}\n\n/**\n * 定位「新建会话」按钮(DSH 0.1.6 起它是 logoRow 的兄弟节点,不再是其子元素);\n * 三级回退:全局 → root 内 → root 的第一个 button。\n */\nfunction newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {\n const scopes: ParentNode[] = [document, root]\n for (const scope of scopes) {\n const found = scope.querySelector<HTMLButtonElement>('button[class*=\"newSession\"]')\n if (found !== null) return found\n }\n for (const child of root.children) {\n if (child.tagName === 'BUTTON') return child as HTMLButtonElement\n }\n return undefined\n}\n\n/** `newSession` 按钮的宿主父元素(入口行的实际插入容器)。 */\nfunction entryHost(button: HTMLButtonElement, fallback: HTMLElement): HTMLElement {\n const parent = button.parentElement\n return parent ?? button.closest<HTMLElement>('[class*=\"logoRow\"]')?.parentElement ?? fallback\n}\n\nfunction createEntry(): HTMLButtonElement {\n const entry = document.createElement('button')\n entry.type = 'button'\n entry.dataset.dshAttEntry = ''\n entry.setAttribute('aria-label', '提醒')\n entry.innerHTML = `<span class=\"todo-entry-icon\">${ICON}</span><span class=\"todo-entry-label\">提醒</span>`\n entry.addEventListener('click', () => {\n attentionStore.togglePanel()\n })\n return entry\n}\n\n/** 家族锚点:与这些兄弟入口排在同一处。 */\nconst FAMILY_SELECTOR = '[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-todo-entry], [data-dsh-att-entry]'\n\nfunction placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {\n // 过期实例绝不抢插。守卫必须在这里而不是只在 tryPlace:rootObserver 也直接调本函数,\n // 少了它,旧实例会把入口插回来 —— 表现就是侧边栏多出一个入口。\n const owners = document.querySelectorAll(ENTRY_SELECTOR)\n if (owners.length > 1 || (owners.length === 1 && owners[0] !== entry)) return false\n const button = newSessionButton(root)\n if (button === undefined) return false\n const host = entryHost(button, root)\n if (entry.parentElement !== host) {\n const family = Array.from(host.children).filter(\n (el): el is HTMLElement => el instanceof HTMLElement && el.matches(FAMILY_SELECTOR),\n )\n const anchor = family.length > 0 ? family[0] : button.nextElementSibling\n host.insertBefore(entry, anchor)\n }\n return true\n}\n\n/** 挂载入口行并绑定激活态高亮。 */\nexport function mountSidebarEntry(): () => void {\n // 先干掉上一个实例(连同它的 observer),否则两个入口会同时存在。\n activeDispose?.()\n activeDispose = undefined\n // 幂等:清理旧实例残留(客户端 bundle 被重复加载时)。\n document.querySelectorAll(ENTRY_SELECTOR).forEach((el) => el.remove())\n\n const entry = createEntry()\n let root: HTMLElement | undefined\n let placed = false\n\n const tryPlace = (): void => {\n // 若场上已有别的实例的入口(跨 bundle 重复加载),本实例作废,绝不抢插。\n const owners = document.querySelectorAll(ENTRY_SELECTOR)\n if (owners.length > 1 || (owners.length === 1 && owners[0] !== entry)) return\n if (root !== undefined && !root.isConnected) {\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n if (placed) {\n if (document.body.contains(entry)) return\n rootObserver.disconnect()\n root = undefined\n placed = false\n }\n root ??= sidebarRoot()\n if (root === undefined) return\n placed = placeEntry(root, entry)\n if (placed) rootObserver.observe(root, { childList: true, subtree: true })\n }\n\n /**\n * 清掉场上不属于本实例的入口。\n * 上限 20 次:万一遇到更老的、没有守卫的实例反复插回,也不会陷入无限循环。\n */\n let sweepBudget = 20\n const sweepForeign = (): void => {\n if (sweepBudget <= 0) return\n for (const el of document.querySelectorAll(ENTRY_SELECTOR)) {\n if (el === entry) continue\n el.remove()\n sweepBudget -= 1\n }\n }\n\n const waitObserver = new MutationObserver(() => { sweepForeign(); tryPlace() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const rootObserver = new MutationObserver(() => {\n if (root === undefined || !root.isConnected) {\n placed = false\n tryPlace()\n return\n }\n sweepForeign()\n if (!root.contains(entry)) placed = placeEntry(root, entry)\n })\n\n const syncActive = (): void => {\n if (attentionStore.getSnapshot().panelOpen) entry.dataset.active = 'true'\n else delete entry.dataset.active\n }\n const unsubscribe = attentionStore.subscribe(syncActive)\n syncActive()\n\n tryPlace()\n\n const dispose = (): void => {\n waitObserver.disconnect()\n rootObserver.disconnect()\n unsubscribe()\n entry.remove()\n if (activeDispose === dispose) activeDispose = undefined\n }\n activeDispose = dispose\n return dispose\n}\n","/**\n * dsh-session-attention 浏览器半:注入样式,挂载侧边栏「提醒」入口、设置面板、\n * 全局提醒层(含活动悬浮球)。失败策略:DOM 挂载异常只记录、不抛出\n * (外部插件不得拖垮 GUI 启动)。\n * @module dsh-session-attention/client\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { attentionStore } from './attention-store.ts'\nimport { AttentionLayer } from './AttentionLayer.tsx'\nimport { AttentionPanel } from './AttentionPanel.tsx'\nimport { resolveSessionRow, setSessionOpener, setSessionTitleResolver, type SessionTarget } from './session-nav.ts'\nimport { registerPanel, seatsAvailable, syncPanelWithLayout } from './panel-seat.tsx'\nimport { mountSidebarEntry } from './sidebar-entry.ts'\nimport { injectStyles } from './styles.ts'\n\n/** 跨插件面板互斥事件(与 task-board / ssh / 待办 同契约)。 */\nconst ACTIVATE_EVENT = 'dsh-panel-activate'\nconst PANEL_NAME = 'attention'\nconst ACTIVE_ATTR = 'data-dsh-attention-active'\nconst SIBLING_ATTRS = ['data-dsh-todo-active', 'data-dsh-ssh-active', 'data-dsh-taskboard-active']\n\n/**\n * 中心列(对话列)候选,**按优先级逐个试**。\n * 注意:querySelector 的逗号列表是「文档顺序优先」而不是「选择器顺序优先」——别的插件的外层\n * 容器只要类名带 centerCol 就会被选中,而 inset:0 的面板挂上去会盖满整个 UI。\n * 0.1.6 起 data-pane 标记被移除,回退到 centerCol / _centerCol 类名。\n */\nconst COLUMN_CANDIDATES = ['[data-pane=\"conversation\"]', '[class*=\"centerCol\"]', '[class*=\"_centerCol\"]']\n\n/** 宿主标记:定位与隐藏兄弟都只作用于自己挂载的这个元素,避免误伤其他插件的同名容器。 */\nconst HOST_ATTR = 'data-dsh-att-host'\n\n/** 应用外壳判定:含侧边栏(或本插件的侧边栏入口)的容器不是对话列,挂上去必然盖满整个 UI。 */\nfunction looksLikeAppShell(el: HTMLElement): boolean {\n if (el === document.body || el === document.documentElement) return true\n return el.querySelector('[data-pane=\"sidebar\"], [class*=\"sidebarCol\"], [data-dsh-todo-entry], [data-dsh-att-entry]') !== null\n}\n\n/** 找对话列:按优先级 + 外壳校验;找不到就返回 undefined(宁可不挂,也不盖满 UI)。 */\nfunction findConversationColumn(): HTMLElement | undefined {\n for (const selector of COLUMN_CANDIDATES) {\n for (const el of document.querySelectorAll<HTMLElement>(selector)) {\n if (!looksLikeAppShell(el)) return el\n }\n }\n return undefined\n}\n\n/**\n * 面板挂载:容器插进中心列,打开时占满它(独立页面式,与任务看板 / SSH 一致),\n * 中心列其余内容暂时隐藏;关闭后立刻回到对话。开合由 store 的 panelOpen 驱动。\n */\nfunction mountPanel(): () => void {\n document.querySelectorAll('[data-dsh-att-view]').forEach((el) => el.remove())\n document.querySelectorAll('[' + HOST_ATTR + ']').forEach((el) => el.removeAttribute(HOST_ATTR))\n\n let root: Root | undefined\n let container: HTMLDivElement | undefined\n\n /** 卸掉容器与宿主标记:宿主被换、面板关闭、插件卸载都会走这里。 */\n const teardown = (): void => {\n try { root?.unmount() } catch { /* 忽略清理异常 */ }\n root = undefined\n container?.remove()\n container = undefined\n document.querySelectorAll('[' + HOST_ATTR + ']').forEach((el) => el.removeAttribute(HOST_ATTR))\n }\n\n const ensure = (): void => {\n // 只在打开时挂载:关掉后 DOM 里不留残留,插件被停用时也就不会有一块东西盖住界面。\n if (!attentionStore.getSnapshot().panelOpen) { if (container !== undefined) teardown(); return }\n // 自愈:外壳/别的插件重渲染会把宿主换掉,此时容器已是游离节点,必须重建。\n if (container !== undefined && container.isConnected) return\n teardown()\n const column = findConversationColumn()\n if (column === undefined) return\n column.setAttribute(HOST_ATTR, '')\n container = document.createElement('div')\n container.dataset.dshAttView = ''\n column.appendChild(container)\n root = createRoot(container)\n root.render(<AttentionPanel />)\n }\n\n const waitObserver = new MutationObserver(() => { ensure() })\n waitObserver.observe(document.body, { childList: true, subtree: true })\n\n const applyActive = (): void => {\n if (attentionStore.getSnapshot().panelOpen) {\n for (const attr of SIBLING_ATTRS) document.documentElement.removeAttribute(attr)\n document.documentElement.setAttribute(ACTIVE_ATTR, '')\n document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))\n } else {\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n }\n ensure()\n }\n const close = (): void => { if (attentionStore.getSnapshot().panelOpen) attentionStore.setPanel(false) }\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail !== PANEL_NAME) close()\n }\n const onKeyDown = (event: KeyboardEvent): void => { if (event.key === 'Escape') close() }\n const SIDEBAR_ROW_SELECTOR = '[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]'\n const onClickSidebarRow = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null\n if (target === null) return\n // 点自己的侧边栏入口不算「点了会话行」:本监听在捕获阶段先跑,若在这里 close(),\n // 紧接着入口自身的 click 又 togglePanel() 开回来 —— 表现就是「点了关不掉」。\n if (target.closest('[data-dsh-att-entry]') !== null) return\n if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) close()\n }\n\n document.addEventListener('keydown', onKeyDown)\n document.addEventListener('click', onClickSidebarRow, true)\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n const unsubscribe = attentionStore.subscribe(applyActive)\n applyActive()\n ensure()\n\n return () => {\n document.removeEventListener('keydown', onKeyDown)\n document.removeEventListener('click', onClickSidebarRow, true)\n document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)\n waitObserver.disconnect()\n unsubscribe()\n document.documentElement.removeAttribute(ACTIVE_ATTR)\n teardown()\n }\n}\n/** 全局提醒层挂载(body 上的 React 根,跨会话常驻;内含活动悬浮球)。 */\nfunction mountLayer(): () => void {\n document.querySelectorAll('[data-dsh-att-layer]').forEach((el) => el.remove())\n\n const container = document.createElement('div')\n container.dataset.dshAttLayer = ''\n document.body.appendChild(container)\n const root = createRoot(container)\n root.render(<AttentionLayer />)\n return () => {\n root.unmount()\n container.remove()\n }\n}\n\n/**\n * 声明依赖 `sessions` 服务:它保证本插件在客户端会话控制器就绪之后才 apply,\n * 侧边栏/主视图此时已可被定位(跳转本身走 DOM 委托,不调用该服务)。\n */\nexport const inject = ['sessions', 'uiWorkspace']\n\n/** 客户端上下文最小接口。 */\nexport interface AttentionClientContext {\n readonly sessions?: unknown\n /** 官方座位服务(0.2 起);缺席时退回 DOM 注入。 */\n readonly slots?: { inject(key: string, callback: () => () => void): () => void, register(options: Record<string, unknown>, component: unknown): () => void }\n /** cordis 上下文取值(本插件只取 layout)。 */\n readonly get?: (name: string) => unknown\n /**\n * ui-workspace 客户端服务(DSH 0.2 起的官方主视图切换入口)。\n * 缺席时退回「委托侧边栏行点击」的老办法。\n */\n readonly uiWorkspace?: { openSession?: (target: string) => void }\n}\n\nexport function apply(ctx: AttentionClientContext): () => void {\n injectStyles()\n\n const disposers: Array<() => void> = []\n\n // 会话标题:DSH 0.2 起 host 侧拿不到标题,真正的来源是客户端会话列表(侧边栏同源)。\n disposers.push(setSessionTitleResolver((sessionId: string): string | undefined => {\n const list = (ctx.sessions as {\n list?: { getSnapshot?: () => { byId?: Record<string, { title?: string, blank?: boolean }> } }\n } | undefined)?.list\n const row = list?.getSnapshot?.()?.byId?.[sessionId]\n if (row === undefined || row.blank === true) return undefined\n const title = typeof row.title === 'string' ? row.title.trim() : ''\n return title === '' ? undefined : title\n }))\n\n // 先登记跳转能力,再挂 UI,保证首帧就知道会话可点。\n disposers.push(setSessionOpener((target: SessionTarget): boolean | Promise<boolean> => {\n // 首选:官方 uiWorkspace.openSession(sessionId)。它内部走 replaceMain 并自行管理\n // 主视图引用,因此**不要求那个会话此刻能在侧边栏里被看见**(工作区收起也行)。\n // 服务未注册时读取本身也可能抛,所以连读取一起兜住。\n let service: { openSession?: (target: string) => void } | undefined\n try { service = ctx.uiWorkspace } catch { service = undefined }\n if (service !== undefined && typeof service.openSession === 'function') {\n try {\n service.openSession(target.sessionId)\n console.info('[dsh-session-attention] 已走官方接口跳转会话', target.sessionId)\n return true\n } catch (error) {\n console.warn('[dsh-session-attention] uiWorkspace.openSession 失败,退回侧边栏点击', error)\n }\n }\n // 兜底:委托官方侧边栏行点击(服务缺席 / 旧版本时)。\n return (async (): Promise<boolean> => {\n for (let attempt = 0; attempt < 2; attempt += 1) {\n const row = resolveSessionRow(target)\n if (row !== undefined) {\n const clickable = row.querySelector<HTMLElement>('button, [role=\"button\"]') ?? row\n clickable.click()\n return true\n }\n await new Promise((resolve) => { window.setTimeout(resolve, 16) })\n }\n console.warn('[dsh-session-attention] 侧边栏里定位不到该会话行,跳转失败', target.sessionId, target.title)\n return false\n })()\n }))\n try {\n // 0.2 起优先用外壳自己的面板座位:行框/高亮/折叠/切换都由外壳拥有,\n // 页面只在被选中时挂载 —— 既不会盖住别的页面,也不会被别的插件带歪样式。\n if (seatsAvailable(ctx)) {\n disposers.push(registerPanel(ctx))\n disposers.push(syncPanelWithLayout(ctx))\n } else {\n // 旧版外壳没有 sidebar.panellist / main 座位,退回 DOM 注入。\n disposers.push(mountSidebarEntry())\n disposers.push(mountPanel())\n }\n disposers.push(mountLayer())\n } catch (error) {\n console.error('[dsh-session-attention] mount failed:', error)\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) {\n try { dispose() } catch { /* 忽略清理异常 */ }\n }\n }\n}\n"],"mappings":"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"}
|