dsh-desktop-enhance 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # dsh-desktop-enhance
2
2
 
3
- 为 **DeepSeek Harness 桌面版**(DSH Desktop `0.2.0-rc.2`)补六个界面能力的插件。**自用**。
3
+ 为 **DeepSeek Harness 桌面版**(DSH Desktop `0.2.1-rc.2`)补界面与效率增强能力的插件。**自用**。
4
+
5
+ ## 功能列表
4
6
 
5
- ## 六个功能
6
7
 
7
8
  ### 页内查找
8
9
 
@@ -99,6 +100,16 @@ Web 版(浏览器里打开的那种)键位是 **`Cmd+Shift+.`** —— `Cmd+
99
100
  - **折叠与展开**:点击顶栏可把整张卡片收起成一枚紧凑胶囊(`会话信息 · Skill N`),点击分节头可单独收起/展开该分节
100
101
  - **全量 + 实时**:实时监听前端会话事件流,同时向 Host 查询完整会话日志基线,即使几百轮的长会话只加载了尾部窗口,第 1 轮注入的 Skill 也不会漏掉;被上下文压缩或裁剪替换掉的节点会自动剔除
101
102
 
103
+ ### 工具管理
104
+
105
+ 在插件设置卡片内以**可折叠前缀树**列出当前 DSH 注册的全部工具(跨全局层与所有 Agent Presets 全量扫描),并支持全局启停:
106
+
107
+ - **前缀分组与批量启停**:自动将有多个工具的前缀(如 `mcp`、`job`、`schedule`、`web`、`cordis`)收拢为可折叠分组,显示启用计数(如 `2/2`),支持一键「全部禁用 / 全部启用」整组工具;只有 1 个工具的普通名称(如 `working_directory`、`todo_write`)直接展示为单行条目,不套多余目录
108
+ - **实时搜索**:支持按工具名或描述关键词即时过滤
109
+ - **两层一致拦截**:被禁用的工具既从发给模型的 Prompt Tool Schemas 中隐藏(节省上下文 Token),又在执行守卫层(`ctx.tools.guard`)拒绝调用,跨会话持久化
110
+ - **总开关无损控制**:关闭「工具管理」总开关时立即放行所有工具,同时保留已保存的禁用列表;重新开启总开关即恢复生效
111
+
112
+
102
113
  ## 安装
103
114
 
104
115
  从 npm 装:
@@ -128,15 +139,16 @@ dsh plugin --profile desktop update dsh-desktop-enhance
128
139
 
129
140
  只改插件内容的话,**刷新页面即可**,不必重启。
130
141
 
131
- > ⚠️ **例外:如果插件更新里改了「宿主半侧」(`src/index.ts` / `src/suggest/**`),必须重启 App** —— 刷新页面不够。此时设置卡片会直接把不可用的项**置灰**并提示你重启,而不是等你点了几次之后才报「设置没写进去」。**看到那句提示 = 重启即可解决。**
142
+ > ⚠️ **例外:如果插件更新里改了「宿主半侧」(`src/index.ts` / `src/suggest/**` / `src/session-info/**` / `src/tools-manager/**`),必须重启 App** —— 刷新页面不够。此时设置卡片会直接把不可用的项**置灰**并提示你重启,而不是等你点了几次之后才报「设置没写进去」。**看到那句提示 = 重启即可解决。**
132
143
 
133
144
  ## 开关
134
145
 
135
- 六个功能各有一个开关,在**本插件自己的页面**上:
146
+ 每个功能各有一个开关,在**本插件自己的页面**上:
136
147
 
137
148
  > 侧栏 **插件** → 插件列表里选中 **`dsh-desktop-enhance`** → 页面里的 **「UI 增强」** 一栏
138
149
 
139
- 六个开关:**页内查找** / **轮次编号** / **压缩行高亮** / **建议** / **每步耗时** / **会话信息**。默认全开,装完与之前行为一致,除非你自己去关。
150
+ 开关列表:**页内查找** / **轮次编号** / **压缩行高亮** / **建议** / **每步耗时** / **会话信息** / **工具管理**。默认全开,装完与之前行为一致,除非你自己去关。
151
+
140
152
 
141
153
  「建议」关掉时,它那三个参数(模型 / 推理强度 / 指令模板)也会一并收起 —— 功能关着时它们不起作用。
142
154
 
package/lib/client.js CHANGED
@@ -41,6 +41,21 @@ window.__ModuleLoader__.load({
41
41
  "settings.stepDuration.hint": "紧跟每一行的文字显示这一步的开始时刻与耗时(如 14:32 · 23s):过程组头是整组、展开后的工具调用与思考行是它自己那一段;正在跑的那一行每秒更新",
42
42
  "settings.sessionInfo": "会话信息",
43
43
  "settings.sessionInfo.hint": "在主对话区右上角悬浮展示当前会话的信息看板(如已注入的 Skill、触发方式与轮次),点击条目可打开 Skill 预览",
44
+ "settings.toolsManager": "工具管理",
45
+ "settings.toolsManager.hint": "以可折叠前缀树列出全部已注册工具,支持单个或按前缀批量启停;被禁用的工具对模型不可见且拒绝执行,跨会话持久化",
46
+ "settings.disabledTools": "已禁用工具列表",
47
+ "settings.disabledTools.hint": "支持在下方工具树中勾选启停,或在目录未就绪时手动填写以逗号分隔的工具名",
48
+ "settings.disabledTools.placeholder": "输入要禁用的工具名(以逗号分隔,留空表示全部启用)",
49
+ "settings.tools.search": "搜索工具名或描述…",
50
+ "settings.tools.refresh": "刷新列表",
51
+ "settings.tools.enableAll": "全部启用",
52
+ "settings.tools.disableAll": "全部禁用",
53
+ "settings.tools.enable": "启用",
54
+ "settings.tools.disable": "禁用",
55
+ "settings.tools.enabled": "已启用",
56
+ "settings.tools.disabled": "已禁用",
57
+ "settings.tools.allDisabled": "全部已禁用",
58
+ "settings.tools.empty": "没有匹配的工具。",
44
59
  "settings.suggestModel": "建议用哪个模型",
45
60
  "settings.suggestModel.hint": "留空则跟随当前会话的模型;建议选一个非推理的轻量模型,否则每条建议都要等它想完",
46
61
  "settings.suggestModel.follow": "跟随当前会话",
@@ -97,6 +112,21 @@ window.__ModuleLoader__.load({
97
112
  "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
113
  "settings.sessionInfo": "Session info",
99
114
  "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",
115
+ "settings.toolsManager": "Tools manager",
116
+ "settings.toolsManager.hint": "Lists all registered tools as a collapsible prefix tree with individual and batch enable/disable controls; disabled tools are hidden from the model and denied at execution across sessions",
117
+ "settings.disabledTools": "Disabled tools",
118
+ "settings.disabledTools.hint": "Toggle tools in the prefix tree below, or enter comma-separated tool names when the catalog is unavailable",
119
+ "settings.disabledTools.placeholder": "Comma-separated tool names to disable (empty enables all)",
120
+ "settings.tools.search": "Search tool name or description…",
121
+ "settings.tools.refresh": "Refresh",
122
+ "settings.tools.enableAll": "Enable all",
123
+ "settings.tools.disableAll": "Disable all",
124
+ "settings.tools.enable": "Enable",
125
+ "settings.tools.disable": "Disable",
126
+ "settings.tools.enabled": "Enabled",
127
+ "settings.tools.disabled": "Disabled",
128
+ "settings.tools.allDisabled": "All disabled",
129
+ "settings.tools.empty": "No matching tools.",
100
130
  "settings.suggestModel": "Model for suggestions",
101
131
  "settings.suggestModel.hint": "Empty follows the current session model. Prefer a lightweight non-reasoning model, or every suggestion waits for it to finish thinking",
102
132
  "settings.suggestModel.follow": "Follow the current session",
@@ -371,18 +401,25 @@ window.__ModuleLoader__.load({
371
401
  //#region src/client/settings/gates.ts
372
402
  const SETTINGS_NS = "desktop-enhance";
373
403
  const PACKAGE_NAME = "dsh-desktop-enhance";
374
- const TEXT_FIELDS = [
404
+ const SUGGEST_TEXT_FIELDS = [
375
405
  "suggestModel",
376
406
  "suggestReasoning",
377
407
  "suggestPrompt"
378
408
  ];
409
+ const TEXT_FIELDS = [
410
+ "suggestModel",
411
+ "suggestReasoning",
412
+ "suggestPrompt",
413
+ "disabledTools"
414
+ ];
379
415
  const GATES = {
380
416
  findBar: "data-desktop-enhance-off-find-bar",
381
417
  turnPrefix: "data-desktop-enhance-off-turn-prefix",
382
418
  compactionRow: "data-desktop-enhance-off-compaction-row",
383
419
  suggest: "data-desktop-enhance-off-suggest",
384
420
  stepDuration: "data-desktop-enhance-off-step-duration",
385
- sessionInfo: "data-desktop-enhance-off-session-info"
421
+ sessionInfo: "data-desktop-enhance-off-session-info",
422
+ toolsManager: "data-desktop-enhance-off-tools-manager"
386
423
  };
387
424
  const FIELDS = [
388
425
  "findBar",
@@ -390,7 +427,8 @@ window.__ModuleLoader__.load({
390
427
  "compactionRow",
391
428
  "suggest",
392
429
  "stepDuration",
393
- "sessionInfo"
430
+ "sessionInfo",
431
+ "toolsManager"
394
432
  ];
395
433
  const ALL_FIELDS = [...FIELDS, ...TEXT_FIELDS];
396
434
  function flagOn(value, field) {
@@ -419,7 +457,7 @@ window.__ModuleLoader__.load({
419
457
  reasoning: "",
420
458
  prompt: ""
421
459
  };
422
- for (const field of TEXT_FIELDS) out[PREFERENCE_KEY[field]] = textOf(value, field);
460
+ for (const field of SUGGEST_TEXT_FIELDS) out[PREFERENCE_KEY[field]] = textOf(value, field);
423
461
  return out;
424
462
  }
425
463
  function applyGates(value, root) {
@@ -457,6 +495,7 @@ window.__ModuleLoader__.load({
457
495
  const SUGGEST_ROUTE = "/api/desktop-enhance/suggest";
458
496
  const SUGGEST_MODELS_ROUTE = "/api/desktop-enhance/suggest-models";
459
497
  const SESSION_INFO_ROUTE = "/api/desktop-enhance/session-info";
498
+ const TOOLS_ROUTE = "/api/desktop-enhance/tools";
460
499
  //#endregion
461
500
  //#region src/client/settings/catalog.ts
462
501
  function parseModelCatalog(body) {
@@ -513,33 +552,40 @@ window.__ModuleLoader__.load({
513
552
  const catalogCache = createCatalogCache();
514
553
  const CACHE_MS = 3e4;
515
554
  const SUGGEST_MODELS_URL = SUGGEST_MODELS_ROUTE;
555
+ const TOOLS_URL = TOOLS_ROUTE;
516
556
  function createCatalogCache(now = () => Date.now()) {
517
557
  const hits = new Map();
518
558
  const pending = new Map();
519
- return { async get(url, fetchImpl) {
520
- const hit = hits.get(url);
521
- if (hit !== void 0 && now() - hit.at < 3e4) return hit.value;
522
- const running = pending.get(url);
523
- if (running !== void 0) return running;
524
- const task = (async () => {
525
- try {
526
- const response = await fetchImpl(url, { method: "GET" });
527
- if (!response.ok) return void 0;
528
- const value = await response.json();
529
- hits.set(url, {
530
- at: now(),
531
- value
532
- });
533
- return value;
534
- } catch {
535
- return;
536
- } finally {
537
- pending.delete(url);
538
- }
539
- })();
540
- pending.set(url, task);
541
- return task;
542
- } };
559
+ return {
560
+ invalidate(url) {
561
+ hits.delete(url);
562
+ pending.delete(url);
563
+ },
564
+ async get(url, fetchImpl) {
565
+ const hit = hits.get(url);
566
+ if (hit !== void 0 && now() - hit.at < 3e4) return hit.value;
567
+ const running = pending.get(url);
568
+ if (running !== void 0) return running;
569
+ const task = (async () => {
570
+ try {
571
+ const response = await fetchImpl(url, { method: "GET" });
572
+ if (!response.ok) return void 0;
573
+ const value = await response.json();
574
+ hits.set(url, {
575
+ at: now(),
576
+ value
577
+ });
578
+ return value;
579
+ } catch {
580
+ return;
581
+ } finally {
582
+ pending.delete(url);
583
+ }
584
+ })();
585
+ pending.set(url, task);
586
+ return task;
587
+ }
588
+ };
543
589
  }
544
590
  const ESCAPE_SEQUENCES = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;
545
591
  const TAG_CHARACTERS = /[\u{E0000}-\u{E007F}]/u;
@@ -631,6 +677,196 @@ window.__ModuleLoader__.load({
631
677
  };
632
678
  }
633
679
  //#endregion
680
+ //#region src/shared/tools-manager.ts
681
+ const DISABLED_TOOL_PREFIX = "tool is disabled by desktop-enhance:";
682
+ function compareCodeUnits(a, b) {
683
+ return a < b ? -1 : a > b ? 1 : 0;
684
+ }
685
+ function parseDisabledTools(raw) {
686
+ const out = new Set();
687
+ if (typeof raw !== "string" || raw.length === 0) return out;
688
+ for (const piece of raw.split(/[\s,]+/)) {
689
+ const trimmed = piece.trim();
690
+ if (trimmed !== "") out.add(trimmed);
691
+ }
692
+ return out;
693
+ }
694
+ function formatDisabledTools(names) {
695
+ const unique = new Set();
696
+ for (const name of names) if (typeof name === "string") {
697
+ const trimmed = name.trim();
698
+ if (trimmed !== "") unique.add(trimmed);
699
+ }
700
+ return [...unique].sort(compareCodeUnits).join(",");
701
+ }
702
+ function splitToolName(name) {
703
+ const parts = name.split("__");
704
+ if (parts.length > 1) {
705
+ const result = [];
706
+ for (let i = 0; i < parts.length; i++) {
707
+ const part = parts[i];
708
+ if (part === void 0 || part === "") continue;
709
+ if (i === 0 || i === parts.length - 1) result.push(part);
710
+ else {
711
+ const sub = part.split("_").filter((s) => s !== "");
712
+ result.push(...sub);
713
+ }
714
+ }
715
+ return result.length > 0 ? result : [name];
716
+ }
717
+ const sub = name.split("_").filter((s) => s !== "");
718
+ return sub.length > 0 ? sub : [name];
719
+ }
720
+ function finalizeBranch(branch) {
721
+ const nodes = [];
722
+ for (const [, child] of branch.children) if (child.kind === "leaf") nodes.push(child);
723
+ else {
724
+ const children = finalizeBranch(child.branch);
725
+ const firstChild = children[0];
726
+ if (children.length === 1 && firstChild !== void 0 && firstChild.kind === "leaf" && !child.prefix.startsWith("mcp")) {
727
+ nodes.push(firstChild);
728
+ continue;
729
+ }
730
+ if (children.length === 1 && firstChild !== void 0 && firstChild.kind === "node") {
731
+ const sep = child.prefix === "mcp" ? "__" : "_";
732
+ nodes.push({
733
+ kind: "node",
734
+ prefix: firstChild.prefix,
735
+ label: `${child.label}${sep}${firstChild.label}`,
736
+ children: firstChild.children,
737
+ total: firstChild.total,
738
+ disabled: firstChild.disabled
739
+ });
740
+ continue;
741
+ }
742
+ let total = 0;
743
+ let disabled = 0;
744
+ for (const c of children) {
745
+ total += c.total;
746
+ disabled += c.kind === "leaf" ? c.disabledCount : c.disabled;
747
+ }
748
+ nodes.push({
749
+ kind: "node",
750
+ prefix: child.prefix,
751
+ label: child.label,
752
+ children,
753
+ total,
754
+ disabled
755
+ });
756
+ }
757
+ nodes.sort((a, b) => {
758
+ if (a.kind !== b.kind) return a.kind === "node" ? -1 : 1;
759
+ return compareCodeUnits(a.kind === "node" ? a.label : a.name, b.kind === "node" ? b.label : b.name);
760
+ });
761
+ return nodes;
762
+ }
763
+ function buildToolPrefixTree(tools, disabled, query = "") {
764
+ const needle = typeof query === "string" ? query.trim().toLowerCase() : "";
765
+ const filtered = needle === "" ? tools : tools.filter((t) => t.name.toLowerCase().includes(needle) || t.description.toLowerCase().includes(needle));
766
+ const root = { children: new Map() };
767
+ for (const tool of filtered) {
768
+ const segments = splitToolName(tool.name);
769
+ let current = root;
770
+ for (let i = 0; i < segments.length; i++) {
771
+ const seg = segments[i];
772
+ if (seg === void 0) continue;
773
+ if (i === segments.length - 1) {
774
+ const isOff = disabled.has(tool.name);
775
+ const leaf = {
776
+ kind: "leaf",
777
+ name: tool.name,
778
+ label: seg,
779
+ description: tool.description,
780
+ disabled: isOff,
781
+ total: 1,
782
+ disabledCount: isOff ? 1 : 0
783
+ };
784
+ const existing = current.children.get(seg);
785
+ if (existing !== void 0 && existing.kind === "branch") existing.branch.children.set(`__self__:${tool.name}`, leaf);
786
+ else current.children.set(seg, leaf);
787
+ } else {
788
+ let next = current.children.get(seg);
789
+ if (next === void 0) {
790
+ next = {
791
+ kind: "branch",
792
+ prefix: segments.slice(0, i + 1).join("_"),
793
+ label: seg,
794
+ branch: { children: new Map() }
795
+ };
796
+ current.children.set(seg, next);
797
+ } else if (next.kind === "leaf") {
798
+ const branch = { children: new Map() };
799
+ branch.children.set(`__self__:${next.name}`, next);
800
+ next = {
801
+ kind: "branch",
802
+ prefix: segments.slice(0, i + 1).join("_"),
803
+ label: seg,
804
+ branch
805
+ };
806
+ current.children.set(seg, next);
807
+ }
808
+ current = next.branch;
809
+ }
810
+ }
811
+ }
812
+ return finalizeBranch(root);
813
+ }
814
+ function collectNodeLeafNames(node) {
815
+ if (node.kind === "leaf") return [node.name];
816
+ const out = [];
817
+ for (const child of node.children) out.push(...collectNodeLeafNames(child));
818
+ return out;
819
+ }
820
+ function toggleToolInSet(current, toolName, nextDisabled) {
821
+ const next = new Set(current);
822
+ if (nextDisabled) next.add(toolName);
823
+ else next.delete(toolName);
824
+ return next;
825
+ }
826
+ function toggleNodeInSet(current, node) {
827
+ const names = collectNodeLeafNames(node);
828
+ const next = new Set(current);
829
+ if (names.some((name) => !current.has(name))) for (const name of names) next.add(name);
830
+ else for (const name of names) next.delete(name);
831
+ return next;
832
+ }
833
+ function disabledToolReason(toolName, enabled, disabled) {
834
+ if (!enabled || disabled.size === 0) return void 0;
835
+ if (disabled.has(toolName)) return `${DISABLED_TOOL_PREFIX} ${JSON.stringify(toolName)}`;
836
+ }
837
+ function filterToolSchemas(tools, enabled, disabled) {
838
+ if (!enabled || disabled.size === 0) return [...tools];
839
+ return tools.filter((tool) => !disabled.has(tool.name));
840
+ }
841
+ function parseToolCatalog(raw) {
842
+ if (raw === null || raw === void 0 || typeof raw !== "object") return {
843
+ tools: [],
844
+ available: false
845
+ };
846
+ const list = raw.tools;
847
+ if (!Array.isArray(list)) return {
848
+ tools: [],
849
+ available: false
850
+ };
851
+ const byName = new Map();
852
+ for (const item of list) {
853
+ if (item === null || item === void 0 || typeof item !== "object") continue;
854
+ const name = item.name;
855
+ if (typeof name !== "string" || name.trim() === "") continue;
856
+ const trimmed = name.trim();
857
+ if (byName.has(trimmed)) continue;
858
+ const desc = item.description;
859
+ byName.set(trimmed, {
860
+ name: trimmed,
861
+ description: typeof desc === "string" ? desc.trim() : ""
862
+ });
863
+ }
864
+ return {
865
+ tools: [...byName.values()].sort((a, b) => compareCodeUnits(a.name, b.name)),
866
+ available: true
867
+ };
868
+ }
869
+ //#endregion
634
870
  //#region src/client/settings/card.ts
635
871
  function withField(list, field) {
636
872
  return list.includes(field) ? list : [...list, field];
@@ -773,6 +1009,7 @@ window.__ModuleLoader__.load({
773
1009
  role: "alert"
774
1010
  }, t("settings.staleHost") + "(" + drifted.map((field) => labelOf(t, field)).join("、") + ")") : null;
775
1011
  const showSuggest = flagOn(value, "suggest") || readOnly;
1012
+ const showToolsManager = flagOn(value, "toolsManager") || readOnly;
776
1013
  return h("div", { "data-desktop-enhance-settings": "" }, h("div", {}, h("div", { className: "de-head-title" }, t("settings.title")), h("div", { className: "de-head-desc" }, t("settings.description"))), ...items, showSuggest ? h(SuggestSettings, {
777
1014
  key: "suggest",
778
1015
  t,
@@ -781,6 +1018,14 @@ window.__ModuleLoader__.load({
781
1018
  readOnly,
782
1019
  write,
783
1020
  drifted
1021
+ }) : null, showToolsManager ? h(ToolsManagerSettings, {
1022
+ key: "toolsManager",
1023
+ t,
1024
+ value,
1025
+ busy,
1026
+ readOnly,
1027
+ write,
1028
+ drifted
784
1029
  }) : null, note);
785
1030
  }
786
1031
  function SuggestSettings({ t, value, busy, readOnly, write, drifted }) {
@@ -1008,6 +1253,199 @@ window.__ModuleLoader__.load({
1008
1253
  }, t("settings.suggestPrompt.reset"))])
1009
1254
  ]);
1010
1255
  }
1256
+ function ToolsManagerSettings({ t, value, busy, readOnly, write, drifted }) {
1257
+ const disabledSet = parseDisabledTools(textOf(value, "disabledTools"));
1258
+ const canonicalDisabled = formatDisabledTools(disabledSet);
1259
+ const controlDisabled = readOnly || busy.includes("disabledTools") || drifted.includes("disabledTools");
1260
+ const [catalog, setCatalog] = React.useState(null);
1261
+ const [query, setQuery] = React.useState("");
1262
+ const [expandedMap, setExpandedMap] = React.useState({});
1263
+ const [refreshTick, setRefreshTick] = React.useState(0);
1264
+ React.useEffect(() => {
1265
+ let live = true;
1266
+ catalogCache.get(TOOLS_URL, globalThis.fetch).then((body) => {
1267
+ if (live) setCatalog(parseToolCatalog(body));
1268
+ }).catch(() => {
1269
+ if (live) setCatalog({
1270
+ tools: [],
1271
+ available: false
1272
+ });
1273
+ });
1274
+ return () => {
1275
+ live = false;
1276
+ };
1277
+ }, [refreshTick]);
1278
+ const commitSet = (nextSet) => {
1279
+ write("disabledTools", formatDisabledTools(nextSet));
1280
+ };
1281
+ const toggleExpand = (prefix, defaultOpen) => {
1282
+ setExpandedMap((prev) => {
1283
+ const current = prev[prefix] ?? defaultOpen;
1284
+ return {
1285
+ ...prev,
1286
+ [prefix]: !current
1287
+ };
1288
+ });
1289
+ };
1290
+ const renderTreeNode = (node, depth) => {
1291
+ const indentStyle = depth > 0 ? { paddingLeft: `${depth * 18 + 8}px` } : void 0;
1292
+ if (node.kind === "leaf") return h("div", {
1293
+ key: `leaf:${node.name}`,
1294
+ className: "de-tools-leaf-row",
1295
+ "data-desktop-enhance-tool-leaf": node.name,
1296
+ "data-disabled": node.disabled ? "true" : "false",
1297
+ style: indentStyle
1298
+ }, [h("div", {
1299
+ key: "info",
1300
+ className: "de-tools-leaf-info"
1301
+ }, [h("div", {
1302
+ key: "name",
1303
+ className: "de-tools-leaf-name"
1304
+ }, node.name), node.description !== "" ? h("div", {
1305
+ key: "desc",
1306
+ className: "de-tools-leaf-desc"
1307
+ }, node.description) : null]), h("div", {
1308
+ key: "ctrl",
1309
+ className: "de-tools-controls"
1310
+ }, [h("span", {
1311
+ key: "pill",
1312
+ className: "de-tools-pill",
1313
+ "data-disabled": node.disabled ? "true" : "false"
1314
+ }, node.disabled ? t("settings.tools.disabled") : t("settings.tools.enabled")), h("button", {
1315
+ key: "btn",
1316
+ type: "button",
1317
+ className: "de-button",
1318
+ "data-desktop-enhance-tool-toggle": node.name,
1319
+ disabled: controlDisabled,
1320
+ onClick: () => commitSet(toggleToolInSet(disabledSet, node.name, !node.disabled))
1321
+ }, node.disabled ? t("settings.tools.enable") : t("settings.tools.disable"))])]);
1322
+ const defaultOpen = query.trim() !== "" || depth < 1;
1323
+ const isOpen = expandedMap[node.prefix] ?? defaultOpen;
1324
+ const allDisabled = node.disabled === node.total;
1325
+ const anyEnabled = node.disabled < node.total;
1326
+ const enabledCount = node.total - node.disabled;
1327
+ return h("div", {
1328
+ key: `node:${node.prefix}`,
1329
+ className: "de-tools-node",
1330
+ "data-desktop-enhance-tool-node": node.prefix
1331
+ }, [h("div", {
1332
+ key: "head",
1333
+ className: "de-tools-node-header",
1334
+ style: indentStyle,
1335
+ onClick: () => toggleExpand(node.prefix, defaultOpen)
1336
+ }, [h("span", {
1337
+ key: "title",
1338
+ className: "de-tools-node-title"
1339
+ }, [h("span", {
1340
+ key: "arrow",
1341
+ "aria-hidden": "true"
1342
+ }, isOpen ? "▾" : "▸"), h("span", { key: "label" }, node.label)]), h("div", {
1343
+ key: "ctrl",
1344
+ className: "de-tools-controls"
1345
+ }, [h("span", {
1346
+ key: "pill",
1347
+ className: "de-tools-pill",
1348
+ "data-disabled": allDisabled ? "true" : "false"
1349
+ }, allDisabled ? t("settings.tools.allDisabled") : `${enabledCount}/${node.total}`), h("button", {
1350
+ key: "batch",
1351
+ type: "button",
1352
+ className: "de-button",
1353
+ "data-desktop-enhance-tool-batch": node.prefix,
1354
+ disabled: controlDisabled,
1355
+ onClick: (e) => {
1356
+ e?.stopPropagation?.();
1357
+ commitSet(toggleNodeInSet(disabledSet, node));
1358
+ }
1359
+ }, anyEnabled ? t("settings.tools.disableAll") : t("settings.tools.enableAll"))])]), isOpen ? h("div", {
1360
+ key: "children",
1361
+ className: "de-tools-node-children"
1362
+ }, node.children.map((child) => renderTreeNode(child, depth + 1))) : null]);
1363
+ };
1364
+ const treeNodes = catalog !== null && catalog.available ? buildToolPrefixTree(catalog.tools, disabledSet, query) : [];
1365
+ const disabledInCatalog = catalog !== null && catalog.available ? catalog.tools.filter((tool) => disabledSet.has(tool.name)).length : 0;
1366
+ const onSearchChange = (event) => {
1367
+ const next = event?.target?.value;
1368
+ if (typeof next === "string") setQuery(next);
1369
+ };
1370
+ return h("div", {
1371
+ className: "de-tools-settings",
1372
+ "data-desktop-enhance-tools-manager": ""
1373
+ }, [catalog === null || !catalog.available ? h("div", {
1374
+ key: "field",
1375
+ className: "de-item de-item-block"
1376
+ }, [h("div", {
1377
+ key: "text",
1378
+ className: "de-text"
1379
+ }, [h("div", {
1380
+ key: "title",
1381
+ className: "de-title"
1382
+ }, t("settings.disabledTools")), h("div", {
1383
+ key: "desc",
1384
+ className: "de-desc"
1385
+ }, t("settings.disabledTools.hint"))]), h("input", {
1386
+ key: "input",
1387
+ type: "text",
1388
+ className: "de-text-input",
1389
+ "data-desktop-enhance-field": "disabledTools",
1390
+ placeholder: t("settings.disabledTools.placeholder"),
1391
+ value: canonicalDisabled,
1392
+ disabled: controlDisabled,
1393
+ onChange: (event) => {
1394
+ const next = event?.target?.value;
1395
+ if (typeof next === "string") commitSet(parseDisabledTools(next));
1396
+ }
1397
+ })]) : h("div", {
1398
+ key: "catalog",
1399
+ className: "de-item de-item-block"
1400
+ }, [
1401
+ h("div", {
1402
+ key: "toolbar",
1403
+ className: "de-tools-toolbar"
1404
+ }, [h("span", {
1405
+ key: "summary",
1406
+ className: "de-tools-summary",
1407
+ "data-desktop-enhance-tools-summary": ""
1408
+ }, `${catalog.tools.length - disabledInCatalog}/${catalog.tools.length}`), h("div", {
1409
+ key: "actions",
1410
+ className: "de-tools-actions"
1411
+ }, [h("button", {
1412
+ key: "enable-all",
1413
+ type: "button",
1414
+ className: "de-button",
1415
+ "data-desktop-enhance-field": "disabledTools",
1416
+ "data-desktop-enhance-action": "tools-enable-all",
1417
+ disabled: controlDisabled || disabledSet.size === 0,
1418
+ onClick: () => write("disabledTools", "")
1419
+ }, t("settings.tools.enableAll")), h("button", {
1420
+ key: "refresh",
1421
+ type: "button",
1422
+ className: "de-button",
1423
+ "data-desktop-enhance-action": "tools-refresh",
1424
+ disabled: readOnly,
1425
+ onClick: () => {
1426
+ catalogCache.invalidate?.(TOOLS_URL);
1427
+ setRefreshTick((n) => n + 1);
1428
+ }
1429
+ }, t("settings.tools.refresh"))])]),
1430
+ h("input", {
1431
+ key: "search",
1432
+ type: "search",
1433
+ className: "de-text-input",
1434
+ "data-desktop-enhance-tools-search": "",
1435
+ placeholder: t("settings.tools.search"),
1436
+ value: query,
1437
+ onInput: onSearchChange,
1438
+ onChange: onSearchChange
1439
+ }),
1440
+ treeNodes.length === 0 ? h("div", {
1441
+ key: "empty",
1442
+ className: "de-tools-empty"
1443
+ }, t("settings.tools.empty")) : h("div", {
1444
+ key: "tree",
1445
+ className: "de-tools-tree"
1446
+ }, treeNodes.map((node) => renderTreeNode(node, 0)))
1447
+ ])]);
1448
+ }
1011
1449
  //#endregion
1012
1450
  //#region src/client/timing/index.ts
1013
1451
  function finite(value) {
@@ -2027,13 +2465,11 @@ window.__ModuleLoader__.load({
2027
2465
  const seq = nonNegativeInt(ev.seq);
2028
2466
  if (seq !== void 0 && (windowStartSeq === null || seq < windowStartSeq)) windowStartSeq = seq;
2029
2467
  const range = replaceRangeOf(ev.surfaceOp);
2030
- if (range !== null) {
2031
- replacedRanges.push(range);
2032
- continue;
2033
- }
2468
+ if (range !== null) replacedRanges.push(range);
2034
2469
  const data = asRecord(ev.data);
2035
2470
  const turnInEvent = positiveInt(data?.turn);
2036
- if (turnInEvent !== void 0) currentTurn = turnInEvent;
2471
+ if (range === null && turnInEvent !== void 0) currentTurn = turnInEvent;
2472
+ const effectiveTurn = turnInEvent ?? currentTurn;
2037
2473
  const type = nonEmptyString(ev.type);
2038
2474
  if (type === "compaction/summary" || type === "compaction/prune") {
2039
2475
  const list = data?.shadowedSeqs;
@@ -2053,7 +2489,7 @@ window.__ModuleLoader__.load({
2053
2489
  name,
2054
2490
  trigger: "user",
2055
2491
  seq,
2056
- ...currentTurn === void 0 ? {} : { turn: currentTurn }
2492
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
2057
2493
  });
2058
2494
  continue;
2059
2495
  }
@@ -2063,7 +2499,7 @@ window.__ModuleLoader__.load({
2063
2499
  name,
2064
2500
  trigger: "agent",
2065
2501
  seq,
2066
- ...currentTurn === void 0 ? {} : { turn: currentTurn }
2502
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
2067
2503
  });
2068
2504
  }
2069
2505
  }
@@ -2423,7 +2859,7 @@ window.__ModuleLoader__.load({
2423
2859
  }
2424
2860
  //#endregion
2425
2861
  //#region \0dsh-desktop-enhance-css:/Users/lijiechengbj/projects/AI-agents/dsh-desktop-enhance/src/client/styles/enhance.css.mjs
2426
- 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";
2862
+ 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\n\n:root[data-desktop-enhance-off-tools-manager] [data-desktop-enhance-tools-manager] {\n display: none !important;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-settings {\n display: flex;\n flex-direction: column;\n gap: 10px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 4px;\n padding-top: 12px;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-summary {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11.5px;\n color: var(--dsw-alias-label-secondary);\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-tree {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 360px;\n overflow-y: auto;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-sm);\n padding: 6px;\n background: var(--dsw-alias-bg-layer-2);\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-node-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 5px 8px;\n border-radius: var(--dsw-radius-xs);\n cursor: pointer;\n user-select: none;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-node-header:hover,\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-leaf-row:hover {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-node-title {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 12.5px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n min-width: 0;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-leaf-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 10px;\n padding: 5px 8px;\n border-radius: var(--dsw-radius-xs);\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-leaf-info {\n flex: 1 1 auto;\n min-width: 0;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-leaf-name {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 12.5px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n word-break: break-all;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-leaf-row[data-disabled=\"true\"] .de-tools-leaf-name {\n color: var(--dsw-alias-label-tertiary);\n text-decoration: line-through;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-leaf-desc {\n font-size: 11.5px;\n line-height: 17px;\n color: var(--dsw-alias-label-tertiary);\n margin-top: 2px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n word-break: break-word;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-controls {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-pill {\n font-family: var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11px;\n padding: 1px 6px;\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:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-pill[data-disabled=\"true\"] {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-tertiary);\n}\n:root:not([data-desktop-enhance-off-tools-manager]) .de-tools-empty {\n padding: 8px;\n color: var(--dsw-alias-label-tertiary);\n font: var(--dsw-font-xxs-12);\n}\n\n";
2427
2863
  //#endregion
2428
2864
  //#region src/client/index.tsx
2429
2865
  const inject = [
@@ -2625,6 +3061,7 @@ window.__ModuleLoader__.load({
2625
3061
  quietEffortOf,
2626
3062
  splitRoute,
2627
3063
  FOLLOW_SESSION,
3064
+ SUGGEST_TEXT_FIELDS,
2628
3065
  TEXT_FIELDS,
2629
3066
  ALL_FIELDS,
2630
3067
  undeclaredFields,
@@ -2635,7 +3072,9 @@ window.__ModuleLoader__.load({
2635
3072
  parseEfforts,
2636
3073
  catalogCache,
2637
3074
  CACHE_MS,
3075
+ TOOLS_URL,
2638
3076
  SuggestSettings,
3077
+ ToolsManagerSettings,
2639
3078
  ModelPicker,
2640
3079
  zh,
2641
3080
  en,
@@ -2663,7 +3102,18 @@ window.__ModuleLoader__.load({
2663
3102
  SESSION_INFO_URL,
2664
3103
  requestSessionInfo,
2665
3104
  openSkillInHost,
2666
- installSessionInfo
3105
+ installSessionInfo,
3106
+ DISABLED_TOOL_PREFIX,
3107
+ buildToolPrefixTree,
3108
+ collectNodeLeafNames,
3109
+ disabledToolReason,
3110
+ filterToolSchemas,
3111
+ formatDisabledTools,
3112
+ parseDisabledTools,
3113
+ parseToolCatalog,
3114
+ splitToolName,
3115
+ toggleNodeInSet,
3116
+ toggleToolInSet
2667
3117
  };
2668
3118
  exports.apply = apply;
2669
3119
  exports.inject = inject;
package/lib/index.d.ts CHANGED
@@ -94,6 +94,32 @@ declare function registerSuggestRoute(ctx: HostContext): () => void;
94
94
  declare function handleSessionInfo(ctx: HostContext, request: Request): Promise<Response>;
95
95
  declare function registerSessionInfoRoute(ctx: HostContext): () => void;
96
96
  //#endregion
97
+ //#region src/shared/tools-manager.d.ts
98
+ interface ToolSummary {
99
+ readonly name: string;
100
+ readonly description: string;
101
+ }
102
+ //#endregion
103
+ //#region src/tools-manager/host.d.ts
104
+ interface ToolsPolicyState {
105
+ readonly enabled: boolean;
106
+ readonly disabled: ReadonlySet<string>;
107
+ }
108
+ interface HostPromptAssembly {
109
+ readonly tools?: readonly {
110
+ readonly name: string;
111
+ }[];
112
+ readonly [key: string]: unknown;
113
+ }
114
+ interface ToolsHostContext extends HostContext {
115
+ on?(event: string, listener: (assembly: HostPromptAssembly, context: unknown, next: () => Promise<HostPromptAssembly>) => Promise<HostPromptAssembly>): () => void;
116
+ }
117
+ declare function readToolsConfig(config: unknown): ToolsPolicyState;
118
+ declare function collectAllToolSchemas(ctx: HostContext): Promise<readonly ToolSummary[]>;
119
+ declare function installToolsPolicy(ctx: ToolsHostContext, readState: () => ToolsPolicyState): () => void;
120
+ declare function handleToolsCatalog(ctx: HostContext): Promise<Response>;
121
+ declare function registerToolsRoute(ctx: HostContext): () => void;
122
+ //#endregion
97
123
  //#region src/index.d.ts
98
124
  export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
99
125
  findBar: Schema<boolean, boolean, "volatile-defined">;
@@ -102,9 +128,11 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
102
128
  suggest: Schema<boolean, boolean, "volatile-defined">;
103
129
  stepDuration: Schema<boolean, boolean, "volatile-defined">;
104
130
  sessionInfo: Schema<boolean, boolean, "volatile-defined">;
131
+ toolsManager: Schema<boolean, boolean, "volatile-defined">;
105
132
  suggestModel: Schema<string, string, "volatile-defined">;
106
133
  suggestReasoning: Schema<string, string, "volatile-defined">;
107
134
  suggestPrompt: Schema<string, string, "volatile-defined">;
135
+ disabledTools: Schema<string, string, "volatile-defined">;
108
136
  }>>, Schemastery.ObjectT<NoInfer<{
109
137
  findBar: Schema<boolean, boolean, "volatile-defined">;
110
138
  turnPrefix: Schema<boolean, boolean, "volatile-defined">;
@@ -112,16 +140,19 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
112
140
  suggest: Schema<boolean, boolean, "volatile-defined">;
113
141
  stepDuration: Schema<boolean, boolean, "volatile-defined">;
114
142
  sessionInfo: Schema<boolean, boolean, "volatile-defined">;
143
+ toolsManager: Schema<boolean, boolean, "volatile-defined">;
115
144
  suggestModel: Schema<string, string, "volatile-defined">;
116
145
  suggestReasoning: Schema<string, string, "volatile-defined">;
117
146
  suggestPrompt: Schema<string, string, "volatile-defined">;
147
+ disabledTools: Schema<string, string, "volatile-defined">;
118
148
  }>>, "plain">;
119
- export declare function apply(ctx: HostContext): void;
149
+ export declare function apply(ctx: ToolsHostContext, config?: unknown): void;
120
150
  export declare const __test: {
121
151
  SUGGEST_PATH: string;
122
152
  SUGGEST_MODELS_PATH: string;
123
153
  SUGGEST_EFFORTS_PATH: string;
124
154
  SESSION_INFO_PATH: string;
155
+ TOOLS_PATH: string;
125
156
  MODEL_TIMEOUT_MS: number;
126
157
  MAX_OUTPUT_TOKENS: number;
127
158
  buildPrompt: typeof buildPrompt;
@@ -130,8 +161,13 @@ export declare const __test: {
130
161
  knownNamesOf: typeof knownNamesOf;
131
162
  handleSuggest: typeof handleSuggest;
132
163
  handleSessionInfo: typeof handleSessionInfo;
164
+ handleToolsCatalog: typeof handleToolsCatalog;
165
+ collectAllToolSchemas: typeof collectAllToolSchemas;
166
+ installToolsPolicy: typeof installToolsPolicy;
167
+ readToolsConfig: typeof readToolsConfig;
133
168
  registerSuggestRoute: typeof registerSuggestRoute;
134
169
  registerSessionInfoRoute: typeof registerSessionInfoRoute;
170
+ registerToolsRoute: typeof registerToolsRoute;
135
171
  routeOf: typeof routeOf;
136
172
  quietEffortFor: typeof quietEffortFor;
137
173
  listModels: typeof listModels;
package/lib/index.js CHANGED
@@ -94,6 +94,7 @@ 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
96
  const SESSION_INFO_ROUTE = "/api/desktop-enhance/session-info";
97
+ const TOOLS_ROUTE = "/api/desktop-enhance/tools";
97
98
  //#endregion
98
99
  //#region src/suggest/host.ts
99
100
  const SUGGEST_PATH = SUGGEST_ROUTE;
@@ -157,7 +158,7 @@ function knownNamesOf(messages) {
157
158
  return found ? names : null;
158
159
  }
159
160
  async function handleSuggest(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
160
- const empty = () => json$1({ items: [] });
161
+ const empty = () => json$2({ items: [] });
161
162
  let sessionId;
162
163
  let preference = FOLLOW_SESSION;
163
164
  try {
@@ -169,9 +170,9 @@ async function handleSuggest(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
169
170
  prompt: typeof body.prompt === "string" ? body.prompt : ""
170
171
  };
171
172
  } catch {
172
- return json$1({ items: [] }, 400);
173
+ return json$2({ items: [] }, 400);
173
174
  }
174
- if (typeof sessionId !== "string" || sessionId === "") return json$1({ items: [] }, 400);
175
+ if (typeof sessionId !== "string" || sessionId === "") return json$2({ items: [] }, 400);
175
176
  const sessions = ctx.get("sessions");
176
177
  const llm = ctx.get("llm");
177
178
  if (sessions === null || sessions === void 0 || llm === null || llm === void 0) return empty();
@@ -230,7 +231,7 @@ async function handleSuggest(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
230
231
  }
231
232
  function withDiag(ctx, body, record) {
232
233
  logDiag(ctx, record);
233
- return json$1({
234
+ return json$2({
234
235
  ...body,
235
236
  diag: record
236
237
  });
@@ -302,7 +303,7 @@ async function listEfforts(ctx, provider, model) {
302
303
  };
303
304
  }
304
305
  }
305
- function json$1(body, status = 200) {
306
+ function json$2(body, status = 200) {
306
307
  return new Response(JSON.stringify(body), {
307
308
  status,
308
309
  headers: { "content-type": "application/json" }
@@ -323,7 +324,7 @@ function registerSuggestRoute(ctx) {
323
324
  path: SUGGEST_MODELS_PATH,
324
325
  methods: ["GET"],
325
326
  requestBody: "buffered",
326
- fetch: async () => json$1(await listModels(ctx))
327
+ fetch: async () => json$2(await listModels(ctx))
327
328
  }));
328
329
  disposers.push(registry.register({
329
330
  path: SUGGEST_EFFORTS_PATH,
@@ -331,11 +332,11 @@ function registerSuggestRoute(ctx) {
331
332
  requestBody: "buffered",
332
333
  fetch: async (request) => {
333
334
  const route = splitRoute(new URL(request.url).searchParams.get("model") ?? "");
334
- if (route === null) return json$1({
335
+ if (route === null) return json$2({
335
336
  efforts: [],
336
337
  defaultEffort: null
337
338
  });
338
- return json$1(await listEfforts(ctx, route.provider, route.model));
339
+ return json$2(await listEfforts(ctx, route.provider, route.model));
339
340
  }
340
341
  }));
341
342
  } catch (error) {
@@ -411,13 +412,11 @@ function projectSkills(events) {
411
412
  const seq = nonNegativeInt(ev.seq);
412
413
  if (seq !== void 0 && (windowStartSeq === null || seq < windowStartSeq)) windowStartSeq = seq;
413
414
  const range = replaceRangeOf(ev.surfaceOp);
414
- if (range !== null) {
415
- replacedRanges.push(range);
416
- continue;
417
- }
415
+ if (range !== null) replacedRanges.push(range);
418
416
  const data = asRecord(ev.data);
419
417
  const turnInEvent = positiveInt(data?.turn);
420
- if (turnInEvent !== void 0) currentTurn = turnInEvent;
418
+ if (range === null && turnInEvent !== void 0) currentTurn = turnInEvent;
419
+ const effectiveTurn = turnInEvent ?? currentTurn;
421
420
  const type = nonEmptyString(ev.type);
422
421
  if (type === "compaction/summary" || type === "compaction/prune") {
423
422
  const list = data?.shadowedSeqs;
@@ -437,7 +436,7 @@ function projectSkills(events) {
437
436
  name,
438
437
  trigger: "user",
439
438
  seq,
440
- ...currentTurn === void 0 ? {} : { turn: currentTurn }
439
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
441
440
  });
442
441
  continue;
443
442
  }
@@ -447,7 +446,7 @@ function projectSkills(events) {
447
446
  name,
448
447
  trigger: "agent",
449
448
  seq,
450
- ...currentTurn === void 0 ? {} : { turn: currentTurn }
449
+ ...effectiveTurn === void 0 ? {} : { turn: effectiveTurn }
451
450
  });
452
451
  }
453
452
  }
@@ -470,7 +469,7 @@ const EMPTY_PROJECTION = {
470
469
  replacedRanges: [],
471
470
  shadowedSeqs: []
472
471
  };
473
- function json(body, status = 200) {
472
+ function json$1(body, status = 200) {
474
473
  return new Response(JSON.stringify(body), {
475
474
  status,
476
475
  headers: { "content-type": "application/json" }
@@ -515,12 +514,12 @@ async function handleSessionInfo(ctx, request) {
515
514
  try {
516
515
  sessionId = (await request.json())?.sessionId;
517
516
  } catch {
518
- return json(EMPTY_PROJECTION, 400);
517
+ return json$1(EMPTY_PROJECTION, 400);
519
518
  }
520
- if (typeof sessionId !== "string" || sessionId === "") return json(EMPTY_PROJECTION, 400);
519
+ if (typeof sessionId !== "string" || sessionId === "") return json$1(EMPTY_PROJECTION, 400);
521
520
  const events = await readAllSessionEvents(ctx, sessionId);
522
- if (events === null) return json(EMPTY_PROJECTION);
523
- return json(projectSkills(events));
521
+ if (events === null) return json$1(EMPTY_PROJECTION);
522
+ return json$1(projectSkills(events));
524
523
  }
525
524
  function registerSessionInfoRoute(ctx) {
526
525
  const connection = ctx.get("connection");
@@ -537,6 +536,192 @@ function registerSessionInfoRoute(ctx) {
537
536
  };
538
537
  }
539
538
  //#endregion
539
+ //#region src/shared/tools-manager.ts
540
+ const DISABLED_TOOL_PREFIX = "tool is disabled by desktop-enhance:";
541
+ function compareCodeUnits(a, b) {
542
+ return a < b ? -1 : a > b ? 1 : 0;
543
+ }
544
+ function parseDisabledTools(raw) {
545
+ const out = new Set();
546
+ if (typeof raw !== "string" || raw.length === 0) return out;
547
+ for (const piece of raw.split(/[\s,]+/)) {
548
+ const trimmed = piece.trim();
549
+ if (trimmed !== "") out.add(trimmed);
550
+ }
551
+ return out;
552
+ }
553
+ function disabledToolReason(toolName, enabled, disabled) {
554
+ if (!enabled || disabled.size === 0) return void 0;
555
+ if (disabled.has(toolName)) return `${DISABLED_TOOL_PREFIX} ${JSON.stringify(toolName)}`;
556
+ }
557
+ function filterToolSchemas(tools, enabled, disabled) {
558
+ if (!enabled || disabled.size === 0) return [...tools];
559
+ return tools.filter((tool) => !disabled.has(tool.name));
560
+ }
561
+ //#endregion
562
+ //#region src/tools-manager/host.ts
563
+ const TOOLS_PATH = TOOLS_ROUTE;
564
+ function json(body, status = 200) {
565
+ return new Response(JSON.stringify(body), {
566
+ status,
567
+ headers: { "content-type": "application/json" }
568
+ });
569
+ }
570
+ function isVolatileCell(cell) {
571
+ return cell !== null && typeof cell === "object" && typeof cell.get === "function";
572
+ }
573
+ function unwrapVolatile(cell) {
574
+ if (isVolatileCell(cell)) try {
575
+ return cell.get();
576
+ } catch {
577
+ return;
578
+ }
579
+ if (cell !== null && typeof cell === "object") return void 0;
580
+ return cell;
581
+ }
582
+ function readToolsConfig(config) {
583
+ if (config === null || config === void 0 || typeof config !== "object") return {
584
+ enabled: true,
585
+ disabled: new Set()
586
+ };
587
+ const raw = config;
588
+ const flag = unwrapVolatile(raw.toolsManager);
589
+ const list = unwrapVolatile(raw.disabledTools);
590
+ return {
591
+ enabled: typeof flag === "boolean" ? flag : true,
592
+ disabled: parseDisabledTools(list)
593
+ };
594
+ }
595
+ async function disposePresetLease(lease) {
596
+ if (lease === null || lease === void 0 || typeof lease !== "object") return;
597
+ try {
598
+ const asyncDispose = lease[Symbol.asyncDispose];
599
+ if (typeof asyncDispose === "function") {
600
+ await asyncDispose.call(lease);
601
+ return;
602
+ }
603
+ const syncDispose = lease[Symbol.dispose];
604
+ if (typeof syncDispose === "function") syncDispose.call(lease);
605
+ } catch {}
606
+ }
607
+ function recordSchemas(target, list) {
608
+ if (!Array.isArray(list)) return;
609
+ for (const item of list) {
610
+ if (item === null || item === void 0 || typeof item !== "object") continue;
611
+ const name = typeof item.name === "string" ? item.name.trim() : "";
612
+ if (name === "" || name === "run_code" || target.has(name)) continue;
613
+ const description = typeof item.description === "string" ? item.description.trim() : "";
614
+ target.set(name, {
615
+ name,
616
+ description
617
+ });
618
+ }
619
+ }
620
+ async function collectAllToolSchemas(ctx) {
621
+ const toolsService = ctx.get("tools");
622
+ if (toolsService === null || toolsService === void 0 || typeof toolsService.schemas !== "function") return [];
623
+ const collected = new Map();
624
+ try {
625
+ recordSchemas(collected, toolsService.schemas());
626
+ } catch {}
627
+ const presetsService = ctx.get("agentPresets");
628
+ if (presetsService !== null && presetsService !== void 0 && typeof presetsService.list === "function" && typeof presetsService.acquireScope === "function") {
629
+ let presets = [];
630
+ try {
631
+ const rows = await presetsService.list();
632
+ if (Array.isArray(rows)) presets = rows;
633
+ } catch {}
634
+ for (const row of presets) {
635
+ if (row === null || row === void 0 || typeof row !== "object") continue;
636
+ if (row.broken !== void 0 && row.broken !== null && row.broken !== "") continue;
637
+ const id = typeof row.id === "string" ? row.id.trim() : "";
638
+ if (id === "") continue;
639
+ let lease;
640
+ try {
641
+ lease = await presetsService.acquireScope(id);
642
+ if (lease !== null && lease !== void 0 && lease.key !== void 0) recordSchemas(collected, toolsService.schemas(lease.key));
643
+ } catch {} finally {
644
+ await disposePresetLease(lease);
645
+ }
646
+ }
647
+ }
648
+ try {
649
+ const scopedMap = toolsService.layers?.scoped;
650
+ if (scopedMap !== void 0 && typeof scopedMap.keys === "function") for (const scopeKey of scopedMap.keys()) try {
651
+ recordSchemas(collected, toolsService.schemas(scopeKey));
652
+ } catch {}
653
+ } catch {}
654
+ return [...collected.values()].sort((a, b) => compareCodeUnits(a.name, b.name));
655
+ }
656
+ function installToolsPolicy(ctx, readState) {
657
+ const disposers = [];
658
+ if (typeof ctx.on === "function") try {
659
+ const offAssemble = ctx.on("system-prompt/assemble", async (_assembly, _context, next) => {
660
+ const result = await next();
661
+ const { enabled, disabled } = readState();
662
+ if (!enabled || disabled.size === 0 || !Array.isArray(result?.tools)) return result;
663
+ const filtered = filterToolSchemas(result.tools, enabled, disabled);
664
+ if (filtered.length === result.tools.length) return result;
665
+ return {
666
+ ...result,
667
+ tools: filtered
668
+ };
669
+ });
670
+ if (typeof offAssemble === "function") disposers.push(offAssemble);
671
+ } catch (error) {
672
+ ctx.logger?.warn(`desktop-enhance: tools assemble hook failed: ${String(error)}`);
673
+ }
674
+ const attachGuard = (targetCtx) => {
675
+ const toolsService = targetCtx.get("tools");
676
+ if (toolsService === null || toolsService === void 0 || typeof toolsService.guard !== "function") return;
677
+ try {
678
+ const offGuard = toolsService.guard((exec) => {
679
+ const { enabled, disabled } = readState();
680
+ return disabledToolReason(exec.name, enabled, disabled);
681
+ });
682
+ if (typeof offGuard === "function") disposers.push(offGuard);
683
+ } catch (error) {
684
+ targetCtx.logger?.warn(`desktop-enhance: tools guard failed: ${String(error)}`);
685
+ }
686
+ };
687
+ const toolsNow = ctx.get("tools");
688
+ if (toolsNow !== null && toolsNow !== void 0 && typeof toolsNow.guard === "function") attachGuard(ctx);
689
+ else if (typeof ctx.inject === "function") try {
690
+ const offInject = ctx.inject(["tools"], (tctx) => {
691
+ attachGuard(tctx);
692
+ });
693
+ if (typeof offInject === "function") disposers.push(() => {
694
+ offInject();
695
+ });
696
+ } catch {}
697
+ return () => {
698
+ for (const dispose of disposers.splice(0)) try {
699
+ dispose();
700
+ } catch {}
701
+ };
702
+ }
703
+ async function handleToolsCatalog(ctx) {
704
+ try {
705
+ return json({ tools: await collectAllToolSchemas(ctx) });
706
+ } catch {
707
+ return json({ tools: [] });
708
+ }
709
+ }
710
+ function registerToolsRoute(ctx) {
711
+ const connection = ctx.get("connection");
712
+ const register = connection?.fetch?.register;
713
+ if (typeof register !== "function") return () => {};
714
+ const dispose = register.call(connection?.fetch, {
715
+ path: TOOLS_PATH,
716
+ methods: ["GET"],
717
+ requestBody: "buffered",
718
+ fetch: () => handleToolsCatalog(ctx)
719
+ });
720
+ return () => {
721
+ Promise.resolve().then(() => dispose()).catch(() => void 0);
722
+ };
723
+ }
724
+ //#endregion
540
725
  //#region src/index.ts
541
726
  const Config = Schema.object({
542
727
  findBar: Schema.boolean().default(true).volatile(),
@@ -545,11 +730,13 @@ const Config = Schema.object({
545
730
  suggest: Schema.boolean().default(true).volatile(),
546
731
  stepDuration: Schema.boolean().default(true).volatile(),
547
732
  sessionInfo: Schema.boolean().default(true).volatile(),
733
+ toolsManager: Schema.boolean().default(true).volatile(),
548
734
  suggestModel: Schema.string().default("").volatile(),
549
735
  suggestReasoning: Schema.string().default("").volatile(),
550
- suggestPrompt: Schema.string().default("").volatile()
736
+ suggestPrompt: Schema.string().default("").volatile(),
737
+ disabledTools: Schema.string().default("").volatile()
551
738
  });
552
- function apply(ctx) {
739
+ function apply(ctx, config) {
553
740
  ctx.inject(["connection"], (serviceCtx) => {
554
741
  try {
555
742
  serviceCtx.effect(() => registerSuggestRoute(serviceCtx), "desktop-enhance: suggest route");
@@ -561,6 +748,16 @@ function apply(ctx) {
561
748
  } catch (error) {
562
749
  serviceCtx.logger?.warn(`desktop-enhance: session-info route not registered: ${String(error)}`);
563
750
  }
751
+ try {
752
+ serviceCtx.effect(() => registerToolsRoute(serviceCtx), "desktop-enhance: tools route");
753
+ } catch (error) {
754
+ serviceCtx.logger?.warn(`desktop-enhance: tools route not registered: ${String(error)}`);
755
+ }
756
+ try {
757
+ serviceCtx.effect(() => installToolsPolicy(serviceCtx, () => readToolsConfig(config)), "desktop-enhance: tools policy");
758
+ } catch (error) {
759
+ serviceCtx.logger?.warn(`desktop-enhance: tools policy not installed: ${String(error)}`);
760
+ }
564
761
  });
565
762
  }
566
763
  const __test = {
@@ -568,6 +765,7 @@ const __test = {
568
765
  SUGGEST_MODELS_PATH,
569
766
  SUGGEST_EFFORTS_PATH,
570
767
  SESSION_INFO_PATH,
768
+ TOOLS_PATH,
571
769
  MODEL_TIMEOUT_MS,
572
770
  MAX_OUTPUT_TOKENS,
573
771
  buildPrompt,
@@ -576,8 +774,13 @@ const __test = {
576
774
  knownNamesOf,
577
775
  handleSuggest,
578
776
  handleSessionInfo,
777
+ handleToolsCatalog,
778
+ collectAllToolSchemas,
779
+ installToolsPolicy,
780
+ readToolsConfig,
579
781
  registerSuggestRoute,
580
782
  registerSessionInfoRoute,
783
+ registerToolsRoute,
581
784
  routeOf,
582
785
  quietEffortFor,
583
786
  listModels,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-desktop-enhance",
3
- "version": "0.2.4",
4
- "description": "DSH Desktop 界面增强:页内查找(Cmd+F)、轮次编号、上下文压缩行常显、建议、每步耗时、会话信息。",
3
+ "version": "0.2.6",
4
+ "description": "DSH Desktop 界面增强:页内查找(Cmd+F)、轮次编号、上下文压缩行常显、建议、每步耗时、会话信息、工具管理。",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./lib/index.js",