@klarkxy/dsh-session-manager 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -72,6 +72,9 @@ const text = {
72
72
  creatingContinuation: "正在创建新会话",
73
73
  continueNewHint: "在来源会话的工作区新建会话,将摘要和原会话 ID 填入草稿,不自动发送。",
74
74
  compactNothing: "没有值得压缩的内容。",
75
+ settingsTitle: "会话管理",
76
+ settingsGroupTitle: "会话行为",
77
+ settingsGroupHint: "继续灰影、自动继续、交接与会话摘要",
75
78
  settingsMode: "继续灰影",
76
79
  modeFixed: "固定文本",
77
80
  modeAuto: "模型生成",
@@ -80,7 +83,12 @@ const text = {
80
83
  continueTextLabel: "固定文本内容",
81
84
  continueTextPlaceholder: "留空使用默认「继续」",
82
85
  ghostModelLabel: "生成模型",
83
- ghostModelHint: "每轮结束后生成一次并缓存;不选择时使用当前会话或宿主的默认模型。",
86
+ ghostModelHint: "每轮结束后生成一次并缓存;不选择时使用默认辅助模型。",
87
+ judgeModelLabel: "判定模型",
88
+ judgeModelHint: "判断回复是否提前结束;不选择时使用默认辅助模型。",
89
+ handoffGroup: "交接摘要",
90
+ handoffModelLabel: "交接模型",
91
+ handoffModelHint: "工具请求会话交接摘要时使用;不选择时使用默认辅助模型。",
84
92
  welcomeBackGroup: "回到会话时",
85
93
  recapEnabledLabel: "显示会话摘要",
86
94
  recapEnabledHint: "在对话中回顾上次进展。",
@@ -88,10 +96,24 @@ const text = {
88
96
  compactEnabledLabel: "建议压缩上下文",
89
97
  compactEnabledHint: "长时间离开后,建议先压缩上下文。",
90
98
  compactMinutesLabel: "离开多久后显示建议",
99
+ autoContinue: "自动继续",
100
+ autoContinueOff: "关闭",
101
+ autoContinueCautious: "谨慎",
102
+ autoContinueBold: "奔放",
103
+ autoContinueTooltip: "回复提前结束时自动发送继续。谨慎:有明确依据才继续;奔放:没完成就继续,低风险的选择按推荐方案走。会话开始后锁定。",
104
+ autoContinueLocked: "请在会话开始前选择自动继续",
105
+ autoContinueStale: "状态已在别处更改,已刷新",
106
+ autoContinueFailed: "切换失败",
107
+ autoContinueGroup: "自动继续",
108
+ autoContinueDefaultLabel: "新会话默认形态",
109
+ autoContinueDefaultHint: "只影响之后新建的会话;发送第一条消息前可在输入框上方单独选择,会话开始后保持锁定。",
110
+ autoContinueMaxLabel: "续跑次数上限",
111
+ autoContinueMaxHint: "你每发一条消息之后,最多自动继续的次数(1–10)。",
112
+ autoContinueMaxInvalid: "请填写 1–10 的整数。",
91
113
  minutesUnit: "分钟",
92
114
  modelLabel: "摘要模型",
93
- modelFollowOption: "当前会话 / 宿主默认模型",
94
- modelGroupHint: "只用于欢迎回来摘要;留空跟随会话或宿主默认。",
115
+ modelFollowOption: "默认辅助模型",
116
+ modelGroupHint: "只用于欢迎回来摘要;留空使用默认辅助模型。",
95
117
  saveSettings: "保存",
96
118
  settingsSaving: "正在保存",
97
119
  settingsSaved: "已保存",
@@ -134,6 +156,9 @@ const text = {
134
156
  creatingContinuation: "Creating new session",
135
157
  continueNewHint: "Create a session in the source session's workspace and fill its draft with the recap and source session ID, without sending.",
136
158
  compactNothing: "There is nothing useful to compact.",
159
+ settingsTitle: "Session Management",
160
+ settingsGroupTitle: "Session behavior",
161
+ settingsGroupHint: "Continue ghost, auto-continue, handoff and session recap",
137
162
  settingsMode: "Continue ghost",
138
163
  modeFixed: "Fixed text",
139
164
  modeAuto: "Model-generated",
@@ -142,7 +167,12 @@ const text = {
142
167
  continueTextLabel: "Fixed text",
143
168
  continueTextPlaceholder: "Empty uses the default \"Continue\"",
144
169
  ghostModelLabel: "Generation model",
145
- ghostModelHint: "Generated once after each turn and cached. The default follows the current conversation or host.",
170
+ ghostModelHint: "Generated once after each turn and cached. The default follows the shared auxiliary model.",
171
+ judgeModelLabel: "Judge model",
172
+ judgeModelHint: "Decides whether a reply stopped early. The default follows the shared auxiliary model.",
173
+ handoffGroup: "Session handoff",
174
+ handoffModelLabel: "Handoff model",
175
+ handoffModelHint: "Used when an agent asks for a session handoff summary. The default follows the shared auxiliary model.",
146
176
  welcomeBackGroup: "When you return",
147
177
  recapEnabledLabel: "Show session recap",
148
178
  recapEnabledHint: "Review your previous progress in the conversation.",
@@ -150,10 +180,24 @@ const text = {
150
180
  compactEnabledLabel: "Suggest context compaction",
151
181
  compactEnabledHint: "Suggest compacting the context after a long time away.",
152
182
  compactMinutesLabel: "Show suggestion after",
183
+ autoContinue: "Auto-continue",
184
+ autoContinueOff: "Off",
185
+ autoContinueCautious: "Cautious",
186
+ autoContinueBold: "Bold",
187
+ autoContinueTooltip: "Sends a follow-up when a reply stops early. Cautious: continues only on clear evidence. Bold: continues unless done, taking the recommended option on low-risk choices. Locked once the session starts.",
188
+ autoContinueLocked: "Choose Auto-continue before the session starts",
189
+ autoContinueStale: "State changed elsewhere; refreshed",
190
+ autoContinueFailed: "Could not change Auto-continue",
191
+ autoContinueGroup: "Auto-continue",
192
+ autoContinueDefaultLabel: "Default for new sessions",
193
+ autoContinueDefaultHint: "Only affects sessions created later. Choose above the composer before sending the first message; the choice stays locked once the session starts.",
194
+ autoContinueMaxLabel: "Maximum continuations",
195
+ autoContinueMaxHint: "How many times it may continue automatically after each message you send (1–10).",
196
+ autoContinueMaxInvalid: "Enter a whole number from 1 to 10.",
153
197
  minutesUnit: "minutes",
154
198
  modelLabel: "Recap model",
155
- modelFollowOption: "Current conversation / host default",
156
- modelGroupHint: "Used only for the welcome-back recap; empty follows the conversation or host default.",
199
+ modelFollowOption: "Default auxiliary model",
200
+ modelGroupHint: "Used only for the welcome-back recap; empty follows the default auxiliary model.",
157
201
  saveSettings: "Save",
158
202
  settingsSaving: "Saving",
159
203
  settingsSaved: "Saved",
@@ -211,6 +255,10 @@ const EDIT_PREPARE_ENDPOINT = "edit.prepare";
211
255
  const EDIT_FILE_ENDPOINT = "edit.file";
212
256
  const CONFIG_READ_ENDPOINT = "config.read";
213
257
  const CONFIG_WRITE_ENDPOINT = "config.write";
258
+ const AUTO_CONTINUE_STATUS_ENDPOINT = "autocontinue.status";
259
+ const AUTO_CONTINUE_UPDATE_ENDPOINT = "autocontinue.update";
260
+ /** This plugin's own row inside the shared Session Management settings list. */
261
+ const SESSION_SETTINGS_ITEM_ID = "session-manager";
214
262
  /** Card entry id in the chat flow; the recap renders at the latest completed turn's tail. */
215
263
  const WELCOME_BACK_CARD_ID = "@klarkxy/dsh-session-manager.welcome-back";
216
264
  const TURN_TAIL_SLOT = "conversation.chat.turnTail";
@@ -800,6 +848,15 @@ function parseModelMenuChoices(value, bound) {
800
848
  });
801
849
  return choices;
802
850
  }
851
+ /** Reasoning-effort options for one choice; the saved value stays selectable. */
852
+ function modelMenuEffortOptions(choice, current) {
853
+ const listed = (choice?.efforts ?? []).map((item) => ({ ...item }));
854
+ if (current && !listed.some((item) => item.id === current)) listed.push({
855
+ id: current,
856
+ name: current
857
+ });
858
+ return listed;
859
+ }
803
860
  //#endregion
804
861
  //#region ../dsh-model-route/src/catalog-ui.tsx
805
862
  /**
@@ -1791,8 +1848,48 @@ ${modelMenuCss}
1791
1848
  font-size: 13px;
1792
1849
  line-height: 18px;
1793
1850
  }
1794
- /* Settings page: one quiet reading surface. Rules, not nested wells, separate
1795
- * sections; dependent controls align with the feature they belong to. */
1851
+ /* General-section item: a named details group mirroring the host's native
1852
+ * item rows (16px vertical padding, a 0.5px l2 rule the shell drops on the
1853
+ * last item, 24px label-control gutter). The body mounts on first expand and
1854
+ * stays mounted while collapsed, so a fold never drops an unsaved draft. */
1855
+ .${FREE_CHAT_ROOT}.dsm-item {
1856
+ padding: 0;
1857
+ border-bottom: 0.5px solid var(--dsw-alias-border-l2);
1858
+ }
1859
+ .${FREE_CHAT_ROOT} .dsm-item-summary {
1860
+ display: flex;
1861
+ align-items: center;
1862
+ justify-content: space-between;
1863
+ gap: 24px;
1864
+ padding: 16px 0;
1865
+ list-style: none;
1866
+ cursor: pointer;
1867
+ }
1868
+ .${FREE_CHAT_ROOT} .dsm-item-summary::-webkit-details-marker { display: none; }
1869
+ .${FREE_CHAT_ROOT} .dsm-item-summary > span:first-child { min-width: 0; }
1870
+ .${FREE_CHAT_ROOT} .dsm-item-title {
1871
+ display: block;
1872
+ font-size: 14px;
1873
+ line-height: 20px;
1874
+ color: var(--dsw-alias-label-primary);
1875
+ }
1876
+ .${FREE_CHAT_ROOT} .dsm-item-desc {
1877
+ display: block;
1878
+ margin-top: 4px;
1879
+ font-size: 12px;
1880
+ line-height: 18px;
1881
+ color: var(--dsw-alias-label-secondary);
1882
+ }
1883
+ .${FREE_CHAT_ROOT} .dsm-item-chevron {
1884
+ width: 14px;
1885
+ height: 14px;
1886
+ color: var(--dsw-alias-label-secondary);
1887
+ transition: transform 0.15s ease;
1888
+ }
1889
+ .${FREE_CHAT_ROOT}.dsm-item[open] > .dsm-item-summary .dsm-item-chevron { transform: rotate(180deg); }
1890
+ .${FREE_CHAT_ROOT} .dsm-item-body { padding: 0 0 16px; }
1891
+ /* Settings group body: one quiet reading surface. Rules, not nested wells,
1892
+ * separate sections; dependent controls align with the feature they belong to. */
1796
1893
  .${FREE_CHAT_ROOT} .dsm-settings {
1797
1894
  display: flex;
1798
1895
  flex-direction: column;
@@ -1931,6 +2028,50 @@ ${modelMenuCss}
1931
2028
  }
1932
2029
  .${FREE_CHAT_ROOT} .dsh-welcome-done { margin: 6px 0 0; }
1933
2030
 
2031
+ /* Hero-row Auto-continue chip: icon + mode label, tinted while a mode is on. */
2032
+ .${FREE_CHAT_ROOT}.smac-option {
2033
+ display: inline-flex;
2034
+ align-items: center;
2035
+ gap: 6px;
2036
+ }
2037
+ .${FREE_CHAT_ROOT} .smac-chip {
2038
+ display: inline-flex;
2039
+ align-items: center;
2040
+ gap: 4px;
2041
+ height: 28px;
2042
+ padding: 0 10px;
2043
+ border-radius: 999px;
2044
+ font-size: 13px;
2045
+ line-height: 20px;
2046
+ border: 1px solid var(--dsw-alias-border-l3);
2047
+ background: transparent;
2048
+ color: var(--dsw-alias-label-secondary);
2049
+ cursor: pointer;
2050
+ }
2051
+ .${FREE_CHAT_ROOT} .smac-chip svg { width: 16px; height: 16px; }
2052
+ .${FREE_CHAT_ROOT} .smac-chip .smac-chevron { width: 12px; height: 12px; }
2053
+ .${FREE_CHAT_ROOT} .smac-chip:hover:not(:disabled) {
2054
+ background: var(--dsw-alias-interactive-bg-hover);
2055
+ }
2056
+ .${FREE_CHAT_ROOT} .smac-chip.smac-chip-on {
2057
+ background: var(--dsw-alias-button-primary-fill);
2058
+ border-color: var(--dsw-alias-button-primary-fill);
2059
+ color: var(--dsw-alias-label-primary-foreground);
2060
+ }
2061
+ .${FREE_CHAT_ROOT} .smac-chip.smac-chip-on:hover:not(:disabled) {
2062
+ background: var(--dsw-alias-button-primary-hover);
2063
+ border-color: var(--dsw-alias-button-primary-hover);
2064
+ }
2065
+ .${FREE_CHAT_ROOT} .smac-chip:disabled {
2066
+ opacity: 0.5;
2067
+ cursor: default;
2068
+ }
2069
+ .${FREE_CHAT_ROOT} .smac-note {
2070
+ font-size: 12px;
2071
+ line-height: 18px;
2072
+ color: var(--dsw-alias-label-tertiary);
2073
+ }
2074
+
1934
2075
  .${FREE_CHAT_ROOT} .dsm-user-original { display: contents; }
1935
2076
  .${FREE_CHAT_ROOT} .dsm-user-original[hidden] { display: none; }
1936
2077
  .${FREE_CHAT_ROOT} .dsm-message-edit { width: min(100%, 720px); align-self: flex-end; }
@@ -3115,7 +3256,7 @@ function createPresenceConfigModel(host, delay = (ms) => new Promise((resolve) =
3115
3256
  function same(view, patch) {
3116
3257
  if (view === void 0) return false;
3117
3258
  return Object.entries(patch).every(([key, value]) => {
3118
- if (key === "model" || key === "ghostModel") return sameRoute(view[key], value);
3259
+ if (key === "model" || key === "ghostModel" || key === "judgeModel" || key === "handoffModel") return sameRoute(view[key], value);
3119
3260
  return view[key] === value;
3120
3261
  });
3121
3262
  }
@@ -3341,6 +3482,175 @@ function createWelcomeBackModel(host, ids = () => crypto.randomUUID(), minInterv
3341
3482
  };
3342
3483
  }
3343
3484
  //#endregion
3485
+ //#region src/auto-continue-option.tsx
3486
+ /**
3487
+ * The hero-row Auto-continue option: a chip that opens a three-mode menu on a
3488
+ * blank session and locks once the session starts. Mirrors the delegation
3489
+ * chip's revision discipline: stale reads never roll the chip back, a 'stale'
3490
+ * rejection refetches, and 'session-locked' explains itself in the tooltip.
3491
+ */
3492
+ var RpcError = class extends Error {
3493
+ code;
3494
+ constructor(code, message) {
3495
+ super(message);
3496
+ this.name = "RpcError";
3497
+ this.code = code;
3498
+ }
3499
+ };
3500
+ async function rpc(call, endpoint, payload) {
3501
+ const result = await call(endpoint, payload);
3502
+ if (!result || result.ok !== true) {
3503
+ const failed = result && "error" in result ? result.error : void 0;
3504
+ throw new RpcError(failed?.code ?? "unknown", failed?.message ?? "");
3505
+ }
3506
+ return result.value;
3507
+ }
3508
+ const MODES = [
3509
+ "off",
3510
+ "cautious",
3511
+ "bold"
3512
+ ];
3513
+ function AutoContinueOption({ sessionId, call, subscribeGeneration, useSession, locale }) {
3514
+ const [status, setStatus] = (0, react.useState)();
3515
+ const [saving, setSaving] = (0, react.useState)(false);
3516
+ const [notice, setNotice] = (0, react.useState)("");
3517
+ const [open, setOpen] = (0, react.useState)(false);
3518
+ const generationRef = (0, react.useRef)(0);
3519
+ const readSeqRef = (0, react.useRef)(0);
3520
+ const openState = useSession?.((snapshot) => snapshot.openState);
3521
+ const sessionRunning = useSession?.((snapshot) => snapshot.running === true);
3522
+ const sessionBlank = useSession?.((snapshot) => snapshot.blank);
3523
+ const submitting = useSession?.((snapshot) => snapshot.awaitingFirstTurn === true || (snapshot.pendingSubmissions?.length ?? 0) > 0);
3524
+ const load = (0, react.useCallback)(async () => {
3525
+ const generation = generationRef.current;
3526
+ const seq = ++readSeqRef.current;
3527
+ try {
3528
+ const next = await rpc(call, AUTO_CONTINUE_STATUS_ENDPOINT, { sessionId });
3529
+ if (generationRef.current !== generation || seq !== readSeqRef.current) return;
3530
+ setStatus((prev) => prev && prev.sessionId === next.sessionId && next.revision < prev.revision ? prev : next);
3531
+ } catch {}
3532
+ }, [call, sessionId]);
3533
+ (0, react.useEffect)(() => {
3534
+ generationRef.current += 1;
3535
+ readSeqRef.current += 1;
3536
+ setStatus(void 0);
3537
+ setSaving(false);
3538
+ setNotice("");
3539
+ setOpen(false);
3540
+ }, [sessionId]);
3541
+ (0, react.useEffect)(() => {
3542
+ load();
3543
+ }, [
3544
+ load,
3545
+ openState,
3546
+ sessionRunning,
3547
+ sessionBlank
3548
+ ]);
3549
+ (0, react.useEffect)(() => {
3550
+ const off = subscribeGeneration?.(() => {
3551
+ load();
3552
+ });
3553
+ const win = typeof window === "undefined" ? void 0 : window;
3554
+ const onFocus = () => {
3555
+ load();
3556
+ };
3557
+ win?.addEventListener("focus", onFocus);
3558
+ return () => {
3559
+ off?.();
3560
+ win?.removeEventListener("focus", onFocus);
3561
+ };
3562
+ }, [load, subscribeGeneration]);
3563
+ (0, react.useEffect)(() => {
3564
+ if (!notice) return;
3565
+ const timer = setTimeout(() => setNotice(""), 4e3);
3566
+ return () => clearTimeout(timer);
3567
+ }, [notice]);
3568
+ const choose = (0, react.useCallback)(async (mode) => {
3569
+ if (!status || saving || status.locked || sessionBlank === false || sessionRunning || submitting || mode === status.mode) return;
3570
+ const generation = generationRef.current;
3571
+ readSeqRef.current += 1;
3572
+ setSaving(true);
3573
+ setNotice("");
3574
+ try {
3575
+ const next = await rpc(call, AUTO_CONTINUE_UPDATE_ENDPOINT, {
3576
+ sessionId,
3577
+ mode,
3578
+ expectedRevision: status.revision
3579
+ });
3580
+ if (generationRef.current !== generation) return;
3581
+ setStatus(next);
3582
+ } catch (reason) {
3583
+ if (generationRef.current !== generation) return;
3584
+ if (reason instanceof RpcError && (reason.code === "stale" || reason.code === "session-locked")) {
3585
+ setNotice(reason.code === "stale" ? "autoContinueStale" : "autoContinueLocked");
3586
+ load();
3587
+ } else setNotice("autoContinueFailed");
3588
+ } finally {
3589
+ if (generationRef.current === generation) setSaving(false);
3590
+ }
3591
+ }, [
3592
+ call,
3593
+ load,
3594
+ saving,
3595
+ sessionId,
3596
+ status,
3597
+ sessionBlank,
3598
+ sessionRunning,
3599
+ submitting
3600
+ ]);
3601
+ if (!status || status.role === "executor" || sessionBlank === false) return null;
3602
+ const mode = status.mode;
3603
+ const locked = status.locked || sessionRunning === true || submitting === true;
3604
+ const tooltip = locked ? uiText(locale, "autoContinueLocked") : notice ? uiText(locale, notice) : uiText(locale, "autoContinueTooltip");
3605
+ const modeKey = mode === "cautious" ? "autoContinueCautious" : mode === "bold" ? "autoContinueBold" : "autoContinueOff";
3606
+ const label = `${uiText(locale, "autoContinue")} · ${uiText(locale, modeKey)}`;
3607
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3608
+ className: `${FREE_CHAT_ROOT} smac-option`,
3609
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
3610
+ open,
3611
+ align: "end",
3612
+ portal: true,
3613
+ listClassName: FREE_CHAT_ROOT,
3614
+ selectedId: mode,
3615
+ items: MODES.map((value) => ({
3616
+ id: value,
3617
+ label: uiText(locale, value === "off" ? "autoContinueOff" : value === "cautious" ? "autoContinueCautious" : "autoContinueBold")
3618
+ })),
3619
+ onSelect: (id) => {
3620
+ setOpen(false);
3621
+ choose(id);
3622
+ },
3623
+ onClose: () => setOpen(false),
3624
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
3625
+ label: tooltip,
3626
+ side: "bottom",
3627
+ gap: 4,
3628
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
3629
+ className: `smac-chip${mode !== "off" ? " smac-chip-on" : ""}`,
3630
+ active: mode !== "off",
3631
+ "aria-haspopup": "menu",
3632
+ "aria-expanded": open,
3633
+ "aria-label": label,
3634
+ disabled: locked || saving,
3635
+ onClick: () => setOpen((current) => !current),
3636
+ children: [
3637
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {}),
3638
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3639
+ className: "smac-chip-label",
3640
+ children: label
3641
+ }),
3642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: "smac-chevron" })
3643
+ ]
3644
+ })
3645
+ })
3646
+ }), notice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3647
+ role: "status",
3648
+ className: "smac-note",
3649
+ children: uiText(locale, notice)
3650
+ }) : null]
3651
+ });
3652
+ }
3653
+ //#endregion
3344
3654
  //#region ../dsh-plugin-kit/src/contracts.ts
3345
3655
  /** `@klarkxy/dsh-git-commit` becomes `KlarkxyDshGitCommitModelSettingsChanges`. */
3346
3656
  function modelSettingsChangesIdentity(packageName) {
@@ -3354,6 +3664,153 @@ function modelSettingsChangesIdentity(packageName) {
3354
3664
  namespace: segment
3355
3665
  };
3356
3666
  }
3667
+ /**
3668
+ * Shared "Session Management" settings section id. The section is provided by
3669
+ * shared infrastructure (see `registerSessionSettingsItem` in `client-utils`),
3670
+ * not by any one business plugin: every participant registers only a row into
3671
+ * {@link SESSION_SETTINGS_ITEM_SLOT}, so any enabled subset yields the same page.
3672
+ */
3673
+ const SESSION_SETTINGS_SECTION_ID = "session-manager";
3674
+ /**
3675
+ * Child list slot declared by the shared Session Management page. Neutral
3676
+ * naming on purpose: participants never import each other, they only
3677
+ * contribute `{id, order}` rows into this slot while the page is alive.
3678
+ */
3679
+ const SESSION_SETTINGS_ITEM_SLOT = "dsh.session-settings.item";
3680
+ /**
3681
+ * Host-owned nav icon seat: a root-scoped list where each entry's `id` must
3682
+ * equal the `settings.section` id it decorates. Owner props are
3683
+ * `{size, active}`; icons stay `aria-hidden` so the section label remains the
3684
+ * accessible name. Contributors into a shared section may register the same
3685
+ * icon `id` with different priorities — the ledger keeps every entry while the
3686
+ * host renders one icon per section.
3687
+ */
3688
+ const SETTINGS_SECTION_ICON_SLOT = "settings.section.icon";
3689
+ /** Authenticated host-RPC channel for get/set of the shared default. */
3690
+ const AUXILIARY_MODEL_CHANNEL = "/dsh-auxiliary-model";
3691
+ /** Model-settings namespace under which the shared default is published. */
3692
+ const AUXILIARY_MODEL_NS = "@klarkxy/dsh-plugin-kit/auxiliary-model";
3693
+ /**
3694
+ * Typert identity of the shared change stream. Mounted on the root context
3695
+ * with ownerNs filtering, so frames only carry {@link AUXILIARY_MODEL_NS}.
3696
+ */
3697
+ const AUXILIARY_MODEL_CHANGES_IDENTITY = modelSettingsChangesIdentity("@klarkxy/dsh-plugin-kit");
3698
+ //#endregion
3699
+ //#region ../dsh-plugin-kit/src/client-utils.ts
3700
+ /** One `<svg>` with the shared icon chrome; children are plain elements. */
3701
+ function lineIcon(children) {
3702
+ return function SettingsSectionIcon(props) {
3703
+ return (0, react.createElement)("svg", {
3704
+ "aria-hidden": "true",
3705
+ width: props.size ?? 16,
3706
+ height: props.size ?? 16,
3707
+ viewBox: "0 0 16 16",
3708
+ fill: "none",
3709
+ stroke: "currentColor",
3710
+ strokeWidth: 1,
3711
+ strokeLinecap: "round",
3712
+ strokeLinejoin: "round"
3713
+ }, ...children.map(([tag, attrs], index) => (0, react.createElement)(tag, {
3714
+ key: index,
3715
+ ...attrs
3716
+ })));
3717
+ };
3718
+ }
3719
+ /** Chat bubble for the shared Session Management section. */
3720
+ const IconSessionSettings = lineIcon([["path", { d: "M8.5 13a5.5 5.5 0 0 0 5.5-5.5A5.5 5.5 0 0 0 8.5 2 5.5 5.5 0 0 0 3 7.5a5.48 5.48 0 0 0 .9 3L2.8 12.6a.5.5 0 0 0 .62.62l2.9-.98a5.48 5.48 0 0 0 2.18.76Z" }]]);
3721
+ /** Private Cordis rendezvous; every bundled copy of this helper resolves the same root service. */
3722
+ const SESSION_SETTINGS_PAGE_SERVICE = "dshSessionSettingsPage";
3723
+ /** The page component renders only its declared child list: items paint their own titles and styles. */
3724
+ function SessionSettingsPageView(props) {
3725
+ return props.renderSlot(SESSION_SETTINGS_ITEM_SLOT, {});
3726
+ }
3727
+ /**
3728
+ * Acquire the root-owned page service, creating and providing it on first use.
3729
+ * One counter covers participants, the section wait and the provider: the last
3730
+ * release removes the wait and then the provider synchronously (Cordis deletes
3731
+ * the impl before its async disposer awaits), so a later acquire sees a clean
3732
+ * get() miss and can re-establish the page.
3733
+ */
3734
+ function acquireSessionSettingsPage(root) {
3735
+ const existing = root.get(SESSION_SETTINGS_PAGE_SERVICE, false);
3736
+ if (existing) return existing;
3737
+ const slots = root.slots;
3738
+ let users = 0;
3739
+ let stopPage;
3740
+ let unprovide;
3741
+ const page = {
3742
+ retain() {
3743
+ users += 1;
3744
+ if (!stopPage) stopPage = slots.inject("settings.section", () => slots.register({
3745
+ name: "settings.section",
3746
+ id: SESSION_SETTINGS_SECTION_ID,
3747
+ order: 30,
3748
+ label: () => {
3749
+ const locale = root.get("locale", false);
3750
+ return String(locale?.getSnapshot?.().active ?? "").startsWith("zh") ? "会话管理" : "Session Management";
3751
+ },
3752
+ children: { [SESSION_SETTINGS_ITEM_SLOT]: {
3753
+ kind: "list",
3754
+ scope: "root"
3755
+ } }
3756
+ }, SessionSettingsPageView));
3757
+ },
3758
+ release() {
3759
+ if (users <= 0) return;
3760
+ users -= 1;
3761
+ if (users > 0) return;
3762
+ stopPage?.();
3763
+ stopPage = void 0;
3764
+ unprovide?.();
3765
+ }
3766
+ };
3767
+ unprovide = root.provide(SESSION_SETTINGS_PAGE_SERVICE, page);
3768
+ return page;
3769
+ }
3770
+ /**
3771
+ * Contribute one row to the shared Session Management settings page.
3772
+ *
3773
+ * `ctx` is the plugin's own context: the row's slot wait unloads with the
3774
+ * plugin, while the page itself is retained on the shared root context for as
3775
+ * long as any contributor lives. There is no fallback seat — when every
3776
+ * contributor is gone the section disappears with the page. Returns a disposer.
3777
+ * Shared root-owned infrastructure (the auxiliary-model card) calls this on
3778
+ * `ctx.root` so its row, icon and page share are owned by the root lifetime
3779
+ * and released by the returned disposer, not by any plugin fiber.
3780
+ */
3781
+ function registerSessionSettingsItem(ctx, item, component) {
3782
+ const slots = ctx.slots;
3783
+ const root = ctx.root;
3784
+ const page = acquireSessionSettingsPage(root);
3785
+ let stopItem;
3786
+ let stopIcon;
3787
+ try {
3788
+ page.retain();
3789
+ stopItem = slots.inject(SESSION_SETTINGS_ITEM_SLOT, () => slots.register({
3790
+ name: SESSION_SETTINGS_ITEM_SLOT,
3791
+ ...item
3792
+ }, component));
3793
+ stopIcon = slots.inject(SETTINGS_SECTION_ICON_SLOT, () => slots.register({
3794
+ name: SETTINGS_SECTION_ICON_SLOT,
3795
+ id: SESSION_SETTINGS_SECTION_ID,
3796
+ order: item.order,
3797
+ priority: item.order
3798
+ }, IconSessionSettings));
3799
+ } catch (error) {
3800
+ stopIcon?.();
3801
+ stopItem?.();
3802
+ page.release();
3803
+ throw error;
3804
+ }
3805
+ let disposed = false;
3806
+ return () => {
3807
+ if (disposed) return;
3808
+ disposed = true;
3809
+ stopIcon?.();
3810
+ stopItem?.();
3811
+ page.release();
3812
+ };
3813
+ }
3357
3814
  //#endregion
3358
3815
  //#region ../dsh-plugin-kit/src/model-settings-client.ts
3359
3816
  /** This stream carries invalidations only; ready is never a settings snapshot. */
@@ -3523,6 +3980,488 @@ function createModelSettingsSubscriber(ctx, packageName, identity = modelSetting
3523
3980
  } });
3524
3981
  }
3525
3982
  //#endregion
3983
+ //#region ../dsh-plugin-kit/src/auxiliary-model-client.tsx
3984
+ const AUXILIARY_MODEL_PACKAGE = "@klarkxy/dsh-plugin-kit";
3985
+ const AUXILIARY_MODEL_SEAT_ID = "auxiliary-model";
3986
+ /** Private Cordis rendezvous; every bundled copy resolves the same root service. */
3987
+ const AUXILIARY_MODEL_CARD_SERVICE = "dshAuxiliaryModelSettingsCard";
3988
+ /** Theme-token-only stylesheet: officialUiCss scope plus the shared ModelMenu recipe. */
3989
+ const auxiliaryModelCss = `${officialUiCss("ams-root")}${modelMenuCss}
3990
+ .ams-root .ams-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
3991
+ `;
3992
+ const auxiliaryCopy = {
3993
+ zh: {
3994
+ title: "默认辅助模型",
3995
+ help: "各辅助功能(如标题生成、摘要)没有单独指定模型时使用的模型;仅影响声明使用它的功能。",
3996
+ model: "辅助模型",
3997
+ modelDefault: "跟随会话或宿主默认",
3998
+ modelHint: "不选择模型时,各功能按自己的规则决定对话模型。",
3999
+ effortDefault: "跟随模型默认",
4000
+ loading: "正在读取设置…",
4001
+ failed: "无法读取模型设置。",
4002
+ stale: "设置已被其他窗口更新,请刷新后重试。",
4003
+ saveFailed: "保存失败,原设置未更改。请重试。",
4004
+ saved: "已保存。",
4005
+ retry: "重试",
4006
+ refresh: "刷新设置",
4007
+ syncUnavailable: "自动刷新不可用,请手动刷新设置。"
4008
+ },
4009
+ en: {
4010
+ title: "Default Auxiliary Model",
4011
+ help: "The model used by auxiliary features (such as title generation or summarizing) that do not name their own model; only features that opted in are affected.",
4012
+ model: "Auxiliary model",
4013
+ modelDefault: "Follow session or host default",
4014
+ modelHint: "With no model selected, each feature follows its own rules for the conversation model.",
4015
+ effortDefault: "Follow model default",
4016
+ loading: "Loading settings…",
4017
+ failed: "Could not load model settings.",
4018
+ stale: "Settings were changed elsewhere; refresh and retry.",
4019
+ saveFailed: "Save failed; the previous setting was not changed. Try again.",
4020
+ saved: "Saved.",
4021
+ retry: "Retry",
4022
+ refresh: "Refresh settings",
4023
+ syncUnavailable: "Automatic refresh unavailable; refresh settings manually."
4024
+ }
4025
+ };
4026
+ /** A failed RPC keeps its code so the card can translate known rejections. */
4027
+ var AuxiliaryRpcError = class extends Error {
4028
+ code;
4029
+ constructor(code, message) {
4030
+ super(message);
4031
+ this.code = code;
4032
+ this.name = "AuxiliaryRpcError";
4033
+ }
4034
+ };
4035
+ /** Host UI language, following live switches with the host's English fallback. */
4036
+ function useAuxiliaryLocale(client) {
4037
+ const subscribe = (0, react.useCallback)((listener) => client.locale?.subscribe(listener) ?? (() => {}), [client]);
4038
+ const snapshot = (0, react.useCallback)(() => client.locale?.getSnapshot().active ?? "en", [client]);
4039
+ return String((0, react.useSyncExternalStore)(subscribe, snapshot, snapshot)).startsWith("zh") ? "zh" : "en";
4040
+ }
4041
+ function isAbort(error) {
4042
+ return error instanceof Error && (error.name === "AbortError" || /abort|cancel/i.test(error.message));
4043
+ }
4044
+ function isStaleRejection(error) {
4045
+ if (!(error instanceof AuxiliaryRpcError)) return false;
4046
+ return error.code === "stale" || error.code === "auxiliary-model/conflict" || /changed|revision|reload|stale|conflict/i.test(error.message);
4047
+ }
4048
+ async function callAuxiliaryRpc(client, endpoint, payload = {}) {
4049
+ const result = await client.connection.rpc.call(AUXILIARY_MODEL_CHANNEL, endpoint, payload);
4050
+ if (!result || result.ok !== true) {
4051
+ const error = result && "error" in result ? result.error : void 0;
4052
+ throw new AuxiliaryRpcError(error?.code ?? "unknown", error?.message ?? `RPC ${endpoint} failed`);
4053
+ }
4054
+ return result.value;
4055
+ }
4056
+ /**
4057
+ * The shared card. All state comes from the shared host service over the
4058
+ * plugin-kit RPC channel; the card only renders the stored row.
4059
+ */
4060
+ function AuxiliaryModelCard({ client, subscribe }) {
4061
+ const language = useAuxiliaryLocale(client);
4062
+ const text = auxiliaryCopy[language];
4063
+ const [settings, setSettings] = (0, react.useState)();
4064
+ const catalogState = useModelCatalog((0, react.useCallback)(() => readModelCatalog(client), [client]), { subscribe: (0, react.useCallback)((change) => client.connection.generation?.subscribe(change), [client]) });
4065
+ const { refresh: refreshCatalog } = catalogState;
4066
+ const choices = parseModelMenuChoices(catalogState.catalog, settings?.model ?? void 0);
4067
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
4068
+ const [error, setError] = (0, react.useState)("");
4069
+ const [note, setNote] = (0, react.useState)(false);
4070
+ const [busy, setBusy] = (0, react.useState)(false);
4071
+ const [syncError, setSyncError] = (0, react.useState)("");
4072
+ const operation = (0, react.useRef)({
4073
+ epoch: 0,
4074
+ active: false,
4075
+ reading: false,
4076
+ writing: false,
4077
+ pending: false
4078
+ });
4079
+ const refreshRef = (0, react.useRef)(() => {});
4080
+ const refresh = () => {
4081
+ const state = operation.current;
4082
+ if (!state.active) return;
4083
+ if (state.reading || state.writing) {
4084
+ state.pending = true;
4085
+ return;
4086
+ }
4087
+ state.pending = false;
4088
+ state.reading = true;
4089
+ setBusy(true);
4090
+ const epoch = state.epoch;
4091
+ callAuxiliaryRpc(client, "defaults.get").then((next) => {
4092
+ if (state.active && state.epoch === epoch && !state.pending) {
4093
+ setSettings(next);
4094
+ setError("");
4095
+ }
4096
+ }).catch((reason) => {
4097
+ if (state.active && state.epoch === epoch && !state.pending && !isAbort(reason)) setError("failed");
4098
+ }).finally(() => {
4099
+ if (!state.active || state.epoch !== epoch) return;
4100
+ state.reading = false;
4101
+ setBusy(false);
4102
+ if (state.pending) refreshRef.current();
4103
+ });
4104
+ };
4105
+ refreshRef.current = refresh;
4106
+ (0, react.useEffect)(() => {
4107
+ const state = operation.current;
4108
+ state.epoch++;
4109
+ state.active = true;
4110
+ state.reading = false;
4111
+ state.writing = false;
4112
+ state.pending = false;
4113
+ setBusy(false);
4114
+ setSettings(void 0);
4115
+ setError("");
4116
+ setNote(false);
4117
+ refreshRef.current();
4118
+ return () => {
4119
+ state.active = false;
4120
+ state.epoch++;
4121
+ };
4122
+ }, [client]);
4123
+ (0, react.useEffect)(() => {
4124
+ setSyncError("");
4125
+ let live = true;
4126
+ let stop;
4127
+ try {
4128
+ stop = subscribe?.(() => {
4129
+ if (live) {
4130
+ setSyncError("");
4131
+ refreshCatalog();
4132
+ refreshRef.current();
4133
+ }
4134
+ }, (message) => {
4135
+ if (live) setSyncError(message);
4136
+ });
4137
+ } catch {
4138
+ if (live) setSyncError("Model settings sync service unavailable");
4139
+ }
4140
+ return () => {
4141
+ live = false;
4142
+ try {
4143
+ stop?.();
4144
+ } catch {}
4145
+ };
4146
+ }, [subscribe, refreshCatalog]);
4147
+ const save = (0, react.useCallback)((model) => {
4148
+ const state = operation.current;
4149
+ if (!settings || !state.active || state.writing || state.reading) return;
4150
+ state.writing = true;
4151
+ const epoch = state.epoch;
4152
+ setBusy(true);
4153
+ setNote(false);
4154
+ setError("");
4155
+ callAuxiliaryRpc(client, "defaults.set", {
4156
+ model: model ?? {
4157
+ provider: "",
4158
+ model: ""
4159
+ },
4160
+ expectedRevision: settings.revision
4161
+ }).then((next) => {
4162
+ if (state.active && state.epoch === epoch) {
4163
+ setSettings(next);
4164
+ setNote(true);
4165
+ refreshCatalog();
4166
+ }
4167
+ }).catch((reason) => {
4168
+ if (state.active && state.epoch === epoch) setError(isStaleRejection(reason) ? "stale" : "saveFailed");
4169
+ }).finally(() => {
4170
+ if (!state.active || state.epoch !== epoch) return;
4171
+ state.writing = false;
4172
+ setBusy(false);
4173
+ if (state.pending) refreshRef.current();
4174
+ });
4175
+ }, [
4176
+ client,
4177
+ settings,
4178
+ refreshCatalog
4179
+ ]);
4180
+ const locked = busy;
4181
+ if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4182
+ className: "ams-root dsh-ui-card",
4183
+ "data-testid": "auxiliary-model-settings",
4184
+ children: [
4185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4186
+ className: "dsh-ui-heading",
4187
+ children: text.title
4188
+ }),
4189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4190
+ className: "dsh-ui-help",
4191
+ children: text.help
4192
+ }),
4193
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4194
+ role: "alert",
4195
+ className: "dsh-ui-error",
4196
+ children: text[error]
4197
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4198
+ role: "status",
4199
+ className: "dsh-ui-hint",
4200
+ children: text.loading
4201
+ }),
4202
+ syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4203
+ role: "status",
4204
+ className: "dsh-ui-hint",
4205
+ children: text.syncUnavailable
4206
+ }),
4207
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4208
+ className: "dsh-ui-actions",
4209
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4210
+ variant: "ghost",
4211
+ size: "sm",
4212
+ disabled: locked,
4213
+ onClick: () => refreshRef.current(),
4214
+ children: text.retry
4215
+ })
4216
+ })
4217
+ ]
4218
+ });
4219
+ const current = settings.model ?? {
4220
+ provider: "",
4221
+ model: ""
4222
+ };
4223
+ const follow = !current.provider || !current.model;
4224
+ const selected = follow ? void 0 : choices.find((item) => item.provider === current.provider && item.model === current.model);
4225
+ const efforts = follow ? [] : modelMenuEffortOptions(selected, current.reasoningEffort);
4226
+ const currentLabel = follow ? text.modelDefault : selected?.label ?? `${current.provider} / ${current.model}`;
4227
+ const selectedEffort = efforts.find((item) => item.id === (current.reasoningEffort || ""));
4228
+ const effortName = selectedEffort ? modelMenuEffortLabel(selectedEffort, language) : void 0;
4229
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4230
+ className: "ams-root dsh-ui-card",
4231
+ "data-testid": "auxiliary-model-settings",
4232
+ children: [
4233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4234
+ className: "dsh-ui-heading",
4235
+ children: text.title
4236
+ }),
4237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4238
+ className: "dsh-ui-help",
4239
+ children: text.help
4240
+ }),
4241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4242
+ className: "dsh-ui-field",
4243
+ children: [
4244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4245
+ className: "dsh-ui-label",
4246
+ children: text.model
4247
+ }),
4248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
4249
+ state: catalogState,
4250
+ locale: language
4251
+ }),
4252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
4253
+ locale: language,
4254
+ open: menuOpen && !locked,
4255
+ onOpenChange: (next) => setMenuOpen(next && !locked),
4256
+ listClassName: "ams-root ams-menu-portal",
4257
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4258
+ type: "button",
4259
+ className: "dsh-model-menu-trigger",
4260
+ disabled: locked,
4261
+ "aria-label": `${text.model}: ${currentLabel}`,
4262
+ children: [
4263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4264
+ className: "dsh-model-menu-triggerLabel",
4265
+ children: currentLabel
4266
+ }),
4267
+ !follow && efforts.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4268
+ className: "dsh-model-menu-triggerEffort",
4269
+ children: effortName ?? text.effortDefault
4270
+ }) : null,
4271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen && !locked ? " dsh-model-menu-chevronOpen" : ""}` })
4272
+ ]
4273
+ }),
4274
+ choices,
4275
+ selected: follow ? void 0 : {
4276
+ provider: current.provider,
4277
+ model: current.model
4278
+ },
4279
+ onPick: (picked) => save({
4280
+ provider: picked.provider,
4281
+ model: picked.model
4282
+ }),
4283
+ leading: [{
4284
+ id: "default",
4285
+ label: text.modelDefault,
4286
+ selected: follow
4287
+ }],
4288
+ onPickLeading: () => save(null),
4289
+ efforts,
4290
+ selectedEffort: current.reasoningEffort || "",
4291
+ onPickEffort: (effort) => save(follow ? null : {
4292
+ provider: current.provider,
4293
+ model: current.model,
4294
+ ...effort ? { reasoningEffort: effort } : {}
4295
+ }),
4296
+ modelLabel: text.model,
4297
+ modelValue: currentLabel,
4298
+ defaultEffortLabel: text.effortDefault
4299
+ }),
4300
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4301
+ className: "dsh-ui-hint",
4302
+ children: text.modelHint
4303
+ })
4304
+ ]
4305
+ }),
4306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4307
+ className: "ams-row",
4308
+ children: [
4309
+ syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4310
+ role: "status",
4311
+ className: "dsh-ui-hint",
4312
+ children: text.syncUnavailable
4313
+ }),
4314
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4315
+ role: "alert",
4316
+ className: "dsh-ui-error",
4317
+ children: text[error]
4318
+ }),
4319
+ note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4320
+ role: "status",
4321
+ className: "dsh-ui-notice",
4322
+ children: text.saved
4323
+ }),
4324
+ error || syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4325
+ variant: "ghost",
4326
+ size: "sm",
4327
+ disabled: locked,
4328
+ onClick: () => refreshRef.current(),
4329
+ children: text.refresh
4330
+ }) : null
4331
+ ]
4332
+ })
4333
+ ]
4334
+ });
4335
+ }
4336
+ const AUXILIARY_MODEL_WATCH_COORDINATOR = "dshAuxiliaryModelWatchCoordinator";
4337
+ function acquireWatchCoordinator(root) {
4338
+ const existing = root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false);
4339
+ if (existing) return existing;
4340
+ let unprovide;
4341
+ const coordinator = {
4342
+ tail: Promise.resolve(),
4343
+ active: 0,
4344
+ mount() {
4345
+ coordinator.active += 1;
4346
+ const previousTail = coordinator.tail;
4347
+ const lease = {
4348
+ closed: false,
4349
+ done() {
4350
+ if (lease.closed) return;
4351
+ lease.closed = true;
4352
+ coordinator.active -= 1;
4353
+ const owned = lease.fiber;
4354
+ lease.fiber = void 0;
4355
+ coordinator.tail = coordinator.tail.then(() => Promise.resolve(owned?.dispose()).catch(() => {}));
4356
+ coordinator.tail.then(() => {
4357
+ if (coordinator.active > 0) return;
4358
+ try {
4359
+ if (root.get(AUXILIARY_MODEL_WATCH_COORDINATOR, false) === coordinator) Promise.resolve(unprovide?.()).catch(() => {});
4360
+ } catch {}
4361
+ });
4362
+ },
4363
+ fiber: void 0
4364
+ };
4365
+ previousTail.then(() => {
4366
+ if (lease.closed) return;
4367
+ try {
4368
+ lease.fiber = root.inject(["remote"], (injected) => {
4369
+ const scope = injected;
4370
+ const mounting = scope.remote.$mount(modelSettingsChangesRemote(AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY));
4371
+ mounting.catch(() => {});
4372
+ scope.effect(() => async () => {
4373
+ await (await mounting.catch(() => void 0))?.();
4374
+ });
4375
+ });
4376
+ } catch {}
4377
+ });
4378
+ return lease;
4379
+ }
4380
+ };
4381
+ unprovide = root.provide(AUXILIARY_MODEL_WATCH_COORDINATOR, coordinator);
4382
+ return coordinator;
4383
+ }
4384
+ /**
4385
+ * Acquire the root-owned card service, creating it on first use. One counter
4386
+ * covers participants, the seat wait and the change-stream mount: the last
4387
+ * release removes the wait, then the watch contribution, then the provider
4388
+ * itself synchronously (Cordis deletes the impl before its async disposer
4389
+ * awaits), so a later acquire sees a clean get() miss and can re-establish.
4390
+ */
4391
+ function acquireAuxiliaryModelCard(root, ctx) {
4392
+ const existing = root.get(AUXILIARY_MODEL_CARD_SERVICE, false);
4393
+ if (existing) return existing;
4394
+ let users = 0;
4395
+ let styleElement;
4396
+ let stopItem;
4397
+ let unprovide;
4398
+ const watchLease = acquireWatchCoordinator(root).mount();
4399
+ const subscriber = createModelSettingsSubscriber(ctx.root, AUXILIARY_MODEL_PACKAGE, AUXILIARY_MODEL_CHANGES_IDENTITY, (ns) => ns === AUXILIARY_MODEL_NS);
4400
+ const render = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuxiliaryModelCard, {
4401
+ client: root,
4402
+ subscribe: subscriber.subscribe
4403
+ });
4404
+ const service = {
4405
+ retain() {
4406
+ users += 1;
4407
+ if (styleElement === void 0 && typeof document !== "undefined") {
4408
+ styleElement = document.createElement("style");
4409
+ styleElement.setAttribute("data-plugin", "dsh-auxiliary-model-shared");
4410
+ styleElement.textContent = auxiliaryModelCss;
4411
+ document.head.appendChild(styleElement);
4412
+ }
4413
+ if (!stopItem) stopItem = registerSessionSettingsItem(ctx.root, {
4414
+ id: AUXILIARY_MODEL_SEAT_ID,
4415
+ order: 55
4416
+ }, render);
4417
+ },
4418
+ release() {
4419
+ if (users <= 0) return;
4420
+ users -= 1;
4421
+ if (users > 0) return;
4422
+ stopItem?.();
4423
+ stopItem = void 0;
4424
+ styleElement?.remove();
4425
+ styleElement = void 0;
4426
+ watchLease.done();
4427
+ try {
4428
+ Promise.resolve(unprovide?.()).catch(() => {});
4429
+ } catch {}
4430
+ unprovide = void 0;
4431
+ }
4432
+ };
4433
+ unprovide = root.provide(AUXILIARY_MODEL_CARD_SERVICE, service);
4434
+ return service;
4435
+ }
4436
+ /**
4437
+ * Show the shared "default auxiliary model" card while the calling plugin is
4438
+ * enabled.
4439
+ *
4440
+ * `ctx` is the plugin's own client context: the card itself is retained on the
4441
+ * shared root context for as long as any participant lives, so an arbitrary
4442
+ * subset of enabled plugins yields the same single card. The mount's slot wait
4443
+ * and the change-stream contribution are owned by the root service — the first
4444
+ * participant's unload cannot take the card down. Returns a disposer; it may
4445
+ * be invoked by both ctx.effect and an explicit caller and releases at most
4446
+ * once, so a double call cannot unbalance the shared refcount.
4447
+ */
4448
+ function registerAuxiliaryModelSettings(ctx) {
4449
+ const root = ctx.root;
4450
+ const service = acquireAuxiliaryModelCard(root, ctx);
4451
+ try {
4452
+ service.retain();
4453
+ } catch (error) {
4454
+ service.release();
4455
+ throw error;
4456
+ }
4457
+ let disposed = false;
4458
+ return () => {
4459
+ if (disposed) return;
4460
+ disposed = true;
4461
+ service.release();
4462
+ };
4463
+ }
4464
+ //#endregion
3526
4465
  //#region src/presence-panel.tsx
3527
4466
  /**
3528
4467
  * Grey inline-completion for an empty composer, IDE style: the shipped
@@ -3767,8 +4706,12 @@ function toDraft(values) {
3767
4706
  compactMinutes: String(compactEnabled ? values.compactMinutes : 60),
3768
4707
  continueText: values.continueText,
3769
4708
  continueMode: values.continueMode,
4709
+ autoContinueMode: values.autoContinueMode,
4710
+ autoContinueMax: String(values.autoContinueMax),
3770
4711
  model: values.model === null ? "" : modelRouteKey(values.model.provider, values.model.model),
3771
- ghostModel: values.ghostModel === null ? "" : modelRouteKey(values.ghostModel.provider, values.ghostModel.model)
4712
+ ghostModel: values.ghostModel === null ? "" : modelRouteKey(values.ghostModel.provider, values.ghostModel.model),
4713
+ judgeModel: values.judgeModel === null ? "" : modelRouteKey(values.judgeModel.provider, values.judgeModel.model),
4714
+ handoffModel: values.handoffModel === null ? "" : modelRouteKey(values.handoffModel.provider, values.handoffModel.model)
3772
4715
  };
3773
4716
  }
3774
4717
  /** Enabled thresholds are whole minutes of at least 1. 0 stays the stored "off" value and is not a valid enabled draft. */
@@ -3777,8 +4720,14 @@ function minutesOf(text) {
3777
4720
  const value = Number.parseInt(text.trim(), 10);
3778
4721
  return Number.isSafeInteger(value) && value >= 1 ? value : void 0;
3779
4722
  }
4723
+ /** Automatic continuations per human message: a whole number from 1 to 10. */
4724
+ function autoMaxOf(text) {
4725
+ if (!/^\d+$/.test(text.trim())) return void 0;
4726
+ const value = Number.parseInt(text.trim(), 10);
4727
+ return Number.isSafeInteger(value) && value >= 1 && value <= 10 ? value : void 0;
4728
+ }
3780
4729
  function sameDraft(left, right) {
3781
- return left.recapEnabled === right.recapEnabled && left.recapMinutes === right.recapMinutes && left.compactEnabled === right.compactEnabled && left.compactMinutes === right.compactMinutes && left.continueText === right.continueText && left.continueMode === right.continueMode && left.model === right.model && left.ghostModel === right.ghostModel;
4730
+ return left.recapEnabled === right.recapEnabled && left.recapMinutes === right.recapMinutes && left.compactEnabled === right.compactEnabled && left.compactMinutes === right.compactMinutes && left.continueText === right.continueText && left.continueMode === right.continueMode && left.autoContinueMode === right.autoContinueMode && left.autoContinueMax === right.autoContinueMax && left.model === right.model && left.ghostModel === right.ghostModel && left.judgeModel === right.judgeModel && left.handoffModel === right.handoffModel;
3782
4731
  }
3783
4732
  const missingCatalog = async () => {
3784
4733
  throw new Error("Model catalog service unavailable");
@@ -3790,6 +4739,8 @@ function SessionManagerSettings(props) {
3790
4739
  const catalog = useModelCatalog(props.modelCatalog ?? missingCatalog, { subscribe: props.subscribeCatalog });
3791
4740
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
3792
4741
  const [ghostMenuOpen, setGhostMenuOpen] = (0, react.useState)(false);
4742
+ const [judgeMenuOpen, setJudgeMenuOpen] = (0, react.useState)(false);
4743
+ const [handoffMenuOpen, setHandoffMenuOpen] = (0, react.useState)(false);
3793
4744
  const [externalChange, setExternalChange] = (0, react.useState)(false);
3794
4745
  const dirty = (0, react.useRef)(false);
3795
4746
  const baseline = (0, react.useRef)();
@@ -3832,325 +4783,539 @@ function SessionManagerSettings(props) {
3832
4783
  });
3833
4784
  const selectedRoute = parseModelRouteKey(draft.model);
3834
4785
  const ghostRoute = parseModelRouteKey(draft.ghostModel);
3835
- const selectableChoices = parseModelMenuChoices(catalog.catalog, selectedRoute ?? ghostRoute);
3836
- const selectedLabel = selectableChoices.find((choice) => modelRouteKey(choice.provider, choice.model) === draft.model)?.label ?? copy("modelFollowOption");
3837
- const ghostLabel = selectableChoices.find((choice) => modelRouteKey(choice.provider, choice.model) === draft.ghostModel)?.label ?? copy("modelFollowOption");
4786
+ const judgeRoute = parseModelRouteKey(draft.judgeModel);
4787
+ const handoffRoute = parseModelRouteKey(draft.handoffModel);
4788
+ const selectableChoices = parseModelMenuChoices(catalog.catalog);
4789
+ for (const bound of [
4790
+ selectedRoute,
4791
+ ghostRoute,
4792
+ judgeRoute,
4793
+ handoffRoute
4794
+ ]) if (bound && !selectableChoices.some((choice) => choice.provider === bound.provider && choice.model === bound.model)) selectableChoices.push({
4795
+ provider: bound.provider,
4796
+ model: bound.model,
4797
+ label: `${bound.provider} / ${bound.model}`,
4798
+ efforts: []
4799
+ });
4800
+ const routeLabel = (key) => selectableChoices.find((choice) => modelRouteKey(choice.provider, choice.model) === key)?.label ?? copy("modelFollowOption");
4801
+ const selectedLabel = routeLabel(draft.model);
4802
+ const ghostLabel = routeLabel(draft.ghostModel);
4803
+ const judgeLabel = routeLabel(draft.judgeModel);
4804
+ const handoffLabel = routeLabel(draft.handoffModel);
3838
4805
  const recapMinutes = draft.recapEnabled ? minutesOf(draft.recapMinutes) : 0;
3839
4806
  const compactMinutes = draft.compactEnabled ? minutesOf(draft.compactMinutes) : 0;
4807
+ const autoContinueMax = autoMaxOf(draft.autoContinueMax);
3840
4808
  const recapInvalid = recapMinutes === void 0;
3841
4809
  const compactInvalid = compactMinutes === void 0;
3842
- const invalid = recapInvalid || compactInvalid;
4810
+ const autoMaxInvalid = autoContinueMax === void 0;
4811
+ const invalid = recapInvalid || compactInvalid || autoMaxInvalid;
3843
4812
  const fixedPanelId = `${tabsId}-fixed-panel`;
3844
4813
  const autoPanelId = `${tabsId}-auto-panel`;
3845
4814
  const save = () => {
3846
4815
  if (invalid) return;
3847
- const route = parseModelRouteKey(draft.model);
3848
- const ghost = parseModelRouteKey(draft.ghostModel);
4816
+ const routePatch = (key) => {
4817
+ if (draft[key] === baseline.current?.[key]) return {};
4818
+ const parsed = parseModelRouteKey(draft[key]);
4819
+ return { [key]: parsed === void 0 ? null : {
4820
+ provider: parsed.provider,
4821
+ model: parsed.model
4822
+ } };
4823
+ };
3849
4824
  setExternalChange(false);
3850
4825
  props.model.save({
3851
4826
  recapMinutes,
3852
4827
  compactMinutes,
3853
4828
  continueText: draft.continueText,
3854
4829
  continueMode: draft.continueMode,
3855
- ...draft.model === baseline.current?.model ? {} : { model: route === void 0 ? null : {
3856
- provider: route.provider,
3857
- model: route.model
3858
- } },
3859
- ...draft.ghostModel === baseline.current?.ghostModel ? {} : { ghostModel: ghost === void 0 ? null : {
3860
- provider: ghost.provider,
3861
- model: ghost.model
3862
- } }
4830
+ autoContinueMode: draft.autoContinueMode,
4831
+ autoContinueMax,
4832
+ ...routePatch("model"),
4833
+ ...routePatch("ghostModel"),
4834
+ ...routePatch("judgeModel"),
4835
+ ...routePatch("handoffModel")
3863
4836
  });
3864
4837
  };
3865
4838
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3866
4839
  className: `${FREE_CHAT_ROOT} dsh-ui-page`,
3867
4840
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3868
4841
  className: "dsm-settings",
3869
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3870
- className: "dsm-setting",
3871
- "aria-labelledby": `${tabsId}-ghost-heading`,
3872
- children: [
3873
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
4842
+ children: [
4843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4844
+ className: "dsm-setting",
4845
+ "aria-labelledby": `${tabsId}-ghost-heading`,
4846
+ children: [
4847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
4848
+ className: "dsm-setting-header",
4849
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4850
+ id: `${tabsId}-ghost-heading`,
4851
+ className: "dsh-ui-title",
4852
+ children: copy("settingsMode")
4853
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4854
+ className: "dsh-ui-hint",
4855
+ children: copy("settingsModeHint")
4856
+ })]
4857
+ }),
4858
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4859
+ className: "dsm-ghost-controls",
4860
+ children: [
4861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4862
+ className: "dsh-ui-field",
4863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4864
+ className: "dsh-ui-label",
4865
+ children: copy("continueSourceLabel")
4866
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
4867
+ label: copy("continueSourceLabel"),
4868
+ items: [{
4869
+ id: `${tabsId}-fixed`,
4870
+ panelId: fixedPanelId,
4871
+ value: "fixed",
4872
+ label: copy("modeFixed")
4873
+ }, {
4874
+ id: `${tabsId}-auto`,
4875
+ panelId: autoPanelId,
4876
+ value: "auto",
4877
+ label: copy("modeAuto")
4878
+ }],
4879
+ value: draft.continueMode,
4880
+ onChange: (value) => edit({
4881
+ ...draft,
4882
+ continueMode: value
4883
+ })
4884
+ })]
4885
+ }),
4886
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4887
+ role: "tabpanel",
4888
+ id: fixedPanelId,
4889
+ "aria-labelledby": `${tabsId}-fixed`,
4890
+ hidden: draft.continueMode !== "fixed",
4891
+ children: draft.continueMode === "fixed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4892
+ className: "dsh-ui-field",
4893
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4894
+ className: "dsh-ui-label",
4895
+ children: copy("continueTextLabel")
4896
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
4897
+ className: "dsm-ghost-input",
4898
+ value: draft.continueText,
4899
+ placeholder: copy("continueTextPlaceholder"),
4900
+ onChange: (event) => edit({
4901
+ ...draft,
4902
+ continueText: event.target.value
4903
+ })
4904
+ })]
4905
+ }) : null
4906
+ }),
4907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4908
+ role: "tabpanel",
4909
+ id: autoPanelId,
4910
+ "aria-labelledby": `${tabsId}-auto`,
4911
+ hidden: draft.continueMode !== "auto",
4912
+ children: draft.continueMode === "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4913
+ className: "dsh-ui-field dsm-model-field",
4914
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4915
+ className: "dsh-ui-label",
4916
+ children: copy("ghostModelLabel")
4917
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
4918
+ locale: props.locale,
4919
+ open: ghostMenuOpen,
4920
+ onOpenChange: setGhostMenuOpen,
4921
+ listClassName: FREE_CHAT_ROOT,
4922
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4923
+ type: "button",
4924
+ className: "dsh-model-menu-trigger",
4925
+ "aria-label": `${copy("ghostModelLabel")}: ${ghostLabel}`,
4926
+ "aria-haspopup": "menu",
4927
+ "aria-expanded": ghostMenuOpen,
4928
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4929
+ className: "dsh-model-menu-triggerLabel",
4930
+ children: ghostLabel
4931
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${ghostMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
4932
+ }),
4933
+ choices: selectableChoices,
4934
+ selected: ghostRoute,
4935
+ onPick: (route) => edit({
4936
+ ...draft,
4937
+ ghostModel: modelRouteKey(route.provider, route.model)
4938
+ }),
4939
+ leading: [{
4940
+ id: "follow",
4941
+ label: copy("modelFollowOption"),
4942
+ selected: !ghostRoute
4943
+ }],
4944
+ onPickLeading: () => edit({
4945
+ ...draft,
4946
+ ghostModel: ""
4947
+ }),
4948
+ modelValue: ghostLabel
4949
+ })]
4950
+ }) : null
4951
+ })
4952
+ ]
4953
+ }),
4954
+ draft.continueMode === "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4955
+ className: "dsh-ui-hint",
4956
+ children: copy("ghostModelHint")
4957
+ }) : null
4958
+ ]
4959
+ }),
4960
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4961
+ className: "dsm-setting",
4962
+ "aria-labelledby": `${tabsId}-auto-continue-heading`,
4963
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
3874
4964
  className: "dsm-setting-header",
3875
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3876
- id: `${tabsId}-ghost-heading`,
4965
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4966
+ id: `${tabsId}-auto-continue-heading`,
3877
4967
  className: "dsh-ui-title",
3878
- children: copy("settingsMode")
3879
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3880
- className: "dsh-ui-hint",
3881
- children: copy("settingsModeHint")
3882
- })]
3883
- }),
3884
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3885
- className: "dsm-ghost-controls",
4968
+ children: copy("autoContinueGroup")
4969
+ })
4970
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4971
+ className: "dsm-feature-options",
3886
4972
  children: [
3887
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3888
4974
  className: "dsh-ui-field",
3889
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3890
- className: "dsh-ui-label",
3891
- children: copy("continueSourceLabel")
3892
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedTabs, {
3893
- label: copy("continueSourceLabel"),
3894
- items: [{
3895
- id: `${tabsId}-fixed`,
3896
- panelId: fixedPanelId,
3897
- value: "fixed",
3898
- label: copy("modeFixed")
3899
- }, {
3900
- id: `${tabsId}-auto`,
3901
- panelId: autoPanelId,
3902
- value: "auto",
3903
- label: copy("modeAuto")
3904
- }],
3905
- value: draft.continueMode,
3906
- onChange: (value) => edit({
3907
- ...draft,
3908
- continueMode: value
4975
+ children: [
4976
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4977
+ className: "dsh-ui-label",
4978
+ children: copy("autoContinueDefaultLabel")
4979
+ }),
4980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
4981
+ className: "dsh-ui-select",
4982
+ value: draft.autoContinueMode,
4983
+ onChange: (event) => edit({
4984
+ ...draft,
4985
+ autoContinueMode: event.target.value
4986
+ }),
4987
+ children: [
4988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4989
+ value: "off",
4990
+ children: copy("autoContinueOff")
4991
+ }),
4992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4993
+ value: "cautious",
4994
+ children: copy("autoContinueCautious")
4995
+ }),
4996
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4997
+ value: "bold",
4998
+ children: copy("autoContinueBold")
4999
+ })
5000
+ ]
5001
+ }),
5002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5003
+ className: "dsh-ui-hint",
5004
+ children: copy("autoContinueDefaultHint")
3909
5005
  })
3910
- })]
5006
+ ]
3911
5007
  }),
3912
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3913
- role: "tabpanel",
3914
- id: fixedPanelId,
3915
- "aria-labelledby": `${tabsId}-fixed`,
3916
- hidden: draft.continueMode !== "fixed",
3917
- children: draft.continueMode === "fixed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
5009
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3918
5010
  className: "dsh-ui-field",
3919
5011
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3920
5012
  className: "dsh-ui-label",
3921
- children: copy("continueTextLabel")
3922
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
3923
- className: "dsm-ghost-input",
3924
- value: draft.continueText,
3925
- placeholder: copy("continueTextPlaceholder"),
3926
- onChange: (event) => edit({
3927
- ...draft,
3928
- continueText: event.target.value
5013
+ children: copy("autoContinueMaxLabel")
5014
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5015
+ className: "dsm-minutes-control",
5016
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
5017
+ className: "dsm-minutes",
5018
+ value: draft.autoContinueMax,
5019
+ inputMode: "numeric",
5020
+ "aria-invalid": autoMaxInvalid || void 0,
5021
+ "aria-describedby": autoMaxInvalid ? `${tabsId}-auto-max-error` : void 0,
5022
+ onChange: (event) => edit({
5023
+ ...draft,
5024
+ autoContinueMax: event.target.value
5025
+ })
3929
5026
  })
3930
5027
  })]
5028
+ }),
5029
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5030
+ className: "dsh-ui-hint",
5031
+ children: copy("autoContinueMaxHint")
5032
+ }),
5033
+ autoMaxInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5034
+ id: `${tabsId}-auto-max-error`,
5035
+ className: "dsh-ui-error",
5036
+ role: "alert",
5037
+ children: copy("autoContinueMaxInvalid")
3931
5038
  }) : null
3932
- }),
3933
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3934
- role: "tabpanel",
3935
- id: autoPanelId,
3936
- "aria-labelledby": `${tabsId}-auto`,
3937
- hidden: draft.continueMode !== "auto",
3938
- children: draft.continueMode === "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3939
- className: "dsh-ui-field dsm-model-field",
3940
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5039
+ ] }),
5040
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5041
+ className: "dsh-ui-field dsm-model-field",
5042
+ children: [
5043
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3941
5044
  className: "dsh-ui-label",
3942
- children: copy("ghostModelLabel")
3943
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
5045
+ children: copy("judgeModelLabel")
5046
+ }),
5047
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
3944
5048
  locale: props.locale,
3945
- open: ghostMenuOpen,
3946
- onOpenChange: setGhostMenuOpen,
5049
+ open: judgeMenuOpen,
5050
+ onOpenChange: setJudgeMenuOpen,
3947
5051
  listClassName: FREE_CHAT_ROOT,
3948
5052
  anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3949
5053
  type: "button",
3950
5054
  className: "dsh-model-menu-trigger",
3951
- "aria-label": `${copy("ghostModelLabel")}: ${ghostLabel}`,
5055
+ "aria-label": `${copy("judgeModelLabel")}: ${judgeLabel}`,
3952
5056
  "aria-haspopup": "menu",
3953
- "aria-expanded": ghostMenuOpen,
5057
+ "aria-expanded": judgeMenuOpen,
3954
5058
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3955
5059
  className: "dsh-model-menu-triggerLabel",
3956
- children: ghostLabel
3957
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${ghostMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
5060
+ children: judgeLabel
5061
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${judgeMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
3958
5062
  }),
3959
5063
  choices: selectableChoices,
3960
- selected: ghostRoute,
5064
+ selected: judgeRoute,
3961
5065
  onPick: (route) => edit({
3962
5066
  ...draft,
3963
- ghostModel: modelRouteKey(route.provider, route.model)
5067
+ judgeModel: modelRouteKey(route.provider, route.model)
3964
5068
  }),
3965
5069
  leading: [{
3966
5070
  id: "follow",
3967
5071
  label: copy("modelFollowOption"),
3968
- selected: !ghostRoute
5072
+ selected: !judgeRoute
3969
5073
  }],
3970
5074
  onPickLeading: () => edit({
3971
5075
  ...draft,
3972
- ghostModel: ""
5076
+ judgeModel: ""
3973
5077
  }),
3974
- modelValue: ghostLabel
3975
- })]
3976
- }) : null
5078
+ modelValue: judgeLabel
5079
+ }),
5080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5081
+ className: "dsh-ui-hint",
5082
+ children: copy("judgeModelHint")
5083
+ })
5084
+ ]
3977
5085
  })
3978
5086
  ]
3979
- }),
3980
- draft.continueMode === "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3981
- className: "dsh-ui-hint",
3982
- children: copy("ghostModelHint")
3983
- }) : null
3984
- ]
3985
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3986
- className: "dsm-setting",
3987
- "aria-labelledby": `${tabsId}-return-heading`,
3988
- children: [
3989
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
5087
+ })]
5088
+ }),
5089
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5090
+ className: "dsm-setting",
5091
+ "aria-labelledby": `${tabsId}-handoff-heading`,
5092
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
3990
5093
  className: "dsm-setting-header",
3991
5094
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3992
- id: `${tabsId}-return-heading`,
5095
+ id: `${tabsId}-handoff-heading`,
3993
5096
  className: "dsh-ui-title",
3994
- children: copy("welcomeBackGroup")
5097
+ children: copy("handoffGroup")
3995
5098
  })
3996
- }),
3997
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3998
- className: "dsm-feature",
3999
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4000
- className: "dsh-ui-toggle-row",
4001
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4002
- className: "dsh-ui-toggle-text",
4003
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4004
- className: "dsh-ui-toggle-label",
4005
- children: copy("recapEnabledLabel")
4006
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5099
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5100
+ className: "dsm-feature-options",
5101
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5102
+ className: "dsh-ui-field dsm-model-field",
5103
+ children: [
5104
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5105
+ className: "dsh-ui-label",
5106
+ children: copy("handoffModelLabel")
5107
+ }),
5108
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
5109
+ locale: props.locale,
5110
+ open: handoffMenuOpen,
5111
+ onOpenChange: setHandoffMenuOpen,
5112
+ listClassName: FREE_CHAT_ROOT,
5113
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5114
+ type: "button",
5115
+ className: "dsh-model-menu-trigger",
5116
+ "aria-label": `${copy("handoffModelLabel")}: ${handoffLabel}`,
5117
+ "aria-haspopup": "menu",
5118
+ "aria-expanded": handoffMenuOpen,
5119
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5120
+ className: "dsh-model-menu-triggerLabel",
5121
+ children: handoffLabel
5122
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${handoffMenuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
5123
+ }),
5124
+ choices: selectableChoices,
5125
+ selected: handoffRoute,
5126
+ onPick: (route) => edit({
5127
+ ...draft,
5128
+ handoffModel: modelRouteKey(route.provider, route.model)
5129
+ }),
5130
+ leading: [{
5131
+ id: "follow",
5132
+ label: copy("modelFollowOption"),
5133
+ selected: !handoffRoute
5134
+ }],
5135
+ onPickLeading: () => edit({
5136
+ ...draft,
5137
+ handoffModel: ""
5138
+ }),
5139
+ modelValue: handoffLabel
5140
+ }),
5141
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4007
5142
  className: "dsh-ui-hint",
4008
- children: copy("recapEnabledHint")
4009
- })]
4010
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
4011
- label: copy("recapEnabledLabel"),
4012
- checked: draft.recapEnabled,
4013
- onChange: (next) => edit({
4014
- ...draft,
4015
- recapEnabled: next
5143
+ children: copy("handoffModelHint")
4016
5144
  })
5145
+ ]
5146
+ })
5147
+ })]
5148
+ }),
5149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5150
+ className: "dsm-setting",
5151
+ "aria-labelledby": `${tabsId}-return-heading`,
5152
+ children: [
5153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
5154
+ className: "dsm-setting-header",
5155
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
5156
+ id: `${tabsId}-return-heading`,
5157
+ className: "dsh-ui-title",
5158
+ children: copy("welcomeBackGroup")
5159
+ })
5160
+ }),
5161
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5162
+ className: "dsm-feature",
5163
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5164
+ className: "dsh-ui-toggle-row",
5165
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5166
+ className: "dsh-ui-toggle-text",
5167
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5168
+ className: "dsh-ui-toggle-label",
5169
+ children: copy("recapEnabledLabel")
5170
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5171
+ className: "dsh-ui-hint",
5172
+ children: copy("recapEnabledHint")
5173
+ })]
5174
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
5175
+ label: copy("recapEnabledLabel"),
5176
+ checked: draft.recapEnabled,
5177
+ onChange: (next) => edit({
5178
+ ...draft,
5179
+ recapEnabled: next
5180
+ })
5181
+ })]
5182
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5183
+ className: "dsm-feature-options",
5184
+ "data-disabled": draft.recapEnabled ? void 0 : "true",
5185
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5186
+ className: "dsh-ui-fields-grid",
5187
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5188
+ className: "dsh-ui-field",
5189
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5190
+ className: "dsh-ui-label",
5191
+ children: copy("recapMinutesLabel")
5192
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5193
+ className: "dsm-minutes-control",
5194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
5195
+ className: "dsm-minutes",
5196
+ value: draft.recapMinutes,
5197
+ inputMode: "numeric",
5198
+ disabled: !draft.recapEnabled,
5199
+ "aria-invalid": recapInvalid || void 0,
5200
+ "aria-describedby": recapInvalid ? `${tabsId}-recap-error` : void 0,
5201
+ onChange: (event) => edit({
5202
+ ...draft,
5203
+ recapMinutes: event.target.value
5204
+ })
5205
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5206
+ className: "dsh-ui-meta",
5207
+ children: copy("minutesUnit")
5208
+ })]
5209
+ })]
5210
+ }), recapInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5211
+ id: `${tabsId}-recap-error`,
5212
+ className: "dsh-ui-error",
5213
+ role: "alert",
5214
+ children: copy("minutesInvalid")
5215
+ }) : null] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5216
+ className: "dsh-ui-field dsm-model-field",
5217
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5218
+ className: "dsh-ui-label",
5219
+ children: copy("modelLabel")
5220
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
5221
+ locale: props.locale,
5222
+ open: menuOpen,
5223
+ onOpenChange: setMenuOpen,
5224
+ listClassName: FREE_CHAT_ROOT,
5225
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5226
+ type: "button",
5227
+ className: "dsh-model-menu-trigger",
5228
+ disabled: !draft.recapEnabled,
5229
+ "aria-label": `${copy("modelLabel")}: ${selectedLabel}`,
5230
+ "aria-haspopup": "menu",
5231
+ "aria-expanded": menuOpen,
5232
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5233
+ className: "dsh-model-menu-triggerLabel",
5234
+ children: selectedLabel
5235
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
5236
+ }),
5237
+ choices: selectableChoices,
5238
+ selected: selectedRoute,
5239
+ onPick: (route) => edit({
5240
+ ...draft,
5241
+ model: modelRouteKey(route.provider, route.model)
5242
+ }),
5243
+ leading: [{
5244
+ id: "follow",
5245
+ label: copy("modelFollowOption"),
5246
+ selected: !selectedRoute
5247
+ }],
5248
+ onPickLeading: () => edit({
5249
+ ...draft,
5250
+ model: ""
5251
+ }),
5252
+ modelValue: selectedLabel
5253
+ })]
5254
+ })]
5255
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
5256
+ state: catalog,
5257
+ locale: props.locale
5258
+ })]
4017
5259
  })]
4018
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4019
- className: "dsm-feature-options",
4020
- "data-disabled": draft.recapEnabled ? void 0 : "true",
5260
+ }),
5261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5262
+ className: "dsm-feature",
4021
5263
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4022
- className: "dsh-ui-fields-grid",
4023
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5264
+ className: "dsh-ui-toggle-row",
5265
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5266
+ className: "dsh-ui-toggle-text",
5267
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5268
+ className: "dsh-ui-toggle-label",
5269
+ children: copy("compactEnabledLabel")
5270
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5271
+ className: "dsh-ui-hint",
5272
+ children: copy("compactEnabledHint")
5273
+ })]
5274
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
5275
+ label: copy("compactEnabledLabel"),
5276
+ checked: draft.compactEnabled,
5277
+ onChange: (next) => edit({
5278
+ ...draft,
5279
+ compactEnabled: next
5280
+ })
5281
+ })]
5282
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5283
+ className: "dsm-feature-options dsm-compact-options",
5284
+ "data-disabled": draft.compactEnabled ? void 0 : "true",
5285
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4024
5286
  className: "dsh-ui-field",
4025
5287
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4026
5288
  className: "dsh-ui-label",
4027
- children: copy("recapMinutesLabel")
5289
+ children: copy("compactMinutesLabel")
4028
5290
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4029
5291
  className: "dsm-minutes-control",
4030
5292
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
4031
5293
  className: "dsm-minutes",
4032
- value: draft.recapMinutes,
5294
+ value: draft.compactMinutes,
4033
5295
  inputMode: "numeric",
4034
- disabled: !draft.recapEnabled,
4035
- "aria-invalid": recapInvalid || void 0,
4036
- "aria-describedby": recapInvalid ? `${tabsId}-recap-error` : void 0,
5296
+ disabled: !draft.compactEnabled,
5297
+ "aria-invalid": compactInvalid || void 0,
5298
+ "aria-describedby": compactInvalid ? `${tabsId}-compact-error` : void 0,
4037
5299
  onChange: (event) => edit({
4038
5300
  ...draft,
4039
- recapMinutes: event.target.value
5301
+ compactMinutes: event.target.value
4040
5302
  })
4041
5303
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4042
5304
  className: "dsh-ui-meta",
4043
5305
  children: copy("minutesUnit")
4044
5306
  })]
4045
5307
  })]
4046
- }), recapInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4047
- id: `${tabsId}-recap-error`,
5308
+ }), compactInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5309
+ id: `${tabsId}-compact-error`,
4048
5310
  className: "dsh-ui-error",
4049
5311
  role: "alert",
4050
5312
  children: copy("minutesInvalid")
4051
- }) : null] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4052
- className: "dsh-ui-field dsm-model-field",
4053
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4054
- className: "dsh-ui-label",
4055
- children: copy("modelLabel")
4056
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMenu, {
4057
- locale: props.locale,
4058
- open: menuOpen,
4059
- onOpenChange: setMenuOpen,
4060
- listClassName: FREE_CHAT_ROOT,
4061
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4062
- type: "button",
4063
- className: "dsh-model-menu-trigger",
4064
- disabled: !draft.recapEnabled,
4065
- "aria-label": `${copy("modelLabel")}: ${selectedLabel}`,
4066
- "aria-haspopup": "menu",
4067
- "aria-expanded": menuOpen,
4068
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4069
- className: "dsh-model-menu-triggerLabel",
4070
- children: selectedLabel
4071
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { className: `dsh-model-menu-chevron${menuOpen ? " dsh-model-menu-chevronOpen" : ""}` })]
4072
- }),
4073
- choices: selectableChoices,
4074
- selected: selectedRoute,
4075
- onPick: (route) => edit({
4076
- ...draft,
4077
- model: modelRouteKey(route.provider, route.model)
4078
- }),
4079
- leading: [{
4080
- id: "follow",
4081
- label: copy("modelFollowOption"),
4082
- selected: !selectedRoute
4083
- }],
4084
- onPickLeading: () => edit({
4085
- ...draft,
4086
- model: ""
4087
- }),
4088
- modelValue: selectedLabel
4089
- })]
4090
- })]
4091
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogStatus, {
4092
- state: catalog,
4093
- locale: props.locale
4094
- })]
4095
- })]
4096
- }),
4097
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4098
- className: "dsm-feature",
4099
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4100
- className: "dsh-ui-toggle-row",
4101
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4102
- className: "dsh-ui-toggle-text",
4103
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4104
- className: "dsh-ui-toggle-label",
4105
- children: copy("compactEnabledLabel")
4106
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4107
- className: "dsh-ui-hint",
4108
- children: copy("compactEnabledHint")
4109
- })]
4110
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
4111
- label: copy("compactEnabledLabel"),
4112
- checked: draft.compactEnabled,
4113
- onChange: (next) => edit({
4114
- ...draft,
4115
- compactEnabled: next
4116
- })
5313
+ }) : null] })
4117
5314
  })]
4118
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4119
- className: "dsm-feature-options dsm-compact-options",
4120
- "data-disabled": draft.compactEnabled ? void 0 : "true",
4121
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4122
- className: "dsh-ui-field",
4123
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4124
- className: "dsh-ui-label",
4125
- children: copy("compactMinutesLabel")
4126
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4127
- className: "dsm-minutes-control",
4128
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
4129
- className: "dsm-minutes",
4130
- value: draft.compactMinutes,
4131
- inputMode: "numeric",
4132
- disabled: !draft.compactEnabled,
4133
- "aria-invalid": compactInvalid || void 0,
4134
- "aria-describedby": compactInvalid ? `${tabsId}-compact-error` : void 0,
4135
- onChange: (event) => edit({
4136
- ...draft,
4137
- compactMinutes: event.target.value
4138
- })
4139
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4140
- className: "dsh-ui-meta",
4141
- children: copy("minutesUnit")
4142
- })]
4143
- })]
4144
- }), compactInvalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4145
- id: `${tabsId}-compact-error`,
4146
- className: "dsh-ui-error",
4147
- role: "alert",
4148
- children: copy("minutesInvalid")
4149
- }) : null] })
4150
- })]
4151
- })
4152
- ]
4153
- })]
5315
+ })
5316
+ ]
5317
+ })
5318
+ ]
4154
5319
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4155
5320
  className: "dsh-ui-form-footer dsm-settings-footer",
4156
5321
  children: [
@@ -5050,6 +6215,21 @@ function apply(ctx) {
5050
6215
  ...props
5051
6216
  }))));
5052
6217
  stops.push(installTemporaryWorkspacePicker(client.slots, temporary, temporaryHost.createWorkspace, client.locale));
6218
+ stops.push(client.slots.inject("conversation.hero.options", () => client.slots.register({
6219
+ name: "conversation.hero.options",
6220
+ id: "auto-continue",
6221
+ order: 30,
6222
+ inject: () => ({
6223
+ call: (endpoint, payload) => client.connection?.rpc?.call(TEMP_WORKSPACE_CHANNEL, endpoint, payload),
6224
+ subscribeGeneration: (listener) => client.connection?.generation?.subscribe(listener)
6225
+ })
6226
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoContinueOption, {
6227
+ sessionId: props.sessionId,
6228
+ call: props.call,
6229
+ subscribeGeneration: props.subscribeGeneration,
6230
+ useSession: props.useSession,
6231
+ locale: useLiveLocale(client.locale)
6232
+ }))));
5053
6233
  stops.push(client.slots.inject(USER_ACTIONS_SLOT, () => client.slots.register({
5054
6234
  name: USER_ACTIONS_SLOT,
5055
6235
  id: USER_ACTIONS_ID,
@@ -5101,15 +6281,16 @@ function apply(ctx) {
5101
6281
  locale: useLiveLocale(client.locale),
5102
6282
  model: welcomeBack
5103
6283
  }))));
5104
- stops.push(client.slots.inject("plugins.bundle.config", () => client.slots.register({
5105
- name: "plugins.bundle.config",
5106
- key: "@klarkxy/dsh-session-manager"
5107
- }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionManagerSettings, {
6284
+ stops.push(registerSessionSettingsItem(ctx, {
6285
+ id: SESSION_SETTINGS_ITEM_ID,
6286
+ order: 65
6287
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionManagerSettingsItem, {
5108
6288
  model: presenceConfig,
5109
6289
  locale: useLiveLocale(client.locale),
5110
6290
  modelCatalog,
5111
- subscribeCatalog
5112
- }))));
6291
+ subscribeCatalog,
6292
+ subscribeSettings
6293
+ })));
5113
6294
  stops.push(client.slots.inject("shell.overlay", () => client.slots.register({
5114
6295
  name: "shell.overlay",
5115
6296
  id: DELETE_DIALOG_ID,
@@ -5132,6 +6313,7 @@ function apply(ctx) {
5132
6313
  })
5133
6314
  ] });
5134
6315
  })));
6316
+ stops.push(registerAuxiliaryModelSettings(ctx));
5135
6317
  const findCurrent = () => Object.values(client.sessions?.list.getSnapshot().byId ?? {}).find((row) => (row?.retainedBy?.mainView ?? 0) > 0)?.id ?? "";
5136
6318
  let current = findCurrent();
5137
6319
  const onReturn = () => {
@@ -5226,10 +6408,49 @@ function DeleteMenuItem(props) {
5226
6408
  children: uiText(locale, "deleteChat")
5227
6409
  });
5228
6410
  }
6411
+ /**
6412
+ * Session Management item: a named native details group. The body mounts on
6413
+ * the first expand only — the settings page must not fire every plugin's
6414
+ * config and model reads — and stays mounted afterwards so collapsing never
6415
+ * drops an unsaved draft or the settings-change subscription.
6416
+ */
6417
+ function SessionManagerSettingsItem(props) {
6418
+ const [visited, setVisited] = (0, react.useState)(false);
6419
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6420
+ className: `${FREE_CHAT_ROOT} dsm-item`,
6421
+ "data-testid": "session-manager-settings-item",
6422
+ onToggle: (event) => {
6423
+ if (event.currentTarget.open) setVisited(true);
6424
+ },
6425
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
6426
+ className: "dsm-item-summary",
6427
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6428
+ className: "dsm-item-title",
6429
+ children: uiText(props.locale, "settingsGroupTitle")
6430
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6431
+ className: "dsm-item-desc",
6432
+ children: uiText(props.locale, "settingsGroupHint")
6433
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
6434
+ className: "dsm-item-chevron",
6435
+ "aria-hidden": "true"
6436
+ })]
6437
+ }), visited ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6438
+ className: "dsm-item-body",
6439
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionManagerSettings, {
6440
+ model: props.model,
6441
+ locale: props.locale,
6442
+ modelCatalog: props.modelCatalog,
6443
+ subscribeCatalog: props.subscribeCatalog,
6444
+ subscribeSettings: props.subscribeSettings
6445
+ })
6446
+ }) : null]
6447
+ });
6448
+ }
5229
6449
  function useLiveLocale(locale) {
5230
6450
  return copyLocale((0, react.useSyncExternalStore)((listener) => locale?.subscribe(listener) ?? (() => {}), () => locale?.getSnapshot().active ?? "en", () => locale?.getSnapshot().active ?? "en"));
5231
6451
  }
5232
6452
  //#endregion
6453
+ exports.SessionManagerSettingsItem = SessionManagerSettingsItem;
5233
6454
  exports.apply = apply;
5234
6455
  exports.inject = inject;
5235
6456
  exports.name = name;