dsh-desktop-enhance 0.1.0 → 0.2.1

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
@@ -36,7 +36,7 @@ Web 版(浏览器里打开的那种)键位是 **`Cmd+Shift+.`** —— `Cmd+
36
36
  - 展开时自动退回通栏样式(宿主展开时把它变成遮盖头,缩成小胶囊会让正文从两侧透出)
37
37
  - 手工 `/compact` 触发的压缩同样适用
38
38
 
39
- ### 下一轮建议
39
+ ### 建议
40
40
 
41
41
  一轮结束后,输入框上方出现最多 3 条「下一步可能想问的」提示词(**一行**,窄屏才折行)。它与输入框**左对齐、同宽**:
42
42
 
@@ -105,7 +105,7 @@ dsh plugin --profile desktop update dsh-desktop-enhance
105
105
 
106
106
  只改插件内容的话,**刷新页面即可**,不必重启。
107
107
 
108
- > ⚠️ **例外:如果插件更新里改了「宿主半侧」(`src/index.ts` / `src/next/**`),必须重启 App** —— 刷新页面不够。此时设置卡片会直接把不可用的项**置灰**并提示你重启,而不是等你点了几次之后才报「设置没写进去」。**看到那句提示 = 重启即可解决。**
108
+ > ⚠️ **例外:如果插件更新里改了「宿主半侧」(`src/index.ts` / `src/suggest/**`),必须重启 App** —— 刷新页面不够。此时设置卡片会直接把不可用的项**置灰**并提示你重启,而不是等你点了几次之后才报「设置没写进去」。**看到那句提示 = 重启即可解决。**
109
109
 
110
110
  ## 开关
111
111
 
@@ -113,9 +113,9 @@ dsh plugin --profile desktop update dsh-desktop-enhance
113
113
 
114
114
  > 侧栏 **插件** → 插件列表里选中 **`dsh-desktop-enhance`** → 页面里的 **「UI 增强」** 一栏
115
115
 
116
- 四个开关:**页内查找** / **轮次编号** / **压缩行高亮** / **下一轮建议**。默认全开,装完与之前行为一致,除非你自己去关。
116
+ 四个开关:**页内查找** / **轮次编号** / **压缩行高亮** / **建议**。默认全开,装完与之前行为一致,除非你自己去关。
117
117
 
118
- 「下一轮建议」关掉时,它那三个参数(模型 / 推理强度 / 指令模板)也会一并收起 —— 功能关着时它们不起作用。
118
+ 「建议」关掉时,它那三个参数(模型 / 推理强度 / 指令模板)也会一并收起 —— 功能关着时它们不起作用。
119
119
 
120
120
  值写进 profile 的 cordis 配置,**重启后仍然生效**(不是浏览器本地存储)。关掉某一项会**完全回到 DSH 原样**。
121
121
 
@@ -124,7 +124,7 @@ dsh plugin --profile desktop update dsh-desktop-enhance
124
124
  - **只搜已经加载的内容。** 更早的轮次需要点「加载更早」才会进搜索范围,查找条不会替你加载。
125
125
  - **`Cmd+F` 只在桌面版注册。** Web 实例上是 `Cmd+Shift+.`。
126
126
  - 跳转临时展开的内容,**关掉查找条就复原** —— 这是有意设计,不会把整组工具调用永久摊开。
127
- - **下一步建议依赖宿主能打模型调用。** 会话还没发过第一次请求、或宿主里 `llm` 服务不在时,建议退化成空列表(页面不报错,也没有提示)—— 这是有意的降级:建议是加分项,不该因为它弹错误。
127
+ - **建议依赖宿主能打模型调用。** 会话还没发过第一次请求、或宿主里 `llm` 服务不在时,建议退化成空列表(页面不报错,也没有提示)—— 这是有意的降级:建议是加分项,不该因为它弹错误。
128
128
  - **建议的语言跟着你。** 指令里点名要求「用用户自己在用的语言」,所以中文对话给中文建议;模型不听话时可能混出英文。
129
129
 
130
130
  ## 卸载
package/lib/client.js CHANGED
@@ -15,10 +15,10 @@ window.__ModuleLoader__.load({
15
15
  "find.next": "下一个匹配",
16
16
  "find.close": "关闭查找",
17
17
  "shortcut.find": "在对话中查找",
18
- "next.title": "建议",
19
- "next.loading": "正在想下一轮…",
20
- "next.dismiss": "收起建议",
21
- "next.failed": "没写进输入框(宿主正忙或草稿有冲突),可以再点一次。",
18
+ "suggest.title": "建议",
19
+ "suggest.loading": "正在想下一轮…",
20
+ "suggest.dismiss": "收起建议",
21
+ "suggest.failed": "没写进输入框(宿主正忙或草稿有冲突),可以再点一次。",
22
22
  "settings.title": "UI 增强",
23
23
  "settings.description": "逐项开关本插件添加的界面增强。关闭后恢复 DSH 原生外观。",
24
24
  "settings.findBar": "页内查找",
@@ -27,27 +27,27 @@ window.__ModuleLoader__.load({
27
27
  "settings.turnPrefix.hint": "悬停右侧轨道刻度时,预览卡上显示 #轮次 前缀",
28
28
  "settings.compactionRow": "压缩行高亮",
29
29
  "settings.compactionRow.hint": "上下文压缩那一行不再折叠隐藏,并显示为醒目的居中胶囊",
30
- "settings.nextSteps": "下一轮建议",
31
- "settings.nextSteps.hint": "每轮结束后问一次模型,把 1 到 3 条可能的下一轮提示词摆在输入框上方,点一下就写进草稿",
32
- "settings.nextModel": "建议用哪个模型",
33
- "settings.nextModel.hint": "留空则跟随当前会话的模型;建议选一个非推理的轻量模型,否则每条建议都要等它想完",
34
- "settings.nextModel.follow": "跟随当前会话",
35
- "settings.nextModel.custom": "手动填写 provider/model",
36
- "settings.nextModel.loading": "正在读取可选模型…",
37
- "settings.nextModel.unavailable": "这台宿主没有提供模型目录,请手动填写 provider/model。",
38
- "settings.nextReasoning": "推理强度",
39
- "settings.nextReasoning.hint": "留空表示自动选择该模型最省事的一档(通常等于关闭思考)。推理越强,建议出得越慢。",
40
- "settings.nextReasoning.auto": "自动(最省事的一档)",
41
- "settings.nextReasoning.needModel": "先选一个模型,才能列出它支持的推理强度。",
42
- "settings.nextPrompt": "指令模板",
43
- "settings.nextPrompt.hint": "发给模型的那段指令。两个占位符必须保留,否则保存不了。",
44
- "settings.nextPrompt.reset": "恢复默认",
45
- "settings.nextPrompt.save": "保存",
46
- "settings.nextPrompt.saved": "已保存。",
47
- "settings.nextPrompt.empty": "模板不能为空。",
48
- "settings.nextPrompt.missingPlaceholder": "模板里必须保留 {{MAX_SUGGESTIONS}} 与 {{LABEL_MAX}} 两个占位符。",
49
- "settings.nextPrompt.missingFormat": "模板里必须保留输出格式那段(一个 JSON 数组,每条带 \"label\" 与 \"prompt\")。",
50
- "settings.nextPrompt.placeholders": "可用占位符:",
30
+ "settings.suggest": "建议",
31
+ "settings.suggest.hint": "每轮结束后问一次模型,把 1 到 3 条可能的下一轮提示词摆在输入框上方,点一下就写进草稿",
32
+ "settings.suggestModel": "建议用哪个模型",
33
+ "settings.suggestModel.hint": "留空则跟随当前会话的模型;建议选一个非推理的轻量模型,否则每条建议都要等它想完",
34
+ "settings.suggestModel.follow": "跟随当前会话",
35
+ "settings.suggestModel.custom": "手动填写 provider/model",
36
+ "settings.suggestModel.loading": "正在读取可选模型…",
37
+ "settings.suggestModel.unavailable": "这台宿主没有提供模型目录,请手动填写 provider/model。",
38
+ "settings.suggestReasoning": "推理强度",
39
+ "settings.suggestReasoning.hint": "留空表示自动选择该模型最省事的一档(通常等于关闭思考)。推理越强,建议出得越慢。",
40
+ "settings.suggestReasoning.auto": "自动(最省事的一档)",
41
+ "settings.suggestReasoning.needModel": "先选一个模型,才能列出它支持的推理强度。",
42
+ "settings.suggestPrompt": "指令模板",
43
+ "settings.suggestPrompt.hint": "发给模型的那段指令。两个占位符必须保留,否则保存不了。",
44
+ "settings.suggestPrompt.reset": "恢复默认",
45
+ "settings.suggestPrompt.save": "保存",
46
+ "settings.suggestPrompt.saved": "已保存。",
47
+ "settings.suggestPrompt.empty": "模板不能为空。",
48
+ "settings.suggestPrompt.missingPlaceholder": "模板里必须保留 {{MAX_SUGGESTIONS}} 与 {{LABEL_MAX}} 两个占位符。",
49
+ "settings.suggestPrompt.missingFormat": "模板里必须保留输出格式那段(一个 JSON 数组,每条带 \"label\" 与 \"prompt\")。",
50
+ "settings.suggestPrompt.placeholders": "可用占位符:",
51
51
  "settings.readOnly": "当前宿主没有可写的设置服务,开关暂不可用;插件按默认(全部开启)运行。",
52
52
  "settings.staleHost": "插件已更新,但应用还跑着旧的宿主半侧,这些设置改不动:请重启应用(刷新页面不够)。",
53
53
  "settings.writeFailed": "设置没写进去,开关已退回原状态;请重试。"
@@ -59,10 +59,10 @@ window.__ModuleLoader__.load({
59
59
  "find.next": "Next match",
60
60
  "find.close": "Close find",
61
61
  "shortcut.find": "Find in conversation",
62
- "next.title": "Suggestions",
63
- "next.loading": "Thinking of the next round…",
64
- "next.dismiss": "Dismiss suggestions",
65
- "next.failed": "Nothing was written into the composer (the host is busy, or the draft conflicted). Try again.",
62
+ "suggest.title": "Suggestions",
63
+ "suggest.loading": "Thinking of the next round…",
64
+ "suggest.dismiss": "Dismiss suggestions",
65
+ "suggest.failed": "Nothing was written into the composer (the host is busy, or the draft conflicted). Try again.",
66
66
  "settings.title": "UI enhancements",
67
67
  "settings.description": "Switch each interface enhancement this plugin adds. Turning one off restores the native DSH look.",
68
68
  "settings.findBar": "Find in conversation",
@@ -71,27 +71,27 @@ window.__ModuleLoader__.load({
71
71
  "settings.turnPrefix.hint": "Shows a #N prefix on the preview card when hovering the rail marks",
72
72
  "settings.compactionRow": "Compaction row highlight",
73
73
  "settings.compactionRow.hint": "Keeps the context-compaction row unfolded and shows it as a prominent centred capsule",
74
- "settings.nextSteps": "Next-round suggestions",
75
- "settings.nextSteps.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",
76
- "settings.nextModel": "Model for suggestions",
77
- "settings.nextModel.hint": "Empty follows the current session model. Prefer a lightweight non-reasoning model, or every suggestion waits for it to finish thinking",
78
- "settings.nextModel.follow": "Follow the current session",
79
- "settings.nextModel.custom": "Type a provider/model by hand",
80
- "settings.nextModel.loading": "Reading the available models…",
81
- "settings.nextModel.unavailable": "This host serves no model catalog; type a provider/model by hand.",
82
- "settings.nextReasoning": "Reasoning effort",
83
- "settings.nextReasoning.hint": "Empty picks the cheapest effort this model accepts (usually no thinking). More reasoning means slower suggestions.",
84
- "settings.nextReasoning.auto": "Automatic (cheapest effort)",
85
- "settings.nextReasoning.needModel": "Pick a model first to list the efforts it accepts.",
86
- "settings.nextPrompt": "Instruction template",
87
- "settings.nextPrompt.hint": "The instruction sent to the model. Both placeholders must stay, or it will not save.",
88
- "settings.nextPrompt.reset": "Restore default",
89
- "settings.nextPrompt.save": "Save",
90
- "settings.nextPrompt.saved": "Saved.",
91
- "settings.nextPrompt.empty": "The template must not be empty.",
92
- "settings.nextPrompt.missingPlaceholder": "The template must keep both the {{MAX_SUGGESTIONS}} and {{LABEL_MAX}} placeholders.",
93
- "settings.nextPrompt.missingFormat": "The template must keep the output-format clause (a JSON array whose entries carry \"label\" and \"prompt\").",
94
- "settings.nextPrompt.placeholders": "Available placeholders: ",
74
+ "settings.suggest": "Suggestions",
75
+ "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",
76
+ "settings.suggestModel": "Model for suggestions",
77
+ "settings.suggestModel.hint": "Empty follows the current session model. Prefer a lightweight non-reasoning model, or every suggestion waits for it to finish thinking",
78
+ "settings.suggestModel.follow": "Follow the current session",
79
+ "settings.suggestModel.custom": "Type a provider/model by hand",
80
+ "settings.suggestModel.loading": "Reading the available models…",
81
+ "settings.suggestModel.unavailable": "This host serves no model catalog; type a provider/model by hand.",
82
+ "settings.suggestReasoning": "Reasoning effort",
83
+ "settings.suggestReasoning.hint": "Empty picks the cheapest effort this model accepts (usually no thinking). More reasoning means slower suggestions.",
84
+ "settings.suggestReasoning.auto": "Automatic (cheapest effort)",
85
+ "settings.suggestReasoning.needModel": "Pick a model first to list the efforts it accepts.",
86
+ "settings.suggestPrompt": "Instruction template",
87
+ "settings.suggestPrompt.hint": "The instruction sent to the model. Both placeholders must stay, or it will not save.",
88
+ "settings.suggestPrompt.reset": "Restore default",
89
+ "settings.suggestPrompt.save": "Save",
90
+ "settings.suggestPrompt.saved": "Saved.",
91
+ "settings.suggestPrompt.empty": "The template must not be empty.",
92
+ "settings.suggestPrompt.missingPlaceholder": "The template must keep both the {{MAX_SUGGESTIONS}} and {{LABEL_MAX}} placeholders.",
93
+ "settings.suggestPrompt.missingFormat": "The template must keep the output-format clause (a JSON array whose entries carry \"label\" and \"prompt\").",
94
+ "settings.suggestPrompt.placeholders": "Available placeholders: ",
95
95
  "settings.readOnly": "This host has no writable settings service, so the switches are unavailable; the plugin runs with its defaults (all on).",
96
96
  "settings.staleHost": "The plugin updated, but the app is still running the old host half, so these settings cannot change: restart the app (refreshing the page is not enough).",
97
97
  "settings.writeFailed": "The setting did not save; the switch went back. Try again."
@@ -104,15 +104,16 @@ window.__ModuleLoader__.load({
104
104
  const notify = () => {
105
105
  for (const listener of listeners) listener(open);
106
106
  };
107
+ const set = (next) => {
108
+ if (open === next) return;
109
+ open = next;
110
+ notify();
111
+ };
107
112
  return {
108
113
  isOpen: () => open,
109
- set(next) {
110
- if (open === next) return;
111
- open = next;
112
- notify();
113
- },
114
+ set,
114
115
  toggle() {
115
- this.set(!open);
116
+ set(!open);
116
117
  },
117
118
  subscribe(listener) {
118
119
  listeners.add(listener);
@@ -347,21 +348,21 @@ window.__ModuleLoader__.load({
347
348
  const SETTINGS_NS = "desktop-enhance";
348
349
  const PACKAGE_NAME = "dsh-desktop-enhance";
349
350
  const TEXT_FIELDS = [
350
- "nextModel",
351
- "nextReasoning",
352
- "nextPrompt"
351
+ "suggestModel",
352
+ "suggestReasoning",
353
+ "suggestPrompt"
353
354
  ];
354
355
  const GATES = {
355
356
  findBar: "data-desktop-enhance-off-find-bar",
356
357
  turnPrefix: "data-desktop-enhance-off-turn-prefix",
357
358
  compactionRow: "data-desktop-enhance-off-compaction-row",
358
- nextSteps: "data-desktop-enhance-off-next-steps"
359
+ suggest: "data-desktop-enhance-off-suggest"
359
360
  };
360
361
  const FIELDS = [
361
362
  "findBar",
362
363
  "turnPrefix",
363
364
  "compactionRow",
364
- "nextSteps"
365
+ "suggest"
365
366
  ];
366
367
  const ALL_FIELDS = [...FIELDS, ...TEXT_FIELDS];
367
368
  function flagOn(value, field) {
@@ -374,6 +375,25 @@ window.__ModuleLoader__.load({
374
375
  const raw = value[field];
375
376
  return typeof raw === "string" ? raw.trim() : "";
376
377
  }
378
+ const NO_PREFERENCE = {
379
+ model: "",
380
+ reasoning: "",
381
+ prompt: ""
382
+ };
383
+ const PREFERENCE_KEY = {
384
+ suggestModel: "model",
385
+ suggestReasoning: "reasoning",
386
+ suggestPrompt: "prompt"
387
+ };
388
+ function preferenceOf(value) {
389
+ const out = {
390
+ model: "",
391
+ reasoning: "",
392
+ prompt: ""
393
+ };
394
+ for (const field of TEXT_FIELDS) out[PREFERENCE_KEY[field]] = textOf(value, field);
395
+ return out;
396
+ }
377
397
  function applyGates(value, root) {
378
398
  for (const field of FIELDS) {
379
399
  const gate = GATES[field];
@@ -406,8 +426,8 @@ window.__ModuleLoader__.load({
406
426
  }
407
427
  //#endregion
408
428
  //#region src/shared/route.ts
409
- const NEXT_STEPS_ROUTE = "/api/desktop-enhance/next-steps";
410
- const NEXT_MODELS_ROUTE = "/api/desktop-enhance/next-models";
429
+ const SUGGEST_ROUTE = "/api/desktop-enhance/suggest";
430
+ const SUGGEST_MODELS_ROUTE = "/api/desktop-enhance/suggest-models";
411
431
  //#endregion
412
432
  //#region src/client/settings/catalog.ts
413
433
  function parseModelCatalog(body) {
@@ -463,7 +483,7 @@ window.__ModuleLoader__.load({
463
483
  }
464
484
  const catalogCache = createCatalogCache();
465
485
  const CACHE_MS = 3e4;
466
- const NEXT_MODELS_URL = NEXT_MODELS_ROUTE;
486
+ const SUGGEST_MODELS_URL = SUGGEST_MODELS_ROUTE;
467
487
  function createCatalogCache(now = () => Date.now()) {
468
488
  const hits = new Map();
469
489
  const pending = new Map();
@@ -723,9 +743,9 @@ window.__ModuleLoader__.load({
723
743
  className: "de-note",
724
744
  role: "alert"
725
745
  }, t("settings.staleHost") + "(" + drifted.map((field) => labelOf(t, field)).join("、") + ")") : null;
726
- const showNext = flagOn(value, "nextSteps") || readOnly;
727
- 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, showNext ? h(NextSettings, {
728
- key: "next",
746
+ const showSuggest = flagOn(value, "suggest") || readOnly;
747
+ 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, {
748
+ key: "suggest",
729
749
  t,
730
750
  value,
731
751
  busy,
@@ -734,41 +754,41 @@ window.__ModuleLoader__.load({
734
754
  drifted
735
755
  }) : null, note);
736
756
  }
737
- function NextSettings({ t, value, busy, readOnly, write, drifted }) {
738
- const model = textOf(value, "nextModel");
739
- const reasoning = textOf(value, "nextReasoning");
740
- const prompt = textOf(value, "nextPrompt");
741
- const modelDisabled = readOnly || busy.includes("nextModel") || drifted.includes("nextModel");
742
- const effortDisabled = readOnly || busy.includes("nextReasoning") || drifted.includes("nextReasoning");
743
- const promptDisabled = readOnly || busy.includes("nextPrompt") || drifted.includes("nextPrompt");
744
- return h("div", { className: "de-next-settings" }, [
757
+ function SuggestSettings({ t, value, busy, readOnly, write, drifted }) {
758
+ const model = textOf(value, "suggestModel");
759
+ const reasoning = textOf(value, "suggestReasoning");
760
+ const prompt = textOf(value, "suggestPrompt");
761
+ const modelDisabled = readOnly || busy.includes("suggestModel") || drifted.includes("suggestModel");
762
+ const effortDisabled = readOnly || busy.includes("suggestReasoning") || drifted.includes("suggestReasoning");
763
+ const promptDisabled = readOnly || busy.includes("suggestPrompt") || drifted.includes("suggestPrompt");
764
+ return h("div", { className: "de-suggest-settings" }, [
745
765
  h("div", {
746
766
  className: "de-item de-item-block",
747
767
  key: "model"
748
- }, h("div", { className: "de-text" }, h("div", { className: "de-title" }, t("settings.nextModel")), h("div", { className: "de-desc" }, t("settings.nextModel.hint"))), h(ModelPicker, {
768
+ }, h("div", { className: "de-text" }, h("div", { className: "de-title" }, t("settings.suggestModel")), h("div", { className: "de-desc" }, t("settings.suggestModel.hint"))), h(ModelPicker, {
749
769
  model,
750
770
  t,
751
771
  disabled: modelDisabled,
752
- onPick: (id) => write("nextModel", id)
772
+ onPick: (id) => write("suggestModel", id)
753
773
  })),
754
774
  h("div", {
755
775
  className: "de-item de-item-block",
756
776
  key: "reasoning"
757
- }, h("div", { className: "de-text" }, h("div", { className: "de-title" }, t("settings.nextReasoning")), h("div", { className: "de-desc" }, t("settings.nextReasoning.hint"))), h(EffortPicker, {
777
+ }, h("div", { className: "de-text" }, h("div", { className: "de-title" }, t("settings.suggestReasoning")), h("div", { className: "de-desc" }, t("settings.suggestReasoning.hint"))), h(EffortPicker, {
758
778
  model,
759
779
  reasoning,
760
780
  t,
761
781
  disabled: effortDisabled,
762
- onPick: (id) => write("nextReasoning", id)
782
+ onPick: (id) => write("suggestReasoning", id)
763
783
  })),
764
784
  h("div", {
765
785
  className: "de-item de-item-block",
766
786
  key: "prompt"
767
- }, h("div", { className: "de-text" }, h("div", { className: "de-title" }, t("settings.nextPrompt")), h("div", { className: "de-desc" }, t("settings.nextPrompt.hint"))), h(PromptEditor, {
787
+ }, h("div", { className: "de-text" }, h("div", { className: "de-title" }, t("settings.suggestPrompt")), h("div", { className: "de-desc" }, t("settings.suggestPrompt.hint"))), h(PromptEditor, {
768
788
  prompt,
769
789
  t,
770
790
  readOnly: promptDisabled,
771
- onSave: (text) => write("nextPrompt", text)
791
+ onSave: (text) => write("suggestPrompt", text)
772
792
  }))
773
793
  ]);
774
794
  }
@@ -776,7 +796,7 @@ window.__ModuleLoader__.load({
776
796
  const [catalog, setCatalog] = React.useState(null);
777
797
  React.useEffect(() => {
778
798
  let live = true;
779
- catalogCache.get(NEXT_MODELS_URL, globalThis.fetch).then((body) => {
799
+ catalogCache.get(SUGGEST_MODELS_URL, globalThis.fetch).then((body) => {
780
800
  if (live) setCatalog(parseModelCatalog(body));
781
801
  }).catch(() => {
782
802
  if (live) setCatalog({
@@ -791,8 +811,8 @@ window.__ModuleLoader__.load({
791
811
  if (catalog === null || !catalog.available) return h("input", {
792
812
  type: "text",
793
813
  className: "de-text-input",
794
- "data-desktop-enhance-field": "nextModel",
795
- placeholder: t("settings.nextModel.custom"),
814
+ "data-desktop-enhance-field": "suggestModel",
815
+ placeholder: t("settings.suggestModel.custom"),
796
816
  value: model,
797
817
  disabled,
798
818
  onChange: (event) => {
@@ -812,7 +832,7 @@ window.__ModuleLoader__.load({
812
832
  const seen = catalog.models.some((choice) => choice.id === model);
813
833
  return h("select", {
814
834
  className: "de-select",
815
- "data-desktop-enhance-field": "nextModel",
835
+ "data-desktop-enhance-field": "suggestModel",
816
836
  value: model,
817
837
  disabled,
818
838
  onChange: (event) => {
@@ -823,7 +843,7 @@ window.__ModuleLoader__.load({
823
843
  h("option", {
824
844
  key: "__follow",
825
845
  value: ""
826
- }, t("settings.nextModel.follow")),
846
+ }, t("settings.suggestModel.follow")),
827
847
  !seen && model !== "" ? h("option", {
828
848
  key: "__kept",
829
849
  value: model
@@ -842,7 +862,7 @@ window.__ModuleLoader__.load({
842
862
  React.useEffect(() => {
843
863
  if (model === "") return void 0;
844
864
  let live = true;
845
- catalogCache.get("/api/desktop-enhance/next-efforts?model=" + globalThis.encodeURIComponent(model), globalThis.fetch).then((body) => {
865
+ catalogCache.get("/api/desktop-enhance/suggest-efforts?model=" + globalThis.encodeURIComponent(model), globalThis.fetch).then((body) => {
846
866
  if (!live) return;
847
867
  setState({
848
868
  model,
@@ -862,8 +882,8 @@ window.__ModuleLoader__.load({
862
882
  if (model === "" || efforts.length === 0) return h("input", {
863
883
  type: "text",
864
884
  className: "de-text-input",
865
- "data-desktop-enhance-field": "nextReasoning",
866
- placeholder: model === "" ? t("settings.nextReasoning.needModel") : t("settings.nextReasoning.auto"),
885
+ "data-desktop-enhance-field": "suggestReasoning",
886
+ placeholder: model === "" ? t("settings.suggestReasoning.needModel") : t("settings.suggestReasoning.auto"),
867
887
  value: reasoning,
868
888
  disabled,
869
889
  onChange: (event) => {
@@ -874,7 +894,7 @@ window.__ModuleLoader__.load({
874
894
  const seen = efforts.some((effort) => effort.id === reasoning);
875
895
  return h("select", {
876
896
  className: "de-select",
877
- "data-desktop-enhance-field": "nextReasoning",
897
+ "data-desktop-enhance-field": "suggestReasoning",
878
898
  value: reasoning,
879
899
  disabled,
880
900
  onChange: (event) => {
@@ -885,7 +905,7 @@ window.__ModuleLoader__.load({
885
905
  h("option", {
886
906
  key: "__auto",
887
907
  value: ""
888
- }, t("settings.nextReasoning.auto")),
908
+ }, t("settings.suggestReasoning.auto")),
889
909
  !seen && reasoning !== "" ? h("option", {
890
910
  key: "__kept",
891
911
  value: reasoning
@@ -914,7 +934,7 @@ window.__ModuleLoader__.load({
914
934
  h("textarea", {
915
935
  key: "box",
916
936
  className: "de-textarea",
917
- "data-desktop-enhance-field": "nextPrompt",
937
+ "data-desktop-enhance-field": "suggestPrompt",
918
938
  rows: 8,
919
939
  value: draft,
920
940
  disabled: readOnly,
@@ -930,12 +950,12 @@ window.__ModuleLoader__.load({
930
950
  h("div", {
931
951
  key: "meta",
932
952
  className: "de-prompt-meta"
933
- }, t("settings.nextPrompt.placeholders") + PROMPT_PLACEHOLDERS.map((name) => "{{" + name + "}}").join("、")),
953
+ }, t("settings.suggestPrompt.placeholders") + PROMPT_PLACEHOLDERS.map((name) => "{{" + name + "}}").join("、")),
934
954
  problem !== null ? h("div", {
935
955
  key: "problem",
936
956
  className: "de-error",
937
957
  role: "alert"
938
- }, t("settings.nextPrompt." + problem)) : null,
958
+ }, t("settings.suggestPrompt." + problem)) : null,
939
959
  h("div", {
940
960
  key: "actions",
941
961
  className: "de-prompt-actions"
@@ -946,7 +966,7 @@ window.__ModuleLoader__.load({
946
966
  "data-desktop-enhance-action": "prompt-save",
947
967
  disabled: readOnly || !dirty,
948
968
  onClick: save
949
- }, t("settings.nextPrompt.save")), h("button", {
969
+ }, t("settings.suggestPrompt.save")), h("button", {
950
970
  key: "reset",
951
971
  type: "button",
952
972
  className: "de-button",
@@ -956,7 +976,7 @@ window.__ModuleLoader__.load({
956
976
  setDraft(DEFAULT_PROMPT);
957
977
  setProblem(null);
958
978
  }
959
- }, t("settings.nextPrompt.reset"))])
979
+ }, t("settings.suggestPrompt.reset"))])
960
980
  ]);
961
981
  }
962
982
  //#endregion
@@ -1041,9 +1061,9 @@ window.__ModuleLoader__.load({
1041
1061
  });
1042
1062
  }
1043
1063
  //#endregion
1044
- //#region src/client/next/trigger.ts
1064
+ //#region src/client/suggest/trigger.ts
1045
1065
  const HIDDEN = { kind: "hidden" };
1046
- function nextView(view, input) {
1066
+ function suggestView(view, input) {
1047
1067
  switch (input.kind) {
1048
1068
  case "turnend": return {
1049
1069
  kind: "loading",
@@ -1074,9 +1094,9 @@ window.__ModuleLoader__.load({
1074
1094
  }
1075
1095
  }
1076
1096
  function assertNever(input) {
1077
- throw new Error(`unhandled next-steps input: ${JSON.stringify(input)}`);
1097
+ throw new Error(`unhandled suggest input: ${JSON.stringify(input)}`);
1078
1098
  }
1079
- function createNextStore() {
1099
+ function createSuggestStore() {
1080
1100
  const views = new Map();
1081
1101
  let session;
1082
1102
  let published = HIDDEN;
@@ -1104,14 +1124,21 @@ window.__ModuleLoader__.load({
1104
1124
  published = now;
1105
1125
  for (const listener of listeners) listener();
1106
1126
  };
1127
+ const drop = (sessionId) => {
1128
+ if (!views.delete(sessionId)) return;
1129
+ if (sessionId !== session) return;
1130
+ if (published === HIDDEN) return;
1131
+ published = HIDDEN;
1132
+ for (const listener of listeners) listener();
1133
+ };
1107
1134
  return {
1108
1135
  get: () => viewOf(session),
1109
1136
  dispatch(sessionId, input) {
1110
- put(sessionId, nextView(viewOf(sessionId), input));
1137
+ put(sessionId, suggestView(viewOf(sessionId), input));
1111
1138
  },
1112
1139
  offer(sessionId, turn, raw) {
1113
1140
  const items = normalizeEntries(Array.isArray(raw) ? raw : [], null);
1114
- put(sessionId, nextView(viewOf(sessionId), {
1141
+ put(sessionId, suggestView(viewOf(sessionId), {
1115
1142
  kind: "reply",
1116
1143
  turn,
1117
1144
  items
@@ -1133,15 +1160,9 @@ window.__ModuleLoader__.load({
1133
1160
  for (const listener of listeners) listener();
1134
1161
  },
1135
1162
  currentSession: () => session,
1136
- forget(sessionId) {
1137
- if (!views.delete(sessionId)) return;
1138
- if (sessionId !== session) return;
1139
- if (published === HIDDEN) return;
1140
- published = HIDDEN;
1141
- for (const listener of listeners) listener();
1142
- },
1163
+ forget: drop,
1143
1164
  forgetCurrent() {
1144
- if (session !== void 0) this.forget(session);
1165
+ if (session !== void 0) drop(session);
1145
1166
  },
1146
1167
  subscribe(listener) {
1147
1168
  listeners.add(listener);
@@ -1150,8 +1171,50 @@ window.__ModuleLoader__.load({
1150
1171
  };
1151
1172
  }
1152
1173
  //#endregion
1153
- //#region src/client/next/bridge.ts
1154
- const NEXT_STEPS_URL = NEXT_STEPS_ROUTE;
1174
+ //#region src/client/suggest/watch.ts
1175
+ const WARM_MS = 12e4;
1176
+ function runningIn(snapshot) {
1177
+ const ids = snapshot?.ids;
1178
+ const byId = snapshot?.byId;
1179
+ if (!Array.isArray(ids) || byId === null || typeof byId !== "object") return [];
1180
+ return ids.filter((id) => byId[id]?.running === true);
1181
+ }
1182
+ function sessionsToWatch(snapshot, current, warm = []) {
1183
+ const out = [];
1184
+ const seen = new Set();
1185
+ const add = (id) => {
1186
+ if (typeof id !== "string" || id === "" || seen.has(id)) return;
1187
+ seen.add(id);
1188
+ out.push(id);
1189
+ };
1190
+ add(current);
1191
+ for (const id of runningIn(snapshot)) add(id);
1192
+ for (const id of warm) add(id);
1193
+ return out;
1194
+ }
1195
+ function createWarmth(windowMs = WARM_MS) {
1196
+ const until = new Map();
1197
+ return {
1198
+ renew(id, at) {
1199
+ until.set(id, at + windowMs);
1200
+ },
1201
+ live(at) {
1202
+ const out = [];
1203
+ for (const [id, deadline] of until) if (deadline > at) out.push(id);
1204
+ return out;
1205
+ },
1206
+ sweep(at) {
1207
+ for (const [id, deadline] of until) if (deadline <= at) until.delete(id);
1208
+ },
1209
+ held: () => until.size,
1210
+ clear() {
1211
+ until.clear();
1212
+ }
1213
+ };
1214
+ }
1215
+ //#endregion
1216
+ //#region src/client/suggest/bridge.ts
1217
+ const SUGGEST_URL = SUGGEST_ROUTE;
1155
1218
  function turnEnded(entries) {
1156
1219
  let turn = null;
1157
1220
  for (const entry of entries) {
@@ -1180,26 +1243,9 @@ window.__ModuleLoader__.load({
1180
1243
  }
1181
1244
  return turn;
1182
1245
  }
1183
- const NO_PREFERENCE = {
1184
- model: "",
1185
- reasoning: "",
1186
- prompt: ""
1187
- };
1188
- function preferenceOf(value) {
1189
- const source = value !== null && typeof value === "object" ? value : {};
1190
- const text = (key) => {
1191
- const raw = source[key];
1192
- return typeof raw === "string" ? raw.trim() : "";
1193
- };
1194
- return {
1195
- model: text("nextModel"),
1196
- reasoning: text("nextReasoning"),
1197
- prompt: text("nextPrompt")
1198
- };
1199
- }
1200
1246
  async function requestSuggestions(sessionId, fetchImpl, signal, preference = NO_PREFERENCE) {
1201
1247
  try {
1202
- const response = await fetchImpl(NEXT_STEPS_URL, {
1248
+ const response = await fetchImpl(SUGGEST_URL, {
1203
1249
  method: "POST",
1204
1250
  headers: { "content-type": "application/json" },
1205
1251
  body: JSON.stringify({
@@ -1212,7 +1258,7 @@ window.__ModuleLoader__.load({
1212
1258
  });
1213
1259
  if (!response.ok) return [];
1214
1260
  const body = await response.json();
1215
- if (body.diag !== void 0 && body.diag !== null) globalThis.console?.info?.("[desktop-enhance] next-steps", body.diag);
1261
+ if (body.diag !== void 0 && body.diag !== null) globalThis.console?.info?.("[desktop-enhance] suggest", body.diag);
1216
1262
  return Array.isArray(body.items) ? body.items : [];
1217
1263
  } catch {
1218
1264
  return [];
@@ -1258,40 +1304,21 @@ window.__ModuleLoader__.load({
1258
1304
  reference.release();
1259
1305
  };
1260
1306
  }
1261
- const WARM_MS = 12e4;
1262
- function sessionsToWatch(snapshot, current, warm = new Map(), now = 0) {
1263
- const out = [];
1264
- const seen = new Set();
1265
- const add = (id) => {
1266
- if (typeof id !== "string" || id === "" || seen.has(id)) return;
1267
- seen.add(id);
1268
- out.push(id);
1269
- };
1270
- add(current);
1271
- const ids = snapshot?.ids;
1272
- const byId = snapshot?.byId;
1273
- if (Array.isArray(ids) && byId !== null && typeof byId === "object") {
1274
- for (const id of ids) if (byId[id]?.running === true) add(id);
1275
- }
1276
- for (const [id, until] of warm) if (until > now) add(id);
1277
- return out;
1278
- }
1279
- function runningIn(snapshot) {
1280
- const ids = snapshot?.ids;
1281
- const byId = snapshot?.byId;
1282
- if (!Array.isArray(ids) || byId === null || typeof byId !== "object") return [];
1283
- return ids.filter((id) => byId[id]?.running === true);
1307
+ function defaultSchedule(tick, everyMs) {
1308
+ const handle = globalThis.setInterval(tick, everyMs);
1309
+ handle?.unref?.();
1310
+ return () => globalThis.clearInterval(handle);
1284
1311
  }
1285
- function createNextWatcher(getSessions, store, fetchImpl = globalThis.fetch, getPreference = () => NO_PREFERENCE, isOn = () => true) {
1312
+ function createSuggestWatcher(getSessions, store, options = {}) {
1313
+ const { fetchImpl = globalThis.fetch, getPreference = () => NO_PREFERENCE, isOn = () => true, now = () => Date.now(), schedule = defaultSchedule } = options;
1286
1314
  const refs = new Map();
1287
1315
  let current;
1288
- const warm = new Map();
1316
+ const warmth = createWarmth();
1289
1317
  let stopped = false;
1290
1318
  let catalogStop;
1291
1319
  let syncing = false;
1292
1320
  let redoSync = false;
1293
1321
  let syncQueued = false;
1294
- const now = () => Date.now();
1295
1322
  const sync = () => {
1296
1323
  if (stopped) return;
1297
1324
  if (syncing) {
@@ -1313,7 +1340,8 @@ window.__ModuleLoader__.load({
1313
1340
  if (!isOn()) {
1314
1341
  for (const stop of refs.values()) stop();
1315
1342
  refs.clear();
1316
- warm.clear();
1343
+ warmth.clear();
1344
+ store.forgetCurrent();
1317
1345
  return;
1318
1346
  }
1319
1347
  const catalog = getSessions()?.list;
@@ -1322,9 +1350,9 @@ window.__ModuleLoader__.load({
1322
1350
  onCatalogChange();
1323
1351
  });
1324
1352
  const at = now();
1325
- for (const id of runningIn(snapshot)) warm.set(id, at + WARM_MS);
1326
- for (const [id, until] of warm) if (until <= at) warm.delete(id);
1327
- const want = snapshot === null ? current === void 0 ? [] : [current] : sessionsToWatch(snapshot, current, warm, at);
1353
+ for (const id of runningIn(snapshot)) warmth.renew(id, at);
1354
+ warmth.sweep(at);
1355
+ const want = snapshot === null ? current === void 0 ? [] : [current] : sessionsToWatch(snapshot, current, warmth.live(at));
1328
1356
  const wanted = new Set(want);
1329
1357
  for (const [id, stop] of refs) {
1330
1358
  if (wanted.has(id)) continue;
@@ -1334,7 +1362,7 @@ window.__ModuleLoader__.load({
1334
1362
  for (const id of want) {
1335
1363
  if (refs.has(id)) continue;
1336
1364
  const stop = watchSession(getSessions, id, store, fetchImpl, getPreference, () => {
1337
- warm.set(id, now() + WARM_MS);
1365
+ warmth.renew(id, now());
1338
1366
  });
1339
1367
  if (stop === null) continue;
1340
1368
  refs.set(id, stop);
@@ -1348,10 +1376,9 @@ window.__ModuleLoader__.load({
1348
1376
  sync();
1349
1377
  });
1350
1378
  }
1351
- const sweep = stopped ? void 0 : globalThis.setInterval(() => {
1379
+ const stopSweep = stopped ? void 0 : schedule(() => {
1352
1380
  sync();
1353
1381
  }, Math.max(1e3, Math.floor(WARM_MS / 4)));
1354
- sweep?.unref?.();
1355
1382
  return {
1356
1383
  setCurrent(sessionId) {
1357
1384
  if (sessionId === current) return;
@@ -1362,44 +1389,31 @@ window.__ModuleLoader__.load({
1362
1389
  sync();
1363
1390
  },
1364
1391
  watching: () => [...refs.keys()],
1392
+ held: () => warmth.held(),
1365
1393
  stop() {
1366
1394
  stopped = true;
1367
1395
  for (const stop of refs.values()) stop();
1368
1396
  refs.clear();
1369
- warm.clear();
1370
- if (sweep !== void 0) globalThis.clearInterval(sweep);
1397
+ warmth.clear();
1398
+ stopSweep?.();
1371
1399
  catalogStop?.();
1372
1400
  catalogStop = void 0;
1373
1401
  }
1374
1402
  };
1375
1403
  }
1376
1404
  //#endregion
1377
- //#region src/client/next/dock.ts
1405
+ //#region src/client/suggest/dock.ts
1378
1406
  function useView(store) {
1379
1407
  return React.useSyncExternalStore(store.subscribe, store.get, store.get);
1380
1408
  }
1381
- function shouldWatch(subscribed, hasSubscription, read) {
1382
- return hasSubscription ? subscribed : read();
1383
- }
1384
- function NextDock({ store, t, sessionId, inputActions, setCurrent, isOn, subscribeSettings }) {
1409
+ function SuggestDock({ store, t, sessionId, inputActions, setCurrent }) {
1385
1410
  const view = useView(store);
1386
- const [subscribed, setSubscribed] = React.useState(() => isOn?.() ?? true);
1387
- const enabled = shouldWatch(subscribed, subscribeSettings !== void 0, () => isOn?.() ?? true);
1388
- React.useEffect(() => {
1389
- if (subscribeSettings === void 0) return void 0;
1390
- const read = () => setSubscribed(isOn?.() ?? true);
1391
- read();
1392
- return subscribeSettings(read);
1393
- }, [isOn, subscribeSettings]);
1394
1411
  React.useEffect(() => {
1395
1412
  store.setSession(sessionId);
1396
1413
  }, [sessionId, store]);
1397
1414
  React.useEffect(() => {
1398
1415
  setCurrent?.(sessionId);
1399
1416
  }, [sessionId, setCurrent]);
1400
- React.useEffect(() => {
1401
- if (!enabled) store.forgetCurrent();
1402
- }, [enabled, store]);
1403
1417
  const take = React.useCallback((index, prompt) => {
1404
1418
  let written = inputActions === void 0;
1405
1419
  if (inputActions !== void 0) try {
@@ -1419,50 +1433,50 @@ window.__ModuleLoader__.load({
1419
1433
  ]);
1420
1434
  if (view.kind === "hidden") return null;
1421
1435
  const head = h("div", {
1422
- className: "de-next-head",
1436
+ className: "de-suggest-head",
1423
1437
  key: "head"
1424
1438
  }, [h("span", {
1425
- className: "de-next-title",
1439
+ className: "de-suggest-title",
1426
1440
  key: "title"
1427
- }, t("next.title")), h("button", {
1441
+ }, t("suggest.title")), h("button", {
1428
1442
  type: "button",
1429
1443
  key: "close",
1430
- className: "de-next-close",
1431
- "aria-label": t("next.dismiss"),
1432
- title: t("next.dismiss"),
1444
+ className: "de-suggest-close",
1445
+ "aria-label": t("suggest.dismiss"),
1446
+ title: t("suggest.dismiss"),
1433
1447
  onClick: () => {
1434
1448
  if (sessionId !== void 0) store.dispatch(sessionId, { kind: "dismiss" });
1435
1449
  }
1436
1450
  }, "✕")]);
1437
1451
  if (view.kind === "loading") return h("div", {
1438
- className: "de-next",
1439
- "data-desktop-enhance": "next"
1452
+ className: "de-suggest",
1453
+ "data-desktop-enhance": "suggest"
1440
1454
  }, [head, h("span", {
1441
- className: "de-next-row de-next-loading",
1455
+ className: "de-suggest-row de-suggest-loading",
1442
1456
  key: "row"
1443
- }, t("next.loading"))]);
1457
+ }, t("suggest.loading"))]);
1444
1458
  const rows = view.items.map((item, index) => h("button", {
1445
1459
  type: "button",
1446
1460
  key: `${String(index)}:${item.prompt}`,
1447
- className: "de-next-row",
1461
+ className: "de-suggest-row",
1448
1462
  "data-used": index === view.used ? "true" : void 0,
1449
1463
  title: item.prompt,
1450
1464
  onClick: () => take(index, item.prompt)
1451
1465
  }, [h("span", {
1452
- className: "de-next-num",
1466
+ className: "de-suggest-num",
1453
1467
  key: "n"
1454
1468
  }, String(index + 1)), h("span", {
1455
- className: "de-next-text",
1469
+ className: "de-suggest-text",
1456
1470
  key: "t"
1457
1471
  }, item.label)]));
1458
1472
  const notice = view.failed === true ? h("div", {
1459
- className: "de-next-error",
1473
+ className: "de-suggest-error",
1460
1474
  key: "failed",
1461
1475
  role: "alert"
1462
- }, t("next.failed")) : null;
1476
+ }, t("suggest.failed")) : null;
1463
1477
  return h("div", {
1464
- className: "de-next",
1465
- "data-desktop-enhance": "next"
1478
+ className: "de-suggest",
1479
+ "data-desktop-enhance": "suggest"
1466
1480
  }, [
1467
1481
  head,
1468
1482
  ...rows,
@@ -1471,7 +1485,7 @@ window.__ModuleLoader__.load({
1471
1485
  }
1472
1486
  //#endregion
1473
1487
  //#region \0dsh-desktop-enhance-css:/Users/lijiechengbj/projects/AI-agents/dsh-desktop-enhance/src/client/styles/enhance.css.mjs
1474
- 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 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 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\"]) {\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 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-next-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-next-steps] [data-desktop-enhance='next'] {\n display: none !important;\n}\n:root:not([data-desktop-enhance-off-next-steps]) .de-next {\n align-items: center;\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-left: 14px;\n width: calc(100% - var(--dsh-composer-side-clearance, 0px) - var(--dsh-composer-side-clearance, 0px));\n}\n:root:not([data-desktop-enhance-off-next-steps]) .de-next-head {\n align-items: center;\n display: flex;\n flex: none;\n gap: 2px;\n}\n\n:root:not([data-desktop-enhance-off-next-steps]) .de-next-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-next-steps]) .de-next-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-next-steps]) .de-next-close:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n:root:not([data-desktop-enhance-off-next-steps]) .de-next-row {\n appearance: none;\n align-items: center;\n background: none;\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 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-next-steps]) .de-next-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-next-steps]) .de-next-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-next-steps]) .de-next-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-next-steps]) .de-next-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-next-steps]) .de-next-loading {\n border-style: dashed;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n\n:root:not([data-desktop-enhance-off-next-steps]) .de-next-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";
1488
+ 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 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 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\"]) {\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 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";
1475
1489
  //#endregion
1476
1490
  //#region src/client/index.tsx
1477
1491
  const inject = [
@@ -1544,31 +1558,33 @@ window.__ModuleLoader__.load({
1544
1558
  }, SettingsRow))), "desktop-enhance: plugin page card");
1545
1559
  });
1546
1560
  installTurnPrefix(ctx);
1547
- const nextStore = createNextStore();
1548
- const nextWatcher = createNextWatcher(() => ctx.get("sessions"), nextStore, globalThis.fetch, () => preferenceOf(settings.get().value), () => flagOn(settings.get().value, "nextSteps"));
1561
+ const suggestStore = createSuggestStore();
1562
+ const suggestWatcher = createSuggestWatcher(() => ctx.get("sessions"), suggestStore, {
1563
+ fetchImpl: globalThis.fetch,
1564
+ getPreference: () => preferenceOf(settings.get().value),
1565
+ isOn: () => flagOn(settings.get().value, "suggest")
1566
+ });
1549
1567
  ctx.effect(() => {
1550
1568
  const off = settings.subscribe(() => {
1551
- nextWatcher.refresh();
1569
+ suggestWatcher.refresh();
1552
1570
  });
1553
1571
  return () => {
1554
1572
  off();
1555
- nextWatcher.stop();
1573
+ suggestWatcher.stop();
1556
1574
  };
1557
- }, "desktop-enhance: next-steps watcher");
1575
+ }, "desktop-enhance: suggest watcher");
1558
1576
  ctx.effect(() => ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
1559
1577
  name: "conversation.input.dock",
1560
- id: "desktop-enhance-next",
1578
+ id: "desktop-enhance-suggest",
1561
1579
  order: 30,
1562
1580
  locale: NS,
1563
1581
  inject: (sessionId) => ({
1564
- store: nextStore,
1565
- setCurrent: (id) => nextWatcher.setCurrent(id),
1582
+ store: suggestStore,
1583
+ setCurrent: (id) => suggestWatcher.setCurrent(id),
1566
1584
  sessionId,
1567
- isOn: () => flagOn(settings.get().value, "nextSteps"),
1568
- subscribeSettings: (listener) => settings.subscribe(listener),
1569
1585
  getPreference: () => preferenceOf(settings.get().value)
1570
1586
  })
1571
- }, NextDock)), "desktop-enhance: next-steps dock");
1587
+ }, SuggestDock)), "desktop-enhance: suggest dock");
1572
1588
  ctx.effect(() => ctx.slots.inject("shell.overlay", () => ctx.slots.register({
1573
1589
  name: "shell.overlay",
1574
1590
  id: "desktop-enhance-find",
@@ -1638,21 +1654,22 @@ window.__ModuleLoader__.load({
1638
1654
  flagOn,
1639
1655
  applyGates,
1640
1656
  createSettingsStore,
1641
- nextView,
1657
+ suggestView,
1642
1658
  HIDDEN,
1643
- createNextStore,
1644
- NextDock,
1659
+ createSuggestStore,
1660
+ SuggestDock,
1645
1661
  watchSession,
1646
1662
  sessionsToWatch,
1647
1663
  runningIn,
1648
1664
  WARM_MS,
1649
- createNextWatcher,
1665
+ createWarmth,
1666
+ createSuggestWatcher,
1650
1667
  MAX_BUCKETS: 8,
1668
+ createFindStore,
1651
1669
  turnEnded,
1652
1670
  turnStarted,
1653
1671
  requestSuggestions,
1654
- NEXT_STEPS_URL,
1655
- shouldWatch,
1672
+ SUGGEST_URL,
1656
1673
  clean,
1657
1674
  parseSuggestions,
1658
1675
  namesKnownCommand,
@@ -1676,7 +1693,7 @@ window.__ModuleLoader__.load({
1676
1693
  parseEfforts,
1677
1694
  catalogCache,
1678
1695
  CACHE_MS,
1679
- NextSettings,
1696
+ SuggestSettings,
1680
1697
  ModelPicker,
1681
1698
  zh,
1682
1699
  en,
package/lib/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import Schema from "@deepseek-ai/schemastery";
2
2
  //#region src/shared/suggest.d.ts
3
3
  declare function buildPrompt(template?: string): string;
4
4
  //#endregion
5
- //#region src/next/host.d.ts
5
+ //#region src/suggest/host.d.ts
6
6
  interface HostContext {
7
7
  get(name: string): unknown;
8
8
  inject(services: readonly string[], callback: (ctx: HostContext) => void): unknown;
@@ -66,7 +66,7 @@ declare function routeOf(session: HostSession): {
66
66
  } | null;
67
67
  declare function buildMessages(history: readonly HostMessage[], instruction: string): HostMessage[];
68
68
  declare function knownNamesOf(messages: readonly HostMessage[]): ReadonlySet<string> | null;
69
- declare function handleNextSteps(ctx: HostContext, request: Request, timeoutMs?: number): Promise<Response>;
69
+ declare function handleSuggest(ctx: HostContext, request: Request, timeoutMs?: number): Promise<Response>;
70
70
  declare function quietEffortFor(llm: HostLlm, route: {
71
71
  readonly provider: string;
72
72
  readonly model: string;
@@ -88,39 +88,39 @@ declare function listEfforts(ctx: HostContext, provider: string, model: string):
88
88
  }[];
89
89
  defaultEffort: string | null;
90
90
  }>;
91
- declare function registerNextStepsRoute(ctx: HostContext): () => void;
91
+ declare function registerSuggestRoute(ctx: HostContext): () => void;
92
92
  //#endregion
93
93
  //#region src/index.d.ts
94
94
  export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
95
95
  findBar: Schema<boolean, boolean, "volatile-defined">;
96
96
  turnPrefix: Schema<boolean, boolean, "volatile-defined">;
97
97
  compactionRow: Schema<boolean, boolean, "volatile-defined">;
98
- nextSteps: Schema<boolean, boolean, "volatile-defined">;
99
- nextModel: Schema<string, string, "volatile-defined">;
100
- nextReasoning: Schema<string, string, "volatile-defined">;
101
- nextPrompt: Schema<string, string, "volatile-defined">;
98
+ suggest: Schema<boolean, boolean, "volatile-defined">;
99
+ suggestModel: Schema<string, string, "volatile-defined">;
100
+ suggestReasoning: Schema<string, string, "volatile-defined">;
101
+ suggestPrompt: Schema<string, string, "volatile-defined">;
102
102
  }>>, Schemastery.ObjectT<NoInfer<{
103
103
  findBar: Schema<boolean, boolean, "volatile-defined">;
104
104
  turnPrefix: Schema<boolean, boolean, "volatile-defined">;
105
105
  compactionRow: Schema<boolean, boolean, "volatile-defined">;
106
- nextSteps: Schema<boolean, boolean, "volatile-defined">;
107
- nextModel: Schema<string, string, "volatile-defined">;
108
- nextReasoning: Schema<string, string, "volatile-defined">;
109
- nextPrompt: Schema<string, string, "volatile-defined">;
106
+ suggest: Schema<boolean, boolean, "volatile-defined">;
107
+ suggestModel: Schema<string, string, "volatile-defined">;
108
+ suggestReasoning: Schema<string, string, "volatile-defined">;
109
+ suggestPrompt: Schema<string, string, "volatile-defined">;
110
110
  }>>, "plain">;
111
111
  export declare function apply(ctx: HostContext): void;
112
112
  export declare const __test: {
113
- NEXT_STEPS_PATH: string;
114
- NEXT_MODELS_PATH: string;
115
- NEXT_EFFORTS_PATH: string;
113
+ SUGGEST_PATH: string;
114
+ SUGGEST_MODELS_PATH: string;
115
+ SUGGEST_EFFORTS_PATH: string;
116
116
  MODEL_TIMEOUT_MS: number;
117
117
  MAX_OUTPUT_TOKENS: number;
118
118
  buildPrompt: typeof buildPrompt;
119
119
  buildMessages: typeof buildMessages;
120
120
  collectText: typeof collectText;
121
121
  knownNamesOf: typeof knownNamesOf;
122
- handleNextSteps: typeof handleNextSteps;
123
- registerNextStepsRoute: typeof registerNextStepsRoute;
122
+ handleSuggest: typeof handleSuggest;
123
+ registerSuggestRoute: typeof registerSuggestRoute;
124
124
  routeOf: typeof routeOf;
125
125
  quietEffortFor: typeof quietEffortFor;
126
126
  listModels: typeof listModels;
package/lib/index.js CHANGED
@@ -90,14 +90,14 @@ function splitRoute(model) {
90
90
  }
91
91
  //#endregion
92
92
  //#region src/shared/route.ts
93
- const NEXT_STEPS_ROUTE = "/api/desktop-enhance/next-steps";
94
- const NEXT_MODELS_ROUTE = "/api/desktop-enhance/next-models";
95
- const NEXT_EFFORTS_ROUTE = "/api/desktop-enhance/next-efforts";
93
+ const SUGGEST_ROUTE = "/api/desktop-enhance/suggest";
94
+ const SUGGEST_MODELS_ROUTE = "/api/desktop-enhance/suggest-models";
95
+ const SUGGEST_EFFORTS_ROUTE = "/api/desktop-enhance/suggest-efforts";
96
96
  //#endregion
97
- //#region src/next/host.ts
98
- const NEXT_STEPS_PATH = NEXT_STEPS_ROUTE;
99
- const NEXT_MODELS_PATH = NEXT_MODELS_ROUTE;
100
- const NEXT_EFFORTS_PATH = NEXT_EFFORTS_ROUTE;
97
+ //#region src/suggest/host.ts
98
+ const SUGGEST_PATH = SUGGEST_ROUTE;
99
+ const SUGGEST_MODELS_PATH = SUGGEST_MODELS_ROUTE;
100
+ const SUGGEST_EFFORTS_PATH = SUGGEST_EFFORTS_ROUTE;
101
101
  const MODEL_TIMEOUT_MS = 2e4;
102
102
  const MAX_OUTPUT_TOKENS = 4e3;
103
103
  function fetchRegistryOf(ctx) {
@@ -116,9 +116,9 @@ async function collectText(chunks) {
116
116
  }
117
117
  if (chunk.type === "finish") finish = chunk.reason ?? {};
118
118
  }
119
- if (finish === null) throw new Error("next-steps: the model stream ended without a finish chunk");
119
+ if (finish === null) throw new Error("suggest: the model stream ended without a finish chunk");
120
120
  const kind = finish.kind;
121
- if (kind !== "stop" && kind !== "max-tokens") throw new Error(`next-steps: the model call did not finish normally (${String(kind)})`);
121
+ if (kind !== "stop" && kind !== "max-tokens") throw new Error(`suggest: the model call did not finish normally (${String(kind)})`);
122
122
  return [...parts.entries()].sort((left, right) => left[0] - right[0]).map(([, value]) => value).join("");
123
123
  }
124
124
  function routeOf(session) {
@@ -155,7 +155,7 @@ function knownNamesOf(messages) {
155
155
  }
156
156
  return found ? names : null;
157
157
  }
158
- async function handleNextSteps(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
158
+ async function handleSuggest(ctx, request, timeoutMs = MODEL_TIMEOUT_MS) {
159
159
  const empty = () => json({ items: [] });
160
160
  let sessionId;
161
161
  let preference = FOLLOW_SESSION;
@@ -235,7 +235,7 @@ function withDiag(ctx, body, record) {
235
235
  });
236
236
  }
237
237
  function logDiag(ctx, record) {
238
- const text = `next-steps: ${JSON.stringify(record)}`;
238
+ const text = `suggest: ${JSON.stringify(record)}`;
239
239
  ctx.logger?.warn(text);
240
240
  }
241
241
  async function quietEffortFor(llm, route) {
@@ -307,25 +307,25 @@ function json(body, status = 200) {
307
307
  headers: { "content-type": "application/json" }
308
308
  });
309
309
  }
310
- function registerNextStepsRoute(ctx) {
310
+ function registerSuggestRoute(ctx) {
311
311
  const registry = fetchRegistryOf(ctx);
312
312
  if (registry === null) return () => {};
313
313
  const disposers = [];
314
314
  disposers.push(registry.register({
315
- path: NEXT_STEPS_PATH,
315
+ path: SUGGEST_PATH,
316
316
  methods: ["POST"],
317
317
  requestBody: "buffered",
318
- fetch: (request) => handleNextSteps(ctx, request)
318
+ fetch: (request) => handleSuggest(ctx, request)
319
319
  }));
320
320
  try {
321
321
  disposers.push(registry.register({
322
- path: NEXT_MODELS_PATH,
322
+ path: SUGGEST_MODELS_PATH,
323
323
  methods: ["GET"],
324
324
  requestBody: "buffered",
325
325
  fetch: async () => json(await listModels(ctx))
326
326
  }));
327
327
  disposers.push(registry.register({
328
- path: NEXT_EFFORTS_PATH,
328
+ path: SUGGEST_EFFORTS_PATH,
329
329
  methods: ["GET"],
330
330
  requestBody: "buffered",
331
331
  fetch: async (request) => {
@@ -350,32 +350,32 @@ const Config = Schema.object({
350
350
  findBar: Schema.boolean().default(true).volatile(),
351
351
  turnPrefix: Schema.boolean().default(true).volatile(),
352
352
  compactionRow: Schema.boolean().default(true).volatile(),
353
- nextSteps: Schema.boolean().default(true).volatile(),
354
- nextModel: Schema.string().default("").volatile(),
355
- nextReasoning: Schema.string().default("").volatile(),
356
- nextPrompt: Schema.string().default("").volatile()
353
+ suggest: Schema.boolean().default(true).volatile(),
354
+ suggestModel: Schema.string().default("").volatile(),
355
+ suggestReasoning: Schema.string().default("").volatile(),
356
+ suggestPrompt: Schema.string().default("").volatile()
357
357
  });
358
358
  function apply(ctx) {
359
359
  ctx.inject(["connection"], (serviceCtx) => {
360
360
  try {
361
- serviceCtx.effect(() => registerNextStepsRoute(serviceCtx), "desktop-enhance: next-steps route");
361
+ serviceCtx.effect(() => registerSuggestRoute(serviceCtx), "desktop-enhance: suggest route");
362
362
  } catch (error) {
363
- serviceCtx.logger?.warn(`desktop-enhance: next-steps route not registered: ${String(error)}`);
363
+ serviceCtx.logger?.warn(`desktop-enhance: suggest route not registered: ${String(error)}`);
364
364
  }
365
365
  });
366
366
  }
367
367
  const __test = {
368
- NEXT_STEPS_PATH,
369
- NEXT_MODELS_PATH,
370
- NEXT_EFFORTS_PATH,
368
+ SUGGEST_PATH,
369
+ SUGGEST_MODELS_PATH,
370
+ SUGGEST_EFFORTS_PATH,
371
371
  MODEL_TIMEOUT_MS,
372
372
  MAX_OUTPUT_TOKENS,
373
373
  buildPrompt,
374
374
  buildMessages,
375
375
  collectText,
376
376
  knownNamesOf,
377
- handleNextSteps,
378
- registerNextStepsRoute,
377
+ handleSuggest,
378
+ registerSuggestRoute,
379
379
  routeOf,
380
380
  quietEffortFor,
381
381
  listModels,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-desktop-enhance",
3
- "version": "0.1.0",
4
- "description": "DSH Desktop 界面增强:页内查找(Cmd+F)、轮次编号、上下文压缩行常显、下一步建议。",
3
+ "version": "0.2.1",
4
+ "description": "DSH Desktop 界面增强:页内查找(Cmd+F)、轮次编号、上下文压缩行常显、建议。",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./lib/index.js",
@@ -26,18 +26,6 @@
26
26
  "lib",
27
27
  "cordis.patch.yml"
28
28
  ],
29
- "scripts": {
30
- "build": "tsdown",
31
- "dev": "tsdown --watch",
32
- "typecheck": "tsc --noEmit",
33
- "test": "node --test tests/*.test.mjs",
34
- "test:isolation": "./tools/test-isolation.sh",
35
- "verify": "pnpm typecheck && pnpm build && pnpm test && pnpm test:isolation",
36
- "prepack": "pnpm build",
37
- "prepublishOnly": "pnpm verify",
38
- "release:patch": "npm version patch && pnpm publish",
39
- "release:minor": "npm version minor && pnpm publish"
40
- },
41
29
  "publishConfig": {
42
30
  "access": "public"
43
31
  },
@@ -55,5 +43,15 @@
55
43
  "@types/react": "^18.3.31",
56
44
  "tsdown": "0.23.0",
57
45
  "typescript": "5.9.3"
46
+ },
47
+ "scripts": {
48
+ "build": "tsdown",
49
+ "dev": "tsdown --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "test": "node --test tests/*.test.mjs",
52
+ "test:isolation": "./tools/test-isolation.sh",
53
+ "verify": "pnpm typecheck && pnpm build && pnpm test && pnpm test:isolation",
54
+ "release:patch": "npm version patch && pnpm publish",
55
+ "release:minor": "npm version minor && pnpm publish"
58
56
  }
59
- }
57
+ }