dsh-desktop-enhance 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # dsh-desktop-enhance
2
2
 
3
- 为 **DeepSeek Harness 桌面版**(DSH Desktop `0.2.0-rc.2`)补五个界面能力的插件。**自用**。
3
+ 为 **DeepSeek Harness 桌面版**(DSH Desktop `0.2.0-rc.2`)补六个界面能力的插件。**自用**。
4
4
 
5
- ## 五个功能
5
+ ## 六个功能
6
6
 
7
7
  ### 页内查找
8
8
 
@@ -90,6 +90,15 @@ Web 版(浏览器里打开的那种)键位是 **`Cmd+Shift+.`** —— `Cmd+
90
90
 
91
91
  算不出精确区间的位置**不显示数字**,不会拿一个 `0s` 糊上去。
92
92
 
93
+ ### 会话信息
94
+
95
+ 在主对话区右上角悬浮展示一个可折叠的手风琴看板(标题为 **「会话信息」**):
96
+
97
+ - **已注入 Skill 分节**:展示当前会话活跃上下文中已注入的 Skill 列表、触发方式(`用户触发` / `Agent触发`)以及对应的轮次号(`#N`);还没有任何 Skill 时显示 `0` 与「暂无已注入 Skill」提示
98
+ - **点击预览**:点击任意一条 Skill,直接打开宿主内置的右侧栏 Skill 详情预览(`openSkill`)
99
+ - **折叠与展开**:点击顶栏可把整张卡片收起成一枚紧凑胶囊(`会话信息 · Skill N`),点击分节头可单独收起/展开该分节
100
+ - **全量 + 实时**:实时监听前端会话事件流,同时向 Host 查询完整会话日志基线,即使几百轮的长会话只加载了尾部窗口,第 1 轮注入的 Skill 也不会漏掉;被上下文压缩或裁剪替换掉的节点会自动剔除
101
+
93
102
  ## 安装
94
103
 
95
104
  从 npm 装:
@@ -123,11 +132,11 @@ dsh plugin --profile desktop update dsh-desktop-enhance
123
132
 
124
133
  ## 开关
125
134
 
126
- 五个功能各有一个开关,在**本插件自己的页面**上:
135
+ 六个功能各有一个开关,在**本插件自己的页面**上:
127
136
 
128
137
  > 侧栏 **插件** → 插件列表里选中 **`dsh-desktop-enhance`** → 页面里的 **「UI 增强」** 一栏
129
138
 
130
- 五个开关:**页内查找** / **轮次编号** / **压缩行高亮** / **建议** / **每步耗时**。默认全开,装完与之前行为一致,除非你自己去关。
139
+ 六个开关:**页内查找** / **轮次编号** / **压缩行高亮** / **建议** / **每步耗时** / **会话信息**。默认全开,装完与之前行为一致,除非你自己去关。
131
140
 
132
141
  「建议」关掉时,它那三个参数(模型 / 推理强度 / 指令模板)也会一并收起 —— 功能关着时它们不起作用。
133
142
 
package/lib/client.js CHANGED
@@ -19,6 +19,14 @@ window.__ModuleLoader__.load({
19
19
  "suggest.loading": "正在想下一轮…",
20
20
  "suggest.dismiss": "收起建议",
21
21
  "suggest.failed": "没写进输入框(宿主正忙或草稿有冲突),可以再点一次。",
22
+ "sessionInfo.title": "会话信息",
23
+ "sessionInfo.collapse": "收起会话信息",
24
+ "sessionInfo.expand": "展开会话信息",
25
+ "sessionInfo.skills.title": "已注入 Skill",
26
+ "sessionInfo.skills.short": "Skill",
27
+ "sessionInfo.skills.empty": "暂无已注入 Skill",
28
+ "sessionInfo.trigger.user": "用户触发",
29
+ "sessionInfo.trigger.agent": "Agent触发",
22
30
  "settings.title": "UI 增强",
23
31
  "settings.description": "逐项开关本插件添加的界面增强。关闭后恢复 DSH 原生外观。",
24
32
  "settings.findBar": "页内查找",
@@ -31,6 +39,8 @@ window.__ModuleLoader__.load({
31
39
  "settings.suggest.hint": "每轮结束后问一次模型,把 1 到 3 条可能的下一轮提示词摆在输入框上方,点一下就写进草稿",
32
40
  "settings.stepDuration": "每步耗时",
33
41
  "settings.stepDuration.hint": "紧跟每一行的文字显示这一步的开始时刻与耗时(如 14:32 · 23s):过程组头是整组、展开后的工具调用与思考行是它自己那一段;正在跑的那一行每秒更新",
42
+ "settings.sessionInfo": "会话信息",
43
+ "settings.sessionInfo.hint": "在主对话区右上角悬浮展示当前会话的信息看板(如已注入的 Skill、触发方式与轮次),点击条目可打开 Skill 预览",
34
44
  "settings.suggestModel": "建议用哪个模型",
35
45
  "settings.suggestModel.hint": "留空则跟随当前会话的模型;建议选一个非推理的轻量模型,否则每条建议都要等它想完",
36
46
  "settings.suggestModel.follow": "跟随当前会话",
@@ -65,6 +75,14 @@ window.__ModuleLoader__.load({
65
75
  "suggest.loading": "Thinking of the next round…",
66
76
  "suggest.dismiss": "Dismiss suggestions",
67
77
  "suggest.failed": "Nothing was written into the composer (the host is busy, or the draft conflicted). Try again.",
78
+ "sessionInfo.title": "Session info",
79
+ "sessionInfo.collapse": "Collapse session info",
80
+ "sessionInfo.expand": "Expand session info",
81
+ "sessionInfo.skills.title": "Active skills",
82
+ "sessionInfo.skills.short": "Skill",
83
+ "sessionInfo.skills.empty": "No active skills",
84
+ "sessionInfo.trigger.user": "User",
85
+ "sessionInfo.trigger.agent": "Agent",
68
86
  "settings.title": "UI enhancements",
69
87
  "settings.description": "Switch each interface enhancement this plugin adds. Turning one off restores the native DSH look.",
70
88
  "settings.findBar": "Find in conversation",
@@ -77,6 +95,8 @@ window.__ModuleLoader__.load({
77
95
  "settings.suggest.hint": "Asks the model once per turn and offers 1–3 likely next-round prompts above the composer; a click writes one into your draft",
78
96
  "settings.stepDuration": "Step durations",
79
97
  "settings.stepDuration.hint": "Shows when each step started and how long it took, right after that row’s own text (e.g. 14:32 · 23s): the whole group on a process-group header, its own span on every expanded tool call and thinking row; running rows update every second",
98
+ "settings.sessionInfo": "Session info",
99
+ "settings.sessionInfo.hint": "Shows a floating Session Info inspector in the top-right corner of the conversation view (active skills, trigger source, and turn); click an item to open its skill preview",
80
100
  "settings.suggestModel": "Model for suggestions",
81
101
  "settings.suggestModel.hint": "Empty follows the current session model. Prefer a lightweight non-reasoning model, or every suggestion waits for it to finish thinking",
82
102
  "settings.suggestModel.follow": "Follow the current session",
@@ -361,14 +381,16 @@ window.__ModuleLoader__.load({
361
381
  turnPrefix: "data-desktop-enhance-off-turn-prefix",
362
382
  compactionRow: "data-desktop-enhance-off-compaction-row",
363
383
  suggest: "data-desktop-enhance-off-suggest",
364
- stepDuration: "data-desktop-enhance-off-step-duration"
384
+ stepDuration: "data-desktop-enhance-off-step-duration",
385
+ sessionInfo: "data-desktop-enhance-off-session-info"
365
386
  };
366
387
  const FIELDS = [
367
388
  "findBar",
368
389
  "turnPrefix",
369
390
  "compactionRow",
370
391
  "suggest",
371
- "stepDuration"
392
+ "stepDuration",
393
+ "sessionInfo"
372
394
  ];
373
395
  const ALL_FIELDS = [...FIELDS, ...TEXT_FIELDS];
374
396
  function flagOn(value, field) {
@@ -434,6 +456,7 @@ window.__ModuleLoader__.load({
434
456
  //#region src/shared/route.ts
435
457
  const SUGGEST_ROUTE = "/api/desktop-enhance/suggest";
436
458
  const SUGGEST_MODELS_ROUTE = "/api/desktop-enhance/suggest-models";
459
+ const SESSION_INFO_ROUTE = "/api/desktop-enhance/session-info";
437
460
  //#endregion
438
461
  //#region src/client/settings/catalog.ts
439
462
  function parseModelCatalog(body) {
@@ -1939,8 +1962,466 @@ window.__ModuleLoader__.load({
1939
1962
  ]);
1940
1963
  }
1941
1964
  //#endregion
1965
+ //#region src/shared/session-info.ts
1966
+ const SKILL_CONTENT_RE = /^\s*<skill_content\s+name="([^"]+)">/;
1967
+ function asRecord(value) {
1968
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1969
+ }
1970
+ function nonEmptyString(value) {
1971
+ return typeof value === "string" && value !== "" ? value : void 0;
1972
+ }
1973
+ function nonNegativeInt(value) {
1974
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1975
+ }
1976
+ function positiveInt(value) {
1977
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
1978
+ }
1979
+ function firstTextOf(content) {
1980
+ if (!Array.isArray(content)) return "";
1981
+ for (const item of content) {
1982
+ const block = asRecord(item);
1983
+ if (block?.type === "text") return typeof block.text === "string" ? block.text : "";
1984
+ }
1985
+ return "";
1986
+ }
1987
+ function toolSkillName(message) {
1988
+ if (message === null || message.isError === true) return void 0;
1989
+ return SKILL_CONTENT_RE.exec(firstTextOf(message.content))?.[1];
1990
+ }
1991
+ function invokedSkillName(type, data) {
1992
+ const source = asRecord((type === "developer/message" ? asRecord(data?.message) : data)?.source);
1993
+ if (source?.kind !== "skill-invocation") return void 0;
1994
+ return nonEmptyString(source.name);
1995
+ }
1996
+ function replaceRangeOf(surfaceOp) {
1997
+ const op = asRecord(surfaceOp);
1998
+ if (op?.op !== "replace") return null;
1999
+ const startSeq = nonNegativeInt(op.startSeq);
2000
+ const endSeq = nonNegativeInt(op.endSeq);
2001
+ if (startSeq === void 0 || endSeq === void 0 || endSeq < startSeq) return null;
2002
+ return {
2003
+ startSeq,
2004
+ endSeq
2005
+ };
2006
+ }
2007
+ function isSeqShadowed(seq, replacedRanges, shadowedSet) {
2008
+ if (shadowedSet.has(seq)) return true;
2009
+ for (const range of replacedRanges) if (seq >= range.startSeq && seq <= range.endSeq) return true;
2010
+ return false;
2011
+ }
2012
+ function deduplicateSkills(nodes) {
2013
+ const byName = new Map();
2014
+ for (const node of nodes) byName.set(node.name, node);
2015
+ return [...byName.values()];
2016
+ }
2017
+ function projectSkills(events) {
2018
+ let windowStartSeq = null;
2019
+ let currentTurn;
2020
+ const rawNodes = [];
2021
+ const replacedRanges = [];
2022
+ const shadowedSeqs = [];
2023
+ const shadowedSet = new Set();
2024
+ for (const raw of events) {
2025
+ const ev = asRecord(raw);
2026
+ if (ev === null) continue;
2027
+ const seq = nonNegativeInt(ev.seq);
2028
+ if (seq !== void 0 && (windowStartSeq === null || seq < windowStartSeq)) windowStartSeq = seq;
2029
+ const range = replaceRangeOf(ev.surfaceOp);
2030
+ if (range !== null) replacedRanges.push(range);
2031
+ const data = asRecord(ev.data);
2032
+ const turnInEvent = positiveInt(data?.turn);
2033
+ if (range === null && turnInEvent !== void 0) currentTurn = turnInEvent;
2034
+ const effectiveTurn = turnInEvent ?? currentTurn;
2035
+ const type = nonEmptyString(ev.type);
2036
+ if (type === "compaction/summary" || type === "compaction/prune") {
2037
+ const list = data?.shadowedSeqs;
2038
+ if (Array.isArray(list)) for (const item of list) {
2039
+ const s = nonNegativeInt(item);
2040
+ if (s !== void 0 && !shadowedSet.has(s)) {
2041
+ shadowedSet.add(s);
2042
+ shadowedSeqs.push(s);
2043
+ }
2044
+ }
2045
+ continue;
2046
+ }
2047
+ if (seq === void 0) continue;
2048
+ if (type === "user/message" || type === "developer/message") {
2049
+ const name = invokedSkillName(type, data);
2050
+ if (name !== void 0) rawNodes.push({
2051
+ name,
2052
+ trigger: "user",
2053
+ seq,
2054
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
2055
+ });
2056
+ continue;
2057
+ }
2058
+ if (type === "tool/result") {
2059
+ const name = toolSkillName(asRecord(data?.message));
2060
+ if (name !== void 0) rawNodes.push({
2061
+ name,
2062
+ trigger: "agent",
2063
+ seq,
2064
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
2065
+ });
2066
+ }
2067
+ }
2068
+ const surviving = rawNodes.filter((node) => !isSeqShadowed(node.seq, replacedRanges, shadowedSet));
2069
+ return {
2070
+ skills: deduplicateSkills(surviving),
2071
+ nodes: surviving,
2072
+ windowStartSeq,
2073
+ replacedRanges,
2074
+ shadowedSeqs
2075
+ };
2076
+ }
2077
+ function mergeSkillProjections(host, client) {
2078
+ if (host === null) return client.skills;
2079
+ if (client.windowStartSeq === null) return host.skills;
2080
+ if (client.windowStartSeq === 0) return client.skills;
2081
+ const windowStart = client.windowStartSeq;
2082
+ const clientShadowed = new Set(client.shadowedSeqs);
2083
+ const hostNodes = host.nodes ?? host.skills;
2084
+ const clientNodes = client.nodes ?? client.skills;
2085
+ return deduplicateSkills([...hostNodes.filter((skill) => skill.seq < windowStart && !isSeqShadowed(skill.seq, client.replacedRanges, clientShadowed)), ...clientNodes]);
2086
+ }
2087
+ function parseSkillArray(list) {
2088
+ const out = [];
2089
+ for (const item of list) {
2090
+ const row = asRecord(item);
2091
+ if (row === null) continue;
2092
+ const name = nonEmptyString(row.name);
2093
+ const trigger = row.trigger === "user" || row.trigger === "agent" ? row.trigger : void 0;
2094
+ const seq = nonNegativeInt(row.seq);
2095
+ const turn = positiveInt(row.turn);
2096
+ if (name !== void 0 && trigger !== void 0 && seq !== void 0) out.push({
2097
+ name,
2098
+ trigger,
2099
+ seq,
2100
+ ...turn === void 0 ? {} : { turn }
2101
+ });
2102
+ }
2103
+ return out;
2104
+ }
2105
+ function normalizeSkillProjection(raw) {
2106
+ const rec = asRecord(raw);
2107
+ if (rec === null || !Array.isArray(rec.skills)) return null;
2108
+ const skills = parseSkillArray(rec.skills);
2109
+ const nodes = Array.isArray(rec.nodes) ? parseSkillArray(rec.nodes) : skills;
2110
+ const windowStartSeq = nonNegativeInt(rec.windowStartSeq) ?? null;
2111
+ const replacedRanges = [];
2112
+ if (Array.isArray(rec.replacedRanges)) for (const item of rec.replacedRanges) {
2113
+ const r = asRecord(item);
2114
+ const startSeq = nonNegativeInt(r?.startSeq);
2115
+ const endSeq = nonNegativeInt(r?.endSeq);
2116
+ if (startSeq !== void 0 && endSeq !== void 0 && endSeq >= startSeq) replacedRanges.push({
2117
+ startSeq,
2118
+ endSeq
2119
+ });
2120
+ }
2121
+ const shadowedSeqs = [];
2122
+ if (Array.isArray(rec.shadowedSeqs)) for (const item of rec.shadowedSeqs) {
2123
+ const s = nonNegativeInt(item);
2124
+ if (s !== void 0) shadowedSeqs.push(s);
2125
+ }
2126
+ return {
2127
+ skills,
2128
+ nodes,
2129
+ windowStartSeq,
2130
+ replacedRanges,
2131
+ shadowedSeqs
2132
+ };
2133
+ }
2134
+ function buildSessionInfoSections(skills, t) {
2135
+ return [{
2136
+ id: "skills",
2137
+ title: t("sessionInfo.skills.title"),
2138
+ shortLabel: t("sessionInfo.skills.short"),
2139
+ emptyText: t("sessionInfo.skills.empty"),
2140
+ items: skills.map((skill) => ({
2141
+ name: skill.name,
2142
+ badge: skill.trigger === "user" ? t("sessionInfo.trigger.user") : t("sessionInfo.trigger.agent"),
2143
+ ...skill.turn === void 0 ? {} : { turnLabel: `#${String(skill.turn)}` }
2144
+ }))
2145
+ }];
2146
+ }
2147
+ //#endregion
2148
+ //#region src/client/session-info/panel.ts
2149
+ const SESSION_INFO_ATTR = "data-desktop-enhance-session-info";
2150
+ const SESSION_INFO_URL = SESSION_INFO_ROUTE;
2151
+ function openSkillInHost(ctx, sessionId, name) {
2152
+ if (sessionId === "" || name === "") return false;
2153
+ const sessions = ctx.get("sessions");
2154
+ if (sessions === null || sessions === void 0 || typeof sessions.scope !== "function") return false;
2155
+ let scope;
2156
+ try {
2157
+ scope = sessions.scope(sessionId);
2158
+ } catch {
2159
+ return false;
2160
+ }
2161
+ if (scope === void 0 || scope === null) return false;
2162
+ const triggers = ctx.get("inputTriggers");
2163
+ if (triggers === null || triggers === void 0 || typeof triggers.sessionOf !== "function") return false;
2164
+ try {
2165
+ const controller = triggers.sessionOf(scope);
2166
+ if (controller === void 0 || typeof controller.openReference !== "function") return false;
2167
+ return Boolean(controller.openReference("skill", { ref: `/${name}` }));
2168
+ } catch {
2169
+ return false;
2170
+ }
2171
+ }
2172
+ async function requestSessionInfo(fetchImpl, sessionId) {
2173
+ try {
2174
+ const response = await fetchImpl(SESSION_INFO_URL, {
2175
+ method: "POST",
2176
+ headers: { "content-type": "application/json" },
2177
+ body: JSON.stringify({ sessionId })
2178
+ });
2179
+ if (!response.ok) return null;
2180
+ return normalizeSkillProjection(await response.json());
2181
+ } catch {
2182
+ return null;
2183
+ }
2184
+ }
2185
+ function findActiveChatConversation() {
2186
+ if (typeof globalThis.document?.querySelector !== "function") return null;
2187
+ const root = globalThis.document.querySelector("[data-conversation-session]");
2188
+ if (root === null || typeof root.querySelector !== "function") return null;
2189
+ const sessionId = root.getAttribute("data-conversation-session");
2190
+ if (sessionId === null || sessionId === "") return null;
2191
+ if (root.querySelector("[data-chat-flow]") === null) return null;
2192
+ return {
2193
+ root,
2194
+ sessionId
2195
+ };
2196
+ }
2197
+ function renderSignature(sections, minimized, collapsedSections) {
2198
+ return JSON.stringify({
2199
+ minimized,
2200
+ collapsed: [...collapsedSections].sort(),
2201
+ sections
2202
+ });
2203
+ }
2204
+ function installSessionInfo(ctx, isOn, getSessions, t, fetchImpl = globalThis.fetch) {
2205
+ let activeSessionId = null;
2206
+ let retained = null;
2207
+ let unsubscribeSource = null;
2208
+ let hostProjection = null;
2209
+ let fetchToken = 0;
2210
+ let minimized = false;
2211
+ const collapsedSections = new Set();
2212
+ let mountedPanel = null;
2213
+ let lastSignature = "";
2214
+ function removePanel() {
2215
+ if (mountedPanel !== null) {
2216
+ mountedPanel.remove();
2217
+ mountedPanel = null;
2218
+ }
2219
+ lastSignature = "";
2220
+ }
2221
+ function detach() {
2222
+ fetchToken += 1;
2223
+ if (unsubscribeSource !== null) {
2224
+ unsubscribeSource();
2225
+ unsubscribeSource = null;
2226
+ }
2227
+ if (retained !== null) {
2228
+ retained.release();
2229
+ retained = null;
2230
+ }
2231
+ activeSessionId = null;
2232
+ hostProjection = null;
2233
+ removePanel();
2234
+ }
2235
+ function currentClientProjection() {
2236
+ return projectSkills(eventsOf(retained?.binding.eventSource.getSnapshot().entries ?? []));
2237
+ }
2238
+ function paint(root, sessionId) {
2239
+ const clientProjection = currentClientProjection();
2240
+ const sections = buildSessionInfoSections(mergeSkillProjections(hostProjection, clientProjection), t);
2241
+ const totalItems = sections.reduce((sum, sec) => sum + sec.items.length, 0);
2242
+ const sig = renderSignature(sections, minimized, collapsedSections);
2243
+ let panel = root.querySelector(`[${SESSION_INFO_ATTR}]`);
2244
+ if (panel === null) {
2245
+ panel = globalThis.document.createElement("aside");
2246
+ panel.setAttribute(SESSION_INFO_ATTR, "");
2247
+ root.appendChild(panel);
2248
+ mountedPanel = panel;
2249
+ lastSignature = "";
2250
+ } else mountedPanel = panel;
2251
+ if (sig === lastSignature) return;
2252
+ lastSignature = sig;
2253
+ panel.setAttribute("data-minimized", minimized ? "true" : "false");
2254
+ panel.setAttribute("data-empty", totalItems === 0 ? "true" : "false");
2255
+ const header = globalThis.document.createElement("button");
2256
+ header.setAttribute("type", "button");
2257
+ header.setAttribute("data-session-info-header", "");
2258
+ header.setAttribute("aria-expanded", minimized ? "false" : "true");
2259
+ header.setAttribute("aria-label", minimized ? t("sessionInfo.expand") : t("sessionInfo.collapse"));
2260
+ const titleWrap = globalThis.document.createElement("span");
2261
+ titleWrap.setAttribute("data-session-info-title", "");
2262
+ const dot = globalThis.document.createElement("span");
2263
+ dot.setAttribute("data-session-info-dot", totalItems === 0 ? "idle" : "active");
2264
+ titleWrap.appendChild(dot);
2265
+ const titleText = globalThis.document.createElement("span");
2266
+ if (minimized) {
2267
+ const summary = sections.map((sec) => `${sec.shortLabel} ${String(sec.items.length)}`).join(" · ");
2268
+ titleText.textContent = `${t("sessionInfo.title")} · ${summary}`;
2269
+ } else titleText.textContent = t("sessionInfo.title");
2270
+ titleWrap.appendChild(titleText);
2271
+ header.appendChild(titleWrap);
2272
+ const toggleIcon = globalThis.document.createElement("span");
2273
+ toggleIcon.setAttribute("data-session-info-chevron", "");
2274
+ toggleIcon.textContent = minimized ? "▾" : "−";
2275
+ header.appendChild(toggleIcon);
2276
+ header.addEventListener("click", () => {
2277
+ minimized = !minimized;
2278
+ paint(root, sessionId);
2279
+ });
2280
+ const children = [header];
2281
+ if (!minimized) for (const sec of sections) {
2282
+ const secCollapsed = collapsedSections.has(sec.id);
2283
+ const secEl = globalThis.document.createElement("section");
2284
+ secEl.setAttribute("data-session-info-section", sec.id);
2285
+ secEl.setAttribute("data-collapsed", secCollapsed ? "true" : "false");
2286
+ const secHead = globalThis.document.createElement("button");
2287
+ secHead.setAttribute("type", "button");
2288
+ secHead.setAttribute("data-session-info-section-header", sec.id);
2289
+ secHead.setAttribute("aria-expanded", secCollapsed ? "false" : "true");
2290
+ const secHeadLeft = globalThis.document.createElement("span");
2291
+ secHeadLeft.setAttribute("data-session-info-section-label", "");
2292
+ const arrow = globalThis.document.createElement("span");
2293
+ arrow.setAttribute("data-session-info-arrow", "");
2294
+ arrow.textContent = secCollapsed ? "▸" : "▾";
2295
+ secHeadLeft.appendChild(arrow);
2296
+ const secTitle = globalThis.document.createElement("span");
2297
+ secTitle.textContent = sec.title;
2298
+ secHeadLeft.appendChild(secTitle);
2299
+ secHead.appendChild(secHeadLeft);
2300
+ const countBadge = globalThis.document.createElement("span");
2301
+ countBadge.setAttribute("data-session-info-count", "");
2302
+ countBadge.textContent = String(sec.items.length);
2303
+ secHead.appendChild(countBadge);
2304
+ secHead.addEventListener("click", () => {
2305
+ if (collapsedSections.has(sec.id)) collapsedSections.delete(sec.id);
2306
+ else collapsedSections.add(sec.id);
2307
+ paint(root, sessionId);
2308
+ });
2309
+ secEl.appendChild(secHead);
2310
+ if (!secCollapsed) {
2311
+ const body = globalThis.document.createElement("div");
2312
+ body.setAttribute("data-session-info-body", "");
2313
+ if (sec.items.length === 0) {
2314
+ const empty = globalThis.document.createElement("div");
2315
+ empty.setAttribute("data-session-info-empty", "");
2316
+ empty.textContent = sec.emptyText;
2317
+ body.appendChild(empty);
2318
+ } else for (const item of sec.items) {
2319
+ const row = globalThis.document.createElement("button");
2320
+ row.setAttribute("type", "button");
2321
+ row.setAttribute("data-session-info-item", item.name);
2322
+ const nameSpan = globalThis.document.createElement("span");
2323
+ nameSpan.setAttribute("data-session-info-item-name", "");
2324
+ nameSpan.textContent = item.name;
2325
+ row.appendChild(nameSpan);
2326
+ const metaSpan = globalThis.document.createElement("span");
2327
+ metaSpan.setAttribute("data-session-info-item-meta", "");
2328
+ const badgeSpan = globalThis.document.createElement("span");
2329
+ badgeSpan.setAttribute("data-session-info-item-badge", "");
2330
+ badgeSpan.textContent = item.badge;
2331
+ metaSpan.appendChild(badgeSpan);
2332
+ if (item.turnLabel !== void 0) {
2333
+ const turnSpan = globalThis.document.createElement("span");
2334
+ turnSpan.setAttribute("data-session-info-item-turn", "");
2335
+ turnSpan.textContent = item.turnLabel;
2336
+ metaSpan.appendChild(turnSpan);
2337
+ }
2338
+ row.appendChild(metaSpan);
2339
+ row.addEventListener("click", () => {
2340
+ openSkillInHost(ctx, sessionId, item.name);
2341
+ });
2342
+ body.appendChild(row);
2343
+ }
2344
+ secEl.appendChild(body);
2345
+ }
2346
+ children.push(secEl);
2347
+ }
2348
+ panel.replaceChildren(...children);
2349
+ }
2350
+ function triggerHostRefresh(sessionId, root) {
2351
+ const token = ++fetchToken;
2352
+ requestSessionInfo(fetchImpl, sessionId).then((result) => {
2353
+ if (token !== fetchToken || activeSessionId !== sessionId || result === null) return;
2354
+ hostProjection = result;
2355
+ const current = findActiveChatConversation();
2356
+ paint(current?.sessionId === sessionId ? current.root : root, sessionId);
2357
+ });
2358
+ }
2359
+ function attach(sessionId, root) {
2360
+ const service = getSessions();
2361
+ if (service === null || service === void 0 || typeof service.retain !== "function") return;
2362
+ let next;
2363
+ try {
2364
+ next = service.retain(sessionId, { source: NS });
2365
+ } catch {
2366
+ return;
2367
+ }
2368
+ activeSessionId = sessionId;
2369
+ retained = next;
2370
+ hostProjection = null;
2371
+ let fetchedForWindowStart = void 0;
2372
+ const maybeRequestHost = (targetRoot) => {
2373
+ const ws = currentClientProjection().windowStartSeq;
2374
+ if (ws !== 0 && ws !== fetchedForWindowStart) {
2375
+ fetchedForWindowStart = ws;
2376
+ triggerHostRefresh(sessionId, targetRoot);
2377
+ }
2378
+ };
2379
+ const source = next.binding.eventSource;
2380
+ paint(root, sessionId);
2381
+ maybeRequestHost(root);
2382
+ unsubscribeSource = source.subscribe(() => {
2383
+ const current = findActiveChatConversation();
2384
+ const targetRoot = current?.sessionId === sessionId ? current.root : root;
2385
+ paint(targetRoot, sessionId);
2386
+ maybeRequestHost(targetRoot);
2387
+ });
2388
+ }
2389
+ function sync() {
2390
+ if (!isOn()) {
2391
+ detach();
2392
+ return;
2393
+ }
2394
+ const found = findActiveChatConversation();
2395
+ if (found === null) {
2396
+ detach();
2397
+ return;
2398
+ }
2399
+ if (found.sessionId !== activeSessionId) {
2400
+ detach();
2401
+ attach(found.sessionId, found.root);
2402
+ return;
2403
+ }
2404
+ paint(found.root, found.sessionId);
2405
+ }
2406
+ ctx.effect(() => {
2407
+ const observer = new globalThis.MutationObserver(() => {
2408
+ sync();
2409
+ });
2410
+ observer.observe(globalThis.document.documentElement, {
2411
+ childList: true,
2412
+ subtree: true
2413
+ });
2414
+ return () => {
2415
+ observer.disconnect();
2416
+ detach();
2417
+ };
2418
+ }, "desktop-enhance: session-info observer");
2419
+ sync();
2420
+ return { refresh: sync };
2421
+ }
2422
+ //#endregion
1942
2423
  //#region \0dsh-desktop-enhance-css:/Users/lijiechengbj/projects/AI-agents/dsh-desktop-enhance/src/client/styles/enhance.css.mjs
1943
- var enhance_css_default = "\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"manual-compaction\"]) {\n content-visibility: visible !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"][hidden=\"until-found\"],\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"][hidden=\"until-found\"] {\n content-visibility: visible !important;\n}\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"])\n [data-turn-process-member=\"true\"]:not([data-chat-flow-kind=\"compaction\"]):not([data-chat-flow-kind=\"manual-compaction\"]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"manual-compaction\"])\n [data-turn-process-member=\"true\"]:not([data-chat-flow-kind=\"compaction\"]):not([data-chat-flow-kind=\"manual-compaction\"]) {\n display: none !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-step-process=\"true\"][hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"])\n > div:has(> [data-process-activity]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-step-process=\"true\"][hidden=\"until-found\"]:has([data-chat-flow-kind=\"manual-compaction\"])\n > div:has(> [data-process-activity]) {\n display: none !important;\n}\n\n\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"compaction\"],\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"manual-compaction\"] {\n padding: 6px 0 !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"compaction\"] button,\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"manual-compaction\"] button {\n width: fit-content !important;\n margin: 0 auto !important;\n height: auto !important;\n min-height: 28px;\n padding: 0 14px !important;\n justify-content: center !important;\n background: var(--dsw-alias-state-warn-tertiary) !important;\n border-radius: 999px !important;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dsw-alias-state-warn-primary) 45%, transparent);\n color: var(--dsw-alias-state-warn-label) !important;\n}\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"] button:hover:not(:disabled),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"] button:hover:not(:disabled) {\n background: var(--dsw-alias-state-warn-secondary) !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"] button:has([data-compaction-disclosure=\"expanded\"]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"] button:has([data-compaction-disclosure=\"expanded\"]) {\n width: 100% !important;\n margin: 0 !important;\n border-radius: var(--dsw-radius-sm) !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"] button > span:not([aria-hidden]):not(:last-child),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"] button > span:not([aria-hidden]):not(:last-child) {\n font-weight: 600;\n}\n\n\n[data-desktop-enhance-reveal] {\n content-visibility: visible !important;\n}\n[hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"])\n [data-turn-process-member=\"true\"][data-desktop-enhance-reveal]:not([data-chat-flow-kind=\"compaction\"]) {\n display: revert !important;\n}\n\n\n:root:not([data-desktop-enhance-off-turn-prefix]) [data-desktop-enhance-turn]::before {\n content: attr(data-desktop-enhance-turn);\n flex: none;\n margin-right: 6px;\n padding: 0 5px;\n border-radius: var(--dsw-radius-xs);\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: var(--dsw-font-xxxs-strong-11);\n}\n\n\n\n:root[data-desktop-enhance-off-find-bar] [data-desktop-enhance-findbar] {\n display: none !important;\n}\n[data-desktop-enhance-findbar] {\n position: absolute;\n top: 12px;\n right: 20px;\n z-index: 30;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: var(--dsw-elevation-panel);\n}\n[data-desktop-enhance-findbar] input {\n width: 200px;\n border: none;\n outline: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-s-14);\n}\n[data-desktop-enhance-findbar] input:focus-visible {\n outline: 1px solid var(--dsw-focus-ring-color);\n outline-offset: 2px;\n}\n[data-desktop-enhance-findbar] .count {\n min-width: 44px;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-findbar] button {\n display: grid;\n place-items: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: var(--dsw-radius-xs);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n font: var(--dsw-font-s-14);\n}\n[data-desktop-enhance-findbar] button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n[data-desktop-enhance-findbar] button:disabled {\n opacity: .4;\n cursor: default;\n}\n::highlight(desktop-enhance-find) {\n background-color: var(--dsw-alias-state-warn-tertiary);\n}\n::highlight(desktop-enhance-find-current) {\n background-color: var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-label-primary-inverted);\n}\n\n\n[data-desktop-enhance-settings] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px 0;\n}\n[data-desktop-enhance-settings] .de-head-title {\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-s-14);\n font-weight: 600;\n}\n[data-desktop-enhance-settings] .de-head-desc,\n[data-desktop-enhance-settings] .de-desc {\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-settings] .de-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n[data-desktop-enhance-settings] .de-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n[data-desktop-enhance-settings] .de-title {\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-s-14);\n}\n\n[data-desktop-enhance-settings] .de-switch {\n position: relative;\n flex: none;\n width: 36px;\n height: 20px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n cursor: pointer;\n transition: background .15s;\n}\n[data-desktop-enhance-settings] .de-switch::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-switch-thumb);\n transition: transform .15s;\n}\n[data-desktop-enhance-settings] .de-switch[aria-checked=\"true\"] {\n background: var(--dsw-alias-state-success-primary);\n}\n[data-desktop-enhance-settings] .de-switch[aria-checked=\"true\"]::after {\n transform: translateX(16px);\n}\n[data-desktop-enhance-settings] .de-switch:disabled {\n opacity: .5;\n cursor: default;\n}\n[data-desktop-enhance-settings] .de-note {\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-settings] .de-error {\n color: var(--dsw-alias-state-error-primary);\n font: var(--dsw-font-xxs-12);\n}\n\n\n\n[data-desktop-enhance-settings] .de-suggest-settings {\n display: flex;\n flex-direction: column;\n gap: 12px;\n \n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 4px;\n padding-top: 12px;\n}\n[data-desktop-enhance-settings] .de-item-block {\n align-items: stretch;\n flex-direction: column;\n gap: 6px;\n}\n[data-desktop-enhance-settings] .de-select,\n[data-desktop-enhance-settings] .de-text-input,\n[data-desktop-enhance-settings] .de-textarea {\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-xs-13);\n padding: 6px 8px;\n width: 100%;\n}\n[data-desktop-enhance-settings] .de-select:disabled,\n[data-desktop-enhance-settings] .de-text-input:disabled,\n[data-desktop-enhance-settings] .de-textarea:disabled {\n opacity: .5;\n}\n\n[data-desktop-enhance-settings] .de-textarea {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n line-height: 1.5;\n resize: vertical;\n white-space: pre-wrap;\n}\n[data-desktop-enhance-settings] .de-prompt {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n[data-desktop-enhance-settings] .de-prompt-meta {\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-settings] .de-prompt-actions {\n display: flex;\n gap: 8px;\n}\n[data-desktop-enhance-settings] .de-button {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n font: var(--dsw-font-xxs-12);\n padding: 4px 10px;\n}\n[data-desktop-enhance-settings] .de-button:hover {\n border-color: var(--dsw-alias-border-l2);\n}\n[data-desktop-enhance-settings] .de-button:disabled {\n cursor: default;\n opacity: .5;\n}\n\n\n\n\n:root[data-desktop-enhance-off-suggest] [data-desktop-enhance='suggest'] {\n display: none !important;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest {\n align-items: center;\n \n background: color-mix(in srgb, var(--dsw-alias-state-business-tertiary) 10%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 20%, transparent);\n border-radius: var(--dsw-radius-md);\n box-sizing: border-box;\n display: flex;\n \n flex-wrap: wrap;\n gap: 6px;\n \n margin: 0 auto 6px;\n max-width: var(--dsh-composer-card-max-width, 100%);\n \n min-width: 0;\n \n padding: 4px 10px 4px 13px;\n width: calc(100% - var(--dsh-composer-side-clearance, 0px) - var(--dsh-composer-side-clearance, 0px));\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-head {\n align-items: center;\n display: flex;\n flex: none;\n gap: 2px;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-title {\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-xs-13);\n font-weight: 500;\n \n white-space: nowrap;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-close {\n appearance: none;\n background: none;\n border: 0;\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n flex: none;\n font: var(--dsw-font-xxs-12);\n line-height: 1;\n padding: 2px 4px;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-close:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-row {\n appearance: none;\n align-items: center;\n background: none;\n \n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 35%, transparent);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n display: flex;\n flex: 0 1 auto;\n font: var(--dsw-font-xs-13);\n gap: 6px;\n \n max-width: 100%;\n min-width: 0;\n padding: 4px 10px;\n text-align: left;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-row:hover {\n background: var(--dsw-alias-state-business-tertiary);\n border-color: var(--dsw-alias-state-business-primary);\n color: var(--dsw-alias-state-business-primary);\n transition: background-color .12s ease, border-color .12s ease, color .12s ease;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-row[data-used='true'] {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-label-secondary);\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-num {\n color: var(--dsw-alias-label-primary);\n flex: none;\n font: var(--dsw-font-xs-13);\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-text {\n max-width: 22ch;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-loading {\n border-style: dashed;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-error {\n color: var(--dsw-alias-state-error-primary);\n flex: 1 1 auto;\n font: var(--dsw-font-xxs-12);\n min-width: 0;\n padding: 2px;\n}\n\n\n:root:not([data-desktop-enhance-off-step-duration]) [data-desktop-enhance-duration]::after {\n content: attr(data-desktop-enhance-duration);\n \n margin-left: 8px;\n font-size: inherit;\n line-height: inherit;\n \n font-variant-numeric: tabular-nums;\n color: inherit;\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n}\n\n:root:not([data-desktop-enhance-off-step-duration])\n [data-desktop-enhance-duration]:has([data-step-process-icon])::after {\n margin-left: 2px;\n}\n";
2424
+ var enhance_css_default = "\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"manual-compaction\"]) {\n content-visibility: visible !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"][hidden=\"until-found\"],\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"][hidden=\"until-found\"] {\n content-visibility: visible !important;\n}\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"])\n [data-turn-process-member=\"true\"]:not([data-chat-flow-kind=\"compaction\"]):not([data-chat-flow-kind=\"manual-compaction\"]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [hidden=\"until-found\"]:has([data-chat-flow-kind=\"manual-compaction\"])\n [data-turn-process-member=\"true\"]:not([data-chat-flow-kind=\"compaction\"]):not([data-chat-flow-kind=\"manual-compaction\"]) {\n display: none !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-step-process=\"true\"][hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"])\n > div:has(> [data-process-activity]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-step-process=\"true\"][hidden=\"until-found\"]:has([data-chat-flow-kind=\"manual-compaction\"])\n > div:has(> [data-process-activity]) {\n display: none !important;\n}\n\n\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"compaction\"],\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"manual-compaction\"] {\n padding: 6px 0 !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"compaction\"] button,\n:root:not([data-desktop-enhance-off-compaction-row]) [data-chat-flow-kind=\"manual-compaction\"] button {\n width: fit-content !important;\n margin: 0 auto !important;\n height: auto !important;\n min-height: 28px;\n padding: 0 14px !important;\n justify-content: center !important;\n background: var(--dsw-alias-state-warn-tertiary) !important;\n border-radius: 999px !important;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dsw-alias-state-warn-primary) 45%, transparent);\n color: var(--dsw-alias-state-warn-label) !important;\n}\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"] button:hover:not(:disabled),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"] button:hover:not(:disabled) {\n background: var(--dsw-alias-state-warn-secondary) !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"] button:has([data-compaction-disclosure=\"expanded\"]),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"] button:has([data-compaction-disclosure=\"expanded\"]) {\n width: 100% !important;\n margin: 0 !important;\n border-radius: var(--dsw-radius-sm) !important;\n}\n\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"compaction\"] button > span:not([aria-hidden]):not(:last-child),\n:root:not([data-desktop-enhance-off-compaction-row])\n [data-chat-flow-kind=\"manual-compaction\"] button > span:not([aria-hidden]):not(:last-child) {\n font-weight: 600;\n}\n\n\n[data-desktop-enhance-reveal] {\n content-visibility: visible !important;\n}\n[hidden=\"until-found\"]:has([data-chat-flow-kind=\"compaction\"])\n [data-turn-process-member=\"true\"][data-desktop-enhance-reveal]:not([data-chat-flow-kind=\"compaction\"]) {\n display: revert !important;\n}\n\n\n:root:not([data-desktop-enhance-off-turn-prefix]) [data-desktop-enhance-turn]::before {\n content: attr(data-desktop-enhance-turn);\n flex: none;\n margin-right: 6px;\n padding: 0 5px;\n border-radius: var(--dsw-radius-xs);\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: var(--dsw-font-xxxs-strong-11);\n}\n\n\n\n:root[data-desktop-enhance-off-find-bar] [data-desktop-enhance-findbar] {\n display: none !important;\n}\n[data-desktop-enhance-findbar] {\n position: absolute;\n top: 12px;\n right: 20px;\n z-index: 30;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: var(--dsw-elevation-panel);\n}\n[data-desktop-enhance-findbar] input {\n width: 200px;\n border: none;\n outline: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-s-14);\n}\n[data-desktop-enhance-findbar] input:focus-visible {\n outline: 1px solid var(--dsw-focus-ring-color);\n outline-offset: 2px;\n}\n[data-desktop-enhance-findbar] .count {\n min-width: 44px;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-findbar] button {\n display: grid;\n place-items: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: var(--dsw-radius-xs);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n font: var(--dsw-font-s-14);\n}\n[data-desktop-enhance-findbar] button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n[data-desktop-enhance-findbar] button:disabled {\n opacity: .4;\n cursor: default;\n}\n::highlight(desktop-enhance-find) {\n background-color: var(--dsw-alias-state-warn-tertiary);\n}\n::highlight(desktop-enhance-find-current) {\n background-color: var(--dsw-alias-state-warn-primary);\n color: var(--dsw-alias-label-primary-inverted);\n}\n\n\n[data-desktop-enhance-settings] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px 0;\n}\n[data-desktop-enhance-settings] .de-head-title {\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-s-14);\n font-weight: 600;\n}\n[data-desktop-enhance-settings] .de-head-desc,\n[data-desktop-enhance-settings] .de-desc {\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-settings] .de-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n[data-desktop-enhance-settings] .de-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n[data-desktop-enhance-settings] .de-title {\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-s-14);\n}\n\n[data-desktop-enhance-settings] .de-switch {\n position: relative;\n flex: none;\n width: 36px;\n height: 20px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n cursor: pointer;\n transition: background .15s;\n}\n[data-desktop-enhance-settings] .de-switch::after {\n content: \"\";\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-switch-thumb);\n transition: transform .15s;\n}\n[data-desktop-enhance-settings] .de-switch[aria-checked=\"true\"] {\n background: var(--dsw-alias-state-success-primary);\n}\n[data-desktop-enhance-settings] .de-switch[aria-checked=\"true\"]::after {\n transform: translateX(16px);\n}\n[data-desktop-enhance-settings] .de-switch:disabled {\n opacity: .5;\n cursor: default;\n}\n[data-desktop-enhance-settings] .de-note {\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-settings] .de-error {\n color: var(--dsw-alias-state-error-primary);\n font: var(--dsw-font-xxs-12);\n}\n\n\n\n[data-desktop-enhance-settings] .de-suggest-settings {\n display: flex;\n flex-direction: column;\n gap: 12px;\n \n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 4px;\n padding-top: 12px;\n}\n[data-desktop-enhance-settings] .de-item-block {\n align-items: stretch;\n flex-direction: column;\n gap: 6px;\n}\n[data-desktop-enhance-settings] .de-select,\n[data-desktop-enhance-settings] .de-text-input,\n[data-desktop-enhance-settings] .de-textarea {\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-xs-13);\n padding: 6px 8px;\n width: 100%;\n}\n[data-desktop-enhance-settings] .de-select:disabled,\n[data-desktop-enhance-settings] .de-text-input:disabled,\n[data-desktop-enhance-settings] .de-textarea:disabled {\n opacity: .5;\n}\n\n[data-desktop-enhance-settings] .de-textarea {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n line-height: 1.5;\n resize: vertical;\n white-space: pre-wrap;\n}\n[data-desktop-enhance-settings] .de-prompt {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n[data-desktop-enhance-settings] .de-prompt-meta {\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-settings] .de-prompt-actions {\n display: flex;\n gap: 8px;\n}\n[data-desktop-enhance-settings] .de-button {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n font: var(--dsw-font-xxs-12);\n padding: 4px 10px;\n}\n[data-desktop-enhance-settings] .de-button:hover {\n border-color: var(--dsw-alias-border-l2);\n}\n[data-desktop-enhance-settings] .de-button:disabled {\n cursor: default;\n opacity: .5;\n}\n\n\n\n\n:root[data-desktop-enhance-off-suggest] [data-desktop-enhance='suggest'] {\n display: none !important;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest {\n align-items: center;\n \n background: color-mix(in srgb, var(--dsw-alias-state-business-tertiary) 10%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 20%, transparent);\n border-radius: var(--dsw-radius-md);\n box-sizing: border-box;\n display: flex;\n \n flex-wrap: wrap;\n gap: 6px;\n \n margin: 0 auto 6px;\n max-width: var(--dsh-composer-card-max-width, 100%);\n \n min-width: 0;\n \n padding: 4px 10px 4px 13px;\n width: calc(100% - var(--dsh-composer-side-clearance, 0px) - var(--dsh-composer-side-clearance, 0px));\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-head {\n align-items: center;\n display: flex;\n flex: none;\n gap: 2px;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-title {\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-xs-13);\n font-weight: 500;\n \n white-space: nowrap;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-close {\n appearance: none;\n background: none;\n border: 0;\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n flex: none;\n font: var(--dsw-font-xxs-12);\n line-height: 1;\n padding: 2px 4px;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-close:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-row {\n appearance: none;\n align-items: center;\n background: none;\n \n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 35%, transparent);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n display: flex;\n flex: 0 1 auto;\n font: var(--dsw-font-xs-13);\n gap: 6px;\n \n max-width: 100%;\n min-width: 0;\n padding: 4px 10px;\n text-align: left;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-row:hover {\n background: var(--dsw-alias-state-business-tertiary);\n border-color: var(--dsw-alias-state-business-primary);\n color: var(--dsw-alias-state-business-primary);\n transition: background-color .12s ease, border-color .12s ease, color .12s ease;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-row[data-used='true'] {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-label-secondary);\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-num {\n color: var(--dsw-alias-label-primary);\n flex: none;\n font: var(--dsw-font-xs-13);\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-text {\n max-width: 22ch;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-loading {\n border-style: dashed;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n\n:root:not([data-desktop-enhance-off-suggest]) .de-suggest-error {\n color: var(--dsw-alias-state-error-primary);\n flex: 1 1 auto;\n font: var(--dsw-font-xxs-12);\n min-width: 0;\n padding: 2px;\n}\n\n\n:root:not([data-desktop-enhance-off-step-duration]) [data-desktop-enhance-duration]::after {\n content: attr(data-desktop-enhance-duration);\n \n margin-left: 8px;\n font-size: inherit;\n line-height: inherit;\n \n font-variant-numeric: tabular-nums;\n color: inherit;\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n}\n\n:root:not([data-desktop-enhance-off-step-duration])\n [data-desktop-enhance-duration]:has([data-step-process-icon])::after {\n margin-left: 2px;\n}\n\n\n:root[data-desktop-enhance-off-session-info] [data-desktop-enhance-session-info] {\n display: none !important;\n}\n:root:not([data-desktop-enhance-off-session-info]) [data-desktop-enhance-session-info] {\n position: absolute;\n top: 12px;\n right: 36px;\n z-index: 10;\n width: 244px;\n box-sizing: border-box;\n border-radius: var(--dsw-radius-lg);\n border: 1px solid var(--dsw-alias-border-l2);\n background: color-mix(in srgb, var(--dsw-alias-bg-layer-1) 90%, transparent);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n box-shadow: var(--dsw-elevation-panel);\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n}\n:root:not([data-desktop-enhance-off-session-info])\n [data-desktop-enhance-session-info][data-minimized=\"true\"] {\n width: auto;\n border-radius: 999px;\n}\n[data-desktop-enhance-session-info] [data-session-info-header] {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 10px;\n border: none;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n background: color-mix(in srgb, var(--dsw-alias-bg-module-platform) 60%, transparent);\n color: var(--dsw-alias-label-secondary);\n font: var(--dsw-font-xxs-strong-12, 600 12px/18px sans-serif);\n cursor: pointer;\n user-select: none;\n}\n[data-desktop-enhance-session-info][data-minimized=\"true\"] [data-session-info-header] {\n border-bottom: none;\n background: transparent;\n padding: 5px 11px;\n}\n[data-desktop-enhance-session-info] [data-session-info-header]:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n[data-desktop-enhance-session-info] [data-session-info-title] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n[data-desktop-enhance-session-info] [data-session-info-dot] {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n flex: none;\n background: var(--dsw-alias-state-business-primary);\n box-shadow: 0 0 0 2.5px var(--dsw-alias-state-business-tertiary);\n}\n[data-desktop-enhance-session-info] [data-session-info-dot=\"idle\"] {\n background: var(--dsw-alias-label-caption);\n box-shadow: none;\n}\n[data-desktop-enhance-session-info] [data-session-info-chevron] {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 1;\n}\n[data-desktop-enhance-session-info] [data-session-info-section] {\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n[data-desktop-enhance-session-info] [data-session-info-section]:last-child {\n border-bottom: none;\n}\n[data-desktop-enhance-session-info] [data-session-info-section-header] {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 10px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font: var(--dsw-font-xxs-strong-12, 600 12px/18px sans-serif);\n cursor: pointer;\n user-select: none;\n}\n[data-desktop-enhance-session-info] [data-session-info-section-header]:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n[data-desktop-enhance-session-info] [data-session-info-section-label] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n min-width: 0;\n}\n[data-desktop-enhance-session-info] [data-session-info-arrow] {\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n}\n[data-desktop-enhance-session-info] [data-session-info-count] {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11px;\n font-weight: 600;\n padding: 0 5px;\n line-height: 16px;\n border-radius: 999px;\n background: var(--dsw-alias-state-business-tertiary);\n color: var(--dsw-alias-label-primary-bluish);\n}\n[data-desktop-enhance-session-info][data-empty=\"true\"] [data-session-info-count] {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-tertiary);\n}\n[data-desktop-enhance-session-info] [data-session-info-body] {\n padding: 2px 6px 6px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 184px;\n overflow-y: auto;\n}\n[data-desktop-enhance-session-info] [data-session-info-empty] {\n padding: 4px 6px;\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n[data-desktop-enhance-session-info] [data-session-info-item] {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 5px 8px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n text-align: left;\n}\n[data-desktop-enhance-session-info] [data-session-info-item]:hover {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n[data-desktop-enhance-session-info] [data-session-info-item-name] {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n[data-desktop-enhance-session-info] [data-session-info-item-meta] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n flex: none;\n}\n[data-desktop-enhance-session-info] [data-session-info-item-badge] {\n font-size: 10.5px;\n color: var(--dsw-alias-label-tertiary);\n}\n[data-desktop-enhance-session-info] [data-session-info-item-turn] {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11px;\n padding: 0 5px;\n line-height: 16px;\n border-radius: var(--dsw-radius-xs);\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n}\n";
1944
2425
  //#endregion
1945
2426
  //#region src/client/index.tsx
1946
2427
  const inject = [
@@ -2014,6 +2495,7 @@ window.__ModuleLoader__.load({
2014
2495
  });
2015
2496
  installTurnPrefix(ctx);
2016
2497
  const timings = installTimings(ctx, () => flagOn(settings.get().value, "stepDuration"), () => ctx.get("sessions"));
2498
+ const sessionInfo = installSessionInfo(ctx, () => flagOn(settings.get().value, "sessionInfo"), () => ctx.get("sessions"), t);
2017
2499
  const suggestStore = createSuggestStore();
2018
2500
  const suggestWatcher = createSuggestWatcher(() => ctx.get("sessions"), suggestStore, {
2019
2501
  fetchImpl: globalThis.fetch,
@@ -2024,6 +2506,7 @@ window.__ModuleLoader__.load({
2024
2506
  const off = settings.subscribe(() => {
2025
2507
  suggestWatcher.refresh();
2026
2508
  timings.refresh();
2509
+ sessionInfo.refresh();
2027
2510
  });
2028
2511
  return () => {
2029
2512
  off();
@@ -2169,7 +2652,16 @@ window.__ModuleLoader__.load({
2169
2652
  anyRunning,
2170
2653
  installTimings,
2171
2654
  DURATION_ATTR,
2172
- titleOf
2655
+ titleOf,
2656
+ projectSkills,
2657
+ mergeSkillProjections,
2658
+ normalizeSkillProjection,
2659
+ buildSessionInfoSections,
2660
+ SESSION_INFO_ATTR,
2661
+ SESSION_INFO_URL,
2662
+ requestSessionInfo,
2663
+ openSkillInHost,
2664
+ installSessionInfo
2173
2665
  };
2174
2666
  exports.apply = apply;
2175
2667
  exports.inject = inject;
package/lib/index.d.ts CHANGED
@@ -90,6 +90,10 @@ declare function listEfforts(ctx: HostContext, provider: string, model: string):
90
90
  }>;
91
91
  declare function registerSuggestRoute(ctx: HostContext): () => void;
92
92
  //#endregion
93
+ //#region src/session-info/host.d.ts
94
+ declare function handleSessionInfo(ctx: HostContext, request: Request): Promise<Response>;
95
+ declare function registerSessionInfoRoute(ctx: HostContext): () => void;
96
+ //#endregion
93
97
  //#region src/index.d.ts
94
98
  export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
95
99
  findBar: Schema<boolean, boolean, "volatile-defined">;
@@ -97,6 +101,7 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
97
101
  compactionRow: Schema<boolean, boolean, "volatile-defined">;
98
102
  suggest: Schema<boolean, boolean, "volatile-defined">;
99
103
  stepDuration: Schema<boolean, boolean, "volatile-defined">;
104
+ sessionInfo: Schema<boolean, boolean, "volatile-defined">;
100
105
  suggestModel: Schema<string, string, "volatile-defined">;
101
106
  suggestReasoning: Schema<string, string, "volatile-defined">;
102
107
  suggestPrompt: Schema<string, string, "volatile-defined">;
@@ -106,6 +111,7 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
106
111
  compactionRow: Schema<boolean, boolean, "volatile-defined">;
107
112
  suggest: Schema<boolean, boolean, "volatile-defined">;
108
113
  stepDuration: Schema<boolean, boolean, "volatile-defined">;
114
+ sessionInfo: Schema<boolean, boolean, "volatile-defined">;
109
115
  suggestModel: Schema<string, string, "volatile-defined">;
110
116
  suggestReasoning: Schema<string, string, "volatile-defined">;
111
117
  suggestPrompt: Schema<string, string, "volatile-defined">;
@@ -115,6 +121,7 @@ export declare const __test: {
115
121
  SUGGEST_PATH: string;
116
122
  SUGGEST_MODELS_PATH: string;
117
123
  SUGGEST_EFFORTS_PATH: string;
124
+ SESSION_INFO_PATH: string;
118
125
  MODEL_TIMEOUT_MS: number;
119
126
  MAX_OUTPUT_TOKENS: number;
120
127
  buildPrompt: typeof buildPrompt;
@@ -122,7 +129,9 @@ export declare const __test: {
122
129
  collectText: typeof collectText;
123
130
  knownNamesOf: typeof knownNamesOf;
124
131
  handleSuggest: typeof handleSuggest;
132
+ handleSessionInfo: typeof handleSessionInfo;
125
133
  registerSuggestRoute: typeof registerSuggestRoute;
134
+ registerSessionInfoRoute: typeof registerSessionInfoRoute;
126
135
  routeOf: typeof routeOf;
127
136
  quietEffortFor: typeof quietEffortFor;
128
137
  listModels: typeof listModels;
package/lib/index.js CHANGED
@@ -93,6 +93,7 @@ function splitRoute(model) {
93
93
  const SUGGEST_ROUTE = "/api/desktop-enhance/suggest";
94
94
  const SUGGEST_MODELS_ROUTE = "/api/desktop-enhance/suggest-models";
95
95
  const SUGGEST_EFFORTS_ROUTE = "/api/desktop-enhance/suggest-efforts";
96
+ const SESSION_INFO_ROUTE = "/api/desktop-enhance/session-info";
96
97
  //#endregion
97
98
  //#region src/suggest/host.ts
98
99
  const SUGGEST_PATH = SUGGEST_ROUTE;
@@ -156,7 +157,7 @@ function knownNamesOf(messages) {
156
157
  return found ? names : null;
157
158
  }
158
159
  async function handleSuggest(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
159
- const empty = () => json({ items: [] });
160
+ const empty = () => json$1({ items: [] });
160
161
  let sessionId;
161
162
  let preference = FOLLOW_SESSION;
162
163
  try {
@@ -168,9 +169,9 @@ async function handleSuggest(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
168
169
  prompt: typeof body.prompt === "string" ? body.prompt : ""
169
170
  };
170
171
  } catch {
171
- return json({ items: [] }, 400);
172
+ return json$1({ items: [] }, 400);
172
173
  }
173
- if (typeof sessionId !== "string" || sessionId === "") return json({ items: [] }, 400);
174
+ if (typeof sessionId !== "string" || sessionId === "") return json$1({ items: [] }, 400);
174
175
  const sessions = ctx.get("sessions");
175
176
  const llm = ctx.get("llm");
176
177
  if (sessions === null || sessions === void 0 || llm === null || llm === void 0) return empty();
@@ -229,7 +230,7 @@ async function handleSuggest(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
229
230
  }
230
231
  function withDiag(ctx, body, record) {
231
232
  logDiag(ctx, record);
232
- return json({
233
+ return json$1({
233
234
  ...body,
234
235
  diag: record
235
236
  });
@@ -301,7 +302,7 @@ async function listEfforts(ctx, provider, model) {
301
302
  };
302
303
  }
303
304
  }
304
- function json(body, status = 200) {
305
+ function json$1(body, status = 200) {
305
306
  return new Response(JSON.stringify(body), {
306
307
  status,
307
308
  headers: { "content-type": "application/json" }
@@ -322,7 +323,7 @@ function registerSuggestRoute(ctx) {
322
323
  path: SUGGEST_MODELS_PATH,
323
324
  methods: ["GET"],
324
325
  requestBody: "buffered",
325
- fetch: async () => json(await listModels(ctx))
326
+ fetch: async () => json$1(await listModels(ctx))
326
327
  }));
327
328
  disposers.push(registry.register({
328
329
  path: SUGGEST_EFFORTS_PATH,
@@ -330,11 +331,11 @@ function registerSuggestRoute(ctx) {
330
331
  requestBody: "buffered",
331
332
  fetch: async (request) => {
332
333
  const route = splitRoute(new URL(request.url).searchParams.get("model") ?? "");
333
- if (route === null) return json({
334
+ if (route === null) return json$1({
334
335
  efforts: [],
335
336
  defaultEffort: null
336
337
  });
337
- return json(await listEfforts(ctx, route.provider, route.model));
338
+ return json$1(await listEfforts(ctx, route.provider, route.model));
338
339
  }
339
340
  }));
340
341
  } catch (error) {
@@ -345,6 +346,195 @@ function registerSuggestRoute(ctx) {
345
346
  };
346
347
  }
347
348
  //#endregion
349
+ //#region src/shared/session-info.ts
350
+ const SKILL_CONTENT_RE = /^\s*<skill_content\s+name="([^"]+)">/;
351
+ function asRecord(value) {
352
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
353
+ }
354
+ function nonEmptyString(value) {
355
+ return typeof value === "string" && value !== "" ? value : void 0;
356
+ }
357
+ function nonNegativeInt(value) {
358
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
359
+ }
360
+ function positiveInt(value) {
361
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
362
+ }
363
+ function firstTextOf(content) {
364
+ if (!Array.isArray(content)) return "";
365
+ for (const item of content) {
366
+ const block = asRecord(item);
367
+ if (block?.type === "text") return typeof block.text === "string" ? block.text : "";
368
+ }
369
+ return "";
370
+ }
371
+ function toolSkillName(message) {
372
+ if (message === null || message.isError === true) return void 0;
373
+ return SKILL_CONTENT_RE.exec(firstTextOf(message.content))?.[1];
374
+ }
375
+ function invokedSkillName(type, data) {
376
+ const source = asRecord((type === "developer/message" ? asRecord(data?.message) : data)?.source);
377
+ if (source?.kind !== "skill-invocation") return void 0;
378
+ return nonEmptyString(source.name);
379
+ }
380
+ function replaceRangeOf(surfaceOp) {
381
+ const op = asRecord(surfaceOp);
382
+ if (op?.op !== "replace") return null;
383
+ const startSeq = nonNegativeInt(op.startSeq);
384
+ const endSeq = nonNegativeInt(op.endSeq);
385
+ if (startSeq === void 0 || endSeq === void 0 || endSeq < startSeq) return null;
386
+ return {
387
+ startSeq,
388
+ endSeq
389
+ };
390
+ }
391
+ function isSeqShadowed(seq, replacedRanges, shadowedSet) {
392
+ if (shadowedSet.has(seq)) return true;
393
+ for (const range of replacedRanges) if (seq >= range.startSeq && seq <= range.endSeq) return true;
394
+ return false;
395
+ }
396
+ function deduplicateSkills(nodes) {
397
+ const byName = new Map();
398
+ for (const node of nodes) byName.set(node.name, node);
399
+ return [...byName.values()];
400
+ }
401
+ function projectSkills(events) {
402
+ let windowStartSeq = null;
403
+ let currentTurn;
404
+ const rawNodes = [];
405
+ const replacedRanges = [];
406
+ const shadowedSeqs = [];
407
+ const shadowedSet = new Set();
408
+ for (const raw of events) {
409
+ const ev = asRecord(raw);
410
+ if (ev === null) continue;
411
+ const seq = nonNegativeInt(ev.seq);
412
+ if (seq !== void 0 && (windowStartSeq === null || seq < windowStartSeq)) windowStartSeq = seq;
413
+ const range = replaceRangeOf(ev.surfaceOp);
414
+ if (range !== null) replacedRanges.push(range);
415
+ const data = asRecord(ev.data);
416
+ const turnInEvent = positiveInt(data?.turn);
417
+ if (range === null && turnInEvent !== void 0) currentTurn = turnInEvent;
418
+ const effectiveTurn = turnInEvent ?? currentTurn;
419
+ const type = nonEmptyString(ev.type);
420
+ if (type === "compaction/summary" || type === "compaction/prune") {
421
+ const list = data?.shadowedSeqs;
422
+ if (Array.isArray(list)) for (const item of list) {
423
+ const s = nonNegativeInt(item);
424
+ if (s !== void 0 && !shadowedSet.has(s)) {
425
+ shadowedSet.add(s);
426
+ shadowedSeqs.push(s);
427
+ }
428
+ }
429
+ continue;
430
+ }
431
+ if (seq === void 0) continue;
432
+ if (type === "user/message" || type === "developer/message") {
433
+ const name = invokedSkillName(type, data);
434
+ if (name !== void 0) rawNodes.push({
435
+ name,
436
+ trigger: "user",
437
+ seq,
438
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
439
+ });
440
+ continue;
441
+ }
442
+ if (type === "tool/result") {
443
+ const name = toolSkillName(asRecord(data?.message));
444
+ if (name !== void 0) rawNodes.push({
445
+ name,
446
+ trigger: "agent",
447
+ seq,
448
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
449
+ });
450
+ }
451
+ }
452
+ const surviving = rawNodes.filter((node) => !isSeqShadowed(node.seq, replacedRanges, shadowedSet));
453
+ return {
454
+ skills: deduplicateSkills(surviving),
455
+ nodes: surviving,
456
+ windowStartSeq,
457
+ replacedRanges,
458
+ shadowedSeqs
459
+ };
460
+ }
461
+ //#endregion
462
+ //#region src/session-info/host.ts
463
+ const SESSION_INFO_PATH = SESSION_INFO_ROUTE;
464
+ const EMPTY_PROJECTION = {
465
+ skills: [],
466
+ nodes: [],
467
+ windowStartSeq: null,
468
+ replacedRanges: [],
469
+ shadowedSeqs: []
470
+ };
471
+ function json(body, status = 200) {
472
+ return new Response(JSON.stringify(body), {
473
+ status,
474
+ headers: { "content-type": "application/json" }
475
+ });
476
+ }
477
+ async function disposeObservation(observation) {
478
+ if (observation === null || observation === void 0 || typeof observation !== "object") return;
479
+ try {
480
+ const asyncDispose = observation[Symbol.asyncDispose];
481
+ if (typeof asyncDispose === "function") {
482
+ await asyncDispose.call(observation);
483
+ return;
484
+ }
485
+ const syncDispose = observation[Symbol.dispose];
486
+ if (typeof syncDispose === "function") syncDispose.call(observation);
487
+ } catch {}
488
+ }
489
+ async function readAllSessionEvents(ctx, sessionId) {
490
+ const sessions = ctx.get("sessions");
491
+ if (sessions !== null && sessions !== void 0 && typeof sessions.get === "function") try {
492
+ const live = sessions.get(sessionId);
493
+ if (live !== void 0 && typeof live.snapshotEvents === "function") {
494
+ const events = live.snapshotEvents();
495
+ if (Array.isArray(events)) return events;
496
+ }
497
+ } catch {}
498
+ const query = ctx.get("sessionQuery");
499
+ if (query === null || query === void 0 || typeof query.observeSession !== "function") return null;
500
+ let observation;
501
+ try {
502
+ observation = await query.observeSession(sessionId, { projectionMode: "none" });
503
+ const events = observation?.events;
504
+ return Array.isArray(events) ? events : null;
505
+ } catch {
506
+ return null;
507
+ } finally {
508
+ await disposeObservation(observation);
509
+ }
510
+ }
511
+ async function handleSessionInfo(ctx, request) {
512
+ let sessionId;
513
+ try {
514
+ sessionId = (await request.json())?.sessionId;
515
+ } catch {
516
+ return json(EMPTY_PROJECTION, 400);
517
+ }
518
+ if (typeof sessionId !== "string" || sessionId === "") return json(EMPTY_PROJECTION, 400);
519
+ const events = await readAllSessionEvents(ctx, sessionId);
520
+ if (events === null) return json(EMPTY_PROJECTION);
521
+ return json(projectSkills(events));
522
+ }
523
+ function registerSessionInfoRoute(ctx) {
524
+ const connection = ctx.get("connection");
525
+ const register = connection?.fetch?.register;
526
+ if (typeof register !== "function") return () => {};
527
+ const dispose = register.call(connection?.fetch, {
528
+ path: SESSION_INFO_PATH,
529
+ methods: ["POST"],
530
+ requestBody: "buffered",
531
+ fetch: (request) => handleSessionInfo(ctx, request)
532
+ });
533
+ return () => {
534
+ Promise.resolve().then(() => dispose()).catch(() => void 0);
535
+ };
536
+ }
537
+ //#endregion
348
538
  //#region src/index.ts
349
539
  const Config = Schema.object({
350
540
  findBar: Schema.boolean().default(true).volatile(),
@@ -352,6 +542,7 @@ const Config = Schema.object({
352
542
  compactionRow: Schema.boolean().default(true).volatile(),
353
543
  suggest: Schema.boolean().default(true).volatile(),
354
544
  stepDuration: Schema.boolean().default(true).volatile(),
545
+ sessionInfo: Schema.boolean().default(true).volatile(),
355
546
  suggestModel: Schema.string().default("").volatile(),
356
547
  suggestReasoning: Schema.string().default("").volatile(),
357
548
  suggestPrompt: Schema.string().default("").volatile()
@@ -363,12 +554,18 @@ function apply(ctx) {
363
554
  } catch (error) {
364
555
  serviceCtx.logger?.warn(`desktop-enhance: suggest route not registered: ${String(error)}`);
365
556
  }
557
+ try {
558
+ serviceCtx.effect(() => registerSessionInfoRoute(serviceCtx), "desktop-enhance: session-info route");
559
+ } catch (error) {
560
+ serviceCtx.logger?.warn(`desktop-enhance: session-info route not registered: ${String(error)}`);
561
+ }
366
562
  });
367
563
  }
368
564
  const __test = {
369
565
  SUGGEST_PATH,
370
566
  SUGGEST_MODELS_PATH,
371
567
  SUGGEST_EFFORTS_PATH,
568
+ SESSION_INFO_PATH,
372
569
  MODEL_TIMEOUT_MS,
373
570
  MAX_OUTPUT_TOKENS,
374
571
  buildPrompt,
@@ -376,7 +573,9 @@ const __test = {
376
573
  collectText,
377
574
  knownNamesOf,
378
575
  handleSuggest,
576
+ handleSessionInfo,
379
577
  registerSuggestRoute,
578
+ registerSessionInfoRoute,
380
579
  routeOf,
381
580
  quietEffortFor,
382
581
  listModels,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-desktop-enhance",
3
- "version": "0.2.3",
4
- "description": "DSH Desktop 界面增强:页内查找(Cmd+F)、轮次编号、上下文压缩行常显、建议、每步耗时。",
3
+ "version": "0.2.5",
4
+ "description": "DSH Desktop 界面增强:页内查找(Cmd+F)、轮次编号、上下文压缩行常显、建议、每步耗时、会话信息。",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./lib/index.js",