dsh-smooth-stream 0.1.0 → 0.3.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/lib/client.js CHANGED
@@ -8,41 +8,42 @@ window.__ModuleLoader__.load({
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
9
  let _deepseek_ai_dsh_client_ui_attachment = require("@deepseek-ai/dsh-client-ui-attachment");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
12
  //#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/TypewriterAssistantNodeView.module.css.mjs
12
- const css = ".I17U7q_root{min-width:0;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:16px;line-height:28px;display:flex}.I17U7q_body{flex-direction:column;gap:16px;min-width:0;display:flex}.I17U7q_think{flex-direction:column;display:flex}.I17U7q_thinkRow{position:relative;overflow:hidden}.I17U7q_think[data-state=running] .I17U7q_thinkRow:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite I17U7q_dsh-smooth-stream-think-sweep;position:absolute;left:0}@keyframes I17U7q_dsh-smooth-stream-think-sweep{0%{left:-300px}90%,to{left:100%}}.I17U7q_thinkLeading{flex-shrink:0}.I17U7q_thinkChevron{color:var(--dsw-alias-label-secondary)}.I17U7q_thinkTitle{font-weight:400}.I17U7q_thinkSeparator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.I17U7q_thinkSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.I17U7q_thinkSummary[data-follow-end]{text-overflow:clip}.I17U7q_thinkBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;padding:4px 0 4px 22px;font-size:14px;line-height:24px}.I17U7q_disclosureRoot{flex-direction:column;width:100%;min-width:0;display:flex}.I17U7q_disclosureRow{cursor:pointer;align-items:center;min-width:0;height:24px;display:flex;position:relative;overflow:hidden}.I17U7q_disclosureLeading{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex;position:relative}.I17U7q_disclosureIconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.I17U7q_disclosureChevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.I17U7q_disclosureRow:hover .I17U7q_disclosureIconIdle{opacity:0}.I17U7q_disclosureRow:hover .I17U7q_disclosureChevronHover{opacity:1}.I17U7q_disclosureTitle{color:var(--dsw-alias-label-secondary);flex:none;font-size:14px;line-height:24px}.I17U7q_disclosureContent{visibility:visible;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s;grid-template-rows:1fr;display:grid}.I17U7q_disclosureContent[data-collapsed]{visibility:hidden;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s var(--ds-transition-duration,.2s);grid-template-rows:0fr}.I17U7q_disclosureContent>*{min-height:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.I17U7q_think[data-state=running] .I17U7q_thinkRow:after{animation:none}.I17U7q_disclosureContent,.I17U7q_disclosureContent[data-collapsed]{transition:none}}.I17U7q_stopped{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:6px;align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}.I17U7q_visuallyHidden{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.I17U7q_follow{min-width:0}@supports (text-box-trim:trim-both){.I17U7q_root :is(p,h1,h2,h3,h4,h5,h6,li,blockquote){text-box-trim:trim-both;text-box-edge:text}}";
13
- const tagId = "dsh-smooth-stream/TypewriterAssistantNodeView.module.css";
13
+ const css$1 = ".I17U7q_root{min-width:0;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:16px;line-height:28px;display:flex}.I17U7q_body{flex-direction:column;gap:16px;min-width:0;display:flex}.I17U7q_think{flex-direction:column;display:flex}.I17U7q_thinkRow{position:relative;overflow:hidden}.I17U7q_think[data-state=running] .I17U7q_thinkRow:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite I17U7q_dsh-smooth-stream-think-sweep;position:absolute;left:0}@keyframes I17U7q_dsh-smooth-stream-think-sweep{0%{left:-300px}90%,to{left:100%}}.I17U7q_thinkLeading{flex-shrink:0}.I17U7q_thinkChevron{color:var(--dsw-alias-label-secondary)}.I17U7q_thinkTitle{font-weight:400}.I17U7q_thinkSeparator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.I17U7q_thinkSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.I17U7q_thinkSummary[data-follow-end]{text-overflow:clip}.I17U7q_thinkBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;padding:4px 0 4px 22px;font-size:14px;line-height:24px}.I17U7q_disclosureRoot{flex-direction:column;width:100%;min-width:0;display:flex}.I17U7q_disclosureRow{cursor:pointer;align-items:center;min-width:0;height:24px;display:flex;position:relative;overflow:hidden}.I17U7q_disclosureLeading{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex;position:relative}.I17U7q_disclosureIconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.I17U7q_disclosureChevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.I17U7q_disclosureRow:hover .I17U7q_disclosureIconIdle{opacity:0}.I17U7q_disclosureRow:hover .I17U7q_disclosureChevronHover{opacity:1}.I17U7q_disclosureTitle{color:var(--dsw-alias-label-secondary);flex:none;font-size:14px;line-height:24px}.I17U7q_disclosureContent{visibility:visible;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s;grid-template-rows:1fr;display:grid}.I17U7q_disclosureContent[data-collapsed]{visibility:hidden;transition:grid-template-rows var(--ds-transition-duration,.2s) var(--ds-ease-in-out,cubic-bezier(.4, 0, .2, 1)), visibility 0s var(--ds-transition-duration,.2s);grid-template-rows:0fr}.I17U7q_disclosureContent>*{min-height:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.I17U7q_think[data-state=running] .I17U7q_thinkRow:after{animation:none}.I17U7q_disclosureContent,.I17U7q_disclosureContent[data-collapsed]{transition:none}}.I17U7q_stopped{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:6px;align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}.I17U7q_visuallyHidden{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.I17U7q_follow{min-width:0}@supports (text-box-trim:trim-both){.I17U7q_root :is(p,h1,h2,h3,h4,h5,h6,li,blockquote){text-box-trim:trim-both;text-box-edge:text}}";
14
+ const tagId$1 = "dsh-smooth-stream/TypewriterAssistantNodeView.module.css";
14
15
  if (typeof document !== "undefined") {
15
- let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
16
+ let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]");
16
17
  if (tag === null) {
17
18
  tag = document.createElement("style");
18
19
  tag.dataset.plugin = "dsh-smooth-stream";
19
- tag.dataset.pluginCss = tagId;
20
+ tag.dataset.pluginCss = tagId$1;
20
21
  document.head.appendChild(tag);
21
22
  }
22
- tag.textContent = css;
23
+ tag.textContent = css$1;
23
24
  }
24
25
  var TypewriterAssistantNodeView_module_css_default = {
26
+ "root": "I17U7q_root",
27
+ "dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
28
+ "disclosureLeading": "I17U7q_disclosureLeading",
29
+ "thinkBody": "I17U7q_thinkBody",
25
30
  "disclosureChevronHover": "I17U7q_disclosureChevronHover",
26
- "think": "I17U7q_think",
31
+ "disclosureContent": "I17U7q_disclosureContent",
32
+ "stopped": "I17U7q_stopped",
33
+ "thinkTitle": "I17U7q_thinkTitle",
27
34
  "thinkSeparator": "I17U7q_thinkSeparator",
35
+ "think": "I17U7q_think",
28
36
  "disclosureTitle": "I17U7q_disclosureTitle",
29
- "thinkSummary": "I17U7q_thinkSummary",
30
- "dsh-smooth-stream-think-sweep": "I17U7q_dsh-smooth-stream-think-sweep",
31
37
  "disclosureRoot": "I17U7q_disclosureRoot",
32
- "disclosureIconIdle": "I17U7q_disclosureIconIdle",
33
- "disclosureContent": "I17U7q_disclosureContent",
34
- "thinkLeading": "I17U7q_thinkLeading",
35
- "thinkRow": "I17U7q_thinkRow",
36
- "thinkChevron": "I17U7q_thinkChevron",
37
- "disclosureRow": "I17U7q_disclosureRow",
38
- "disclosureLeading": "I17U7q_disclosureLeading",
39
- "thinkTitle": "I17U7q_thinkTitle",
40
- "stopped": "I17U7q_stopped",
41
- "thinkBody": "I17U7q_thinkBody",
42
38
  "visuallyHidden": "I17U7q_visuallyHidden",
43
- "body": "I17U7q_body",
39
+ "thinkChevron": "I17U7q_thinkChevron",
44
40
  "follow": "I17U7q_follow",
45
- "root": "I17U7q_root"
41
+ "disclosureIconIdle": "I17U7q_disclosureIconIdle",
42
+ "body": "I17U7q_body",
43
+ "thinkSummary": "I17U7q_thinkSummary",
44
+ "disclosureRow": "I17U7q_disclosureRow",
45
+ "thinkLeading": "I17U7q_thinkLeading",
46
+ "thinkRow": "I17U7q_thinkRow"
46
47
  };
47
48
  //#endregion
48
49
  //#region src/client/AnimatedDisclosure.tsx
@@ -201,8 +202,7 @@ window.__ModuleLoader__.load({
201
202
  const SETTLE_DRAIN_MULTIPLIER = 1.8;
202
203
  /** Pure per-frame reveal decision shared by the loop and its tests. */
203
204
  function computeRevealStep(config, input, dtSeconds) {
204
- const trackedCps = Math.max(input.emaCps, input.arrivalCpsEma);
205
- const baseCps = clamp$1(trackedCps, config.minCps, config.maxFlushCps);
205
+ const baseCps = clamp$1(Math.max(input.emaCps, input.arrivalCpsEma), config.minCps, config.maxFlushCps);
206
206
  const targetLagChars = input.inputActive ? Math.max(2, Math.round(baseCps * config.targetBufferMs / 1e3)) : 0;
207
207
  let currentCps;
208
208
  if (input.steadyCps !== void 0) currentCps = input.inputActive || input.settling ? clamp$1(input.steadyCps * (input.inputActive ? 1 : SETTLE_DRAIN_MULTIPLIER), config.minCps, config.maxFlushCps) : 0;
@@ -211,10 +211,7 @@ window.__ModuleLoader__.load({
211
211
  const catchup = overflow > 0 ? overflow / CATCHUP_SECONDS : 0;
212
212
  currentCps = clamp$1(baseCps * 1.08 + catchup, config.minCps, config.maxFlushCps);
213
213
  } else if (input.settling) currentCps = computeSettleDrain(config, input);
214
- else {
215
- const idleFlushCps = Math.max(config.flushCps, baseCps * 1.8, input.arrivalCpsEma * .8);
216
- currentCps = clamp$1(idleFlushCps, config.flushCps, config.maxFlushCps);
217
- }
214
+ else currentCps = clamp$1(Math.max(config.flushCps, baseCps * 1.8, input.arrivalCpsEma * .8), config.flushCps, config.maxFlushCps);
218
215
  const minRevealChars = input.inputActive ? 1 : 2;
219
216
  return {
220
217
  revealChars: Math.max(minRevealChars, Math.round(currentCps * dtSeconds)),
@@ -388,11 +385,10 @@ window.__ModuleLoader__.load({
388
385
  const deltaChars = targetCountRef.current - lastInputCountRef.current;
389
386
  const deltaMs = Math.max(1, now - lastInputTsRef.current);
390
387
  if (hadSample && deltaChars > 0) {
391
- const instantCps = deltaChars * 1e3 / deltaMs;
392
- const normalizedInstantCps = clamp$1(instantCps, config.minCps, config.maxFlushCps * 3);
388
+ const normalizedInstantCps = clamp$1(deltaChars * 1e3 / deltaMs, config.minCps, config.maxFlushCps * 3);
393
389
  const chunkEmaAlpha = .45;
394
- chunkSizeEmaRef.current = chunkSizeEmaRef.current * .55 + appendedCount * chunkEmaAlpha;
395
- arrivalCpsEmaRef.current = arrivalCpsEmaRef.current * .55 + normalizedInstantCps * chunkEmaAlpha;
390
+ chunkSizeEmaRef.current = chunkSizeEmaRef.current * (1 - chunkEmaAlpha) + appendedCount * chunkEmaAlpha;
391
+ arrivalCpsEmaRef.current = arrivalCpsEmaRef.current * (1 - chunkEmaAlpha) + normalizedInstantCps * chunkEmaAlpha;
396
392
  emaCpsRef.current = emaCpsRef.current * (1 - config.emaAlpha) + normalizedInstantCps * config.emaAlpha;
397
393
  }
398
394
  lastInputTsRef.current = now;
@@ -864,6 +860,10 @@ window.__ModuleLoader__.load({
864
860
  */
865
861
  const STREAM_BOOT_GLOBAL = "__DSH_SMOOTH_STREAM_CONFIG__";
866
862
  //#endregion
863
+ //#region src/settings.ts
864
+ /** Defaults shared by the Host schema and the client-side fallback. */
865
+ const DEFAULT_STREAM_SETTINGS = { thinkAutoExpand: true };
866
+ //#endregion
867
867
  //#region src/client/TypewriterAssistantNodeView.tsx
868
868
  function usePrefersReducedMotion() {
869
869
  const [reduced, setReduced] = (0, react.useState)(() => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true);
@@ -957,9 +957,9 @@ window.__ModuleLoader__.load({
957
957
  * or the assistant node settling — not when the rest of the reply is
958
958
  * still streaming.
959
959
  */
960
- function AnimatedReasoning({ text, running, preset, shouldHoldBack, t }) {
960
+ function AnimatedReasoning({ text, running, preset, thinkAutoExpand, shouldHoldBack, t }) {
961
961
  const reduced = usePrefersReducedMotion();
962
- const [expanded, setExpanded] = (0, react.useState)(running);
962
+ const [expanded, setExpanded] = (0, react.useState)(running && thinkAutoExpand);
963
963
  const summaryRef = (0, react.useRef)(null);
964
964
  const speedCpsRef = (0, react.useRef)(35);
965
965
  const displayed = useSmoothStreamContent(text, {
@@ -971,8 +971,8 @@ window.__ModuleLoader__.load({
971
971
  const shown = running && !reduced ? displayed : text;
972
972
  const summary = running ? latestLine(shown) : firstLine(text);
973
973
  (0, react.useEffect)(() => {
974
- setExpanded(running);
975
- }, [running]);
974
+ if (thinkAutoExpand) setExpanded(running);
975
+ }, [running, thinkAutoExpand]);
976
976
  (0, react.useEffect)(() => {
977
977
  const element = summaryRef.current;
978
978
  if (element === null) return;
@@ -1026,7 +1026,7 @@ window.__ModuleLoader__.load({
1026
1026
  * reveals when the frame rate is degraded. Settled text renders with the
1027
1027
  * full Markdown pipeline.
1028
1028
  */
1029
- const TypewriterAssistantNodeView = (0, react.memo)(function TypewriterAssistantNodeView({ mode: _mode = DEFAULT_STREAM_CONFIG.mode, preset = DEFAULT_STREAM_CONFIG.preset, revealCharsPerSec: _revealCharsPerSec = DEFAULT_STREAM_CONFIG.revealCharsPerSec, scrollSpeedPxPerSec: _scrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.scrollSpeedPxPerSec, maxScrollSpeedPxPerSec: _maxScrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.maxScrollSpeedPxPerSec, node, useTurnData, openFile, loadImage, fileMentions, t }) {
1029
+ const TypewriterAssistantNodeView = (0, react.memo)(function TypewriterAssistantNodeView({ mode: _mode = DEFAULT_STREAM_CONFIG.mode, preset = DEFAULT_STREAM_CONFIG.preset, revealCharsPerSec: _revealCharsPerSec = DEFAULT_STREAM_CONFIG.revealCharsPerSec, scrollSpeedPxPerSec: _scrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.scrollSpeedPxPerSec, maxScrollSpeedPxPerSec: _maxScrollSpeedPxPerSec = DEFAULT_STREAM_CONFIG.maxScrollSpeedPxPerSec, thinkAutoExpand = DEFAULT_STREAM_SETTINGS.thinkAutoExpand, node, useTurnData, openFile, loadImage, fileMentions, t }) {
1030
1030
  const data = node.data;
1031
1031
  const streaming = data.status === "running";
1032
1032
  const { ref: guardRef, shouldHoldBack } = useFpsGuard(streaming);
@@ -1084,6 +1084,7 @@ window.__ModuleLoader__.load({
1084
1084
  text: block.text,
1085
1085
  running: streaming && index === last,
1086
1086
  preset,
1087
+ thinkAutoExpand,
1087
1088
  shouldHoldBack,
1088
1089
  t
1089
1090
  }, index));
@@ -1106,11 +1107,13 @@ window.__ModuleLoader__.load({
1106
1107
  break;
1107
1108
  }
1108
1109
  case "tool-call": break;
1109
- case "other": rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
1110
- label: t("message.unknownBlock"),
1111
- payload: block.block,
1112
- truncatedLabel: (total) => t("json.truncated", { total })
1113
- }, index));
1110
+ case "other":
1111
+ rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
1112
+ label: t("message.unknownBlock"),
1113
+ payload: block.block,
1114
+ truncatedLabel: (total) => t("json.truncated", { total })
1115
+ }, index));
1116
+ break;
1114
1117
  }
1115
1118
  }
1116
1119
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1172,8 +1175,436 @@ window.__ModuleLoader__.load({
1172
1175
  };
1173
1176
  }
1174
1177
  //#endregion
1178
+ //#region \0dsh-css:/Users/dzlin/work/project/dsh-smooth-stream/src/client/SmoothStreamCard.module.css.mjs
1179
+ const css = ".XNGeRG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;list-style:none;transition:border-color .16s,background .16s;display:flex}.XNGeRG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.XNGeRG_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.XNGeRG_header{appearance:none;cursor:pointer;text-align:left;width:100%;font:inherit;color:inherit;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.XNGeRG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.XNGeRG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.XNGeRG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.XNGeRG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.XNGeRG_version{text-overflow:ellipsis;white-space:nowrap;max-width:16rem;color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:1.5;overflow:hidden}.XNGeRG_pending{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.XNGeRG_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.XNGeRG_chevronOpen{transform:rotate(180deg)}.XNGeRG_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;margin:0 16px;padding-bottom:8px;display:flex}.XNGeRG_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.XNGeRG_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.XNGeRG_fieldHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.XNGeRG_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.XNGeRG_toggle{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.XNGeRG_toggle:disabled{cursor:default}.XNGeRG_toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.XNGeRG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.XNGeRG_failure{justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XNGeRG_updateRow{border-top:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XNGeRG_updateCopy{flex-direction:column;gap:4px;min-width:0;display:flex}.XNGeRG_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.XNGeRG_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.XNGeRG_save,.XNGeRG_discard,.XNGeRG_update{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.XNGeRG_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.XNGeRG_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.XNGeRG_discard:hover:not(:disabled),.XNGeRG_update:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}.XNGeRG_update{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0;flex:none;align-items:center;gap:6px;display:inline-flex}.XNGeRG_discard:disabled,.XNGeRG_save:disabled,.XNGeRG_update:disabled{opacity:.4;cursor:default}.XNGeRG_discard:focus-visible,.XNGeRG_save:focus-visible,.XNGeRG_update:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
1180
+ const tagId = "dsh-smooth-stream/SmoothStreamCard.module.css";
1181
+ if (typeof document !== "undefined") {
1182
+ let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
1183
+ if (tag === null) {
1184
+ tag = document.createElement("style");
1185
+ tag.dataset.plugin = "dsh-smooth-stream";
1186
+ tag.dataset.pluginCss = tagId;
1187
+ document.head.appendChild(tag);
1188
+ }
1189
+ tag.textContent = css;
1190
+ }
1191
+ var SmoothStreamCard_module_css_default = {
1192
+ "description": "XNGeRG_description",
1193
+ "header": "XNGeRG_header",
1194
+ "fieldHead": "XNGeRG_fieldHead",
1195
+ "updateCopy": "XNGeRG_updateCopy",
1196
+ "footer": "XNGeRG_footer",
1197
+ "discard": "XNGeRG_discard",
1198
+ "readOnly": "XNGeRG_readOnly",
1199
+ "failure": "XNGeRG_failure",
1200
+ "chevronOpen": "XNGeRG_chevronOpen",
1201
+ "cardOpen": "XNGeRG_cardOpen",
1202
+ "update": "XNGeRG_update",
1203
+ "save": "XNGeRG_save",
1204
+ "chevron": "XNGeRG_chevron",
1205
+ "label": "XNGeRG_label",
1206
+ "body": "XNGeRG_body",
1207
+ "hint": "XNGeRG_hint",
1208
+ "headText": "XNGeRG_headText",
1209
+ "pending": "XNGeRG_pending",
1210
+ "name": "XNGeRG_name",
1211
+ "toggle": "XNGeRG_toggle",
1212
+ "updateRow": "XNGeRG_updateRow",
1213
+ "failed": "XNGeRG_failed",
1214
+ "version": "XNGeRG_version",
1215
+ "field": "XNGeRG_field",
1216
+ "card": "XNGeRG_card"
1217
+ };
1218
+ //#endregion
1219
+ //#region src/client/SmoothStreamCard.tsx
1220
+ /**
1221
+ * The smooth-stream plugin configuration card, rendered inside the Web
1222
+ * Settings "plugin configuration" page. One toggle, staged until the user
1223
+ * saves — the same shape as the Host-shipped cards, hand-drawn because the
1224
+ * Host cards' chrome is not exported for reuse.
1225
+ */
1226
+ /** Render the smooth-stream card independently of the core settings namespace allowlist. */
1227
+ function SmoothStreamCard(props) {
1228
+ const { t } = props;
1229
+ const [open, setOpen] = (0, react.useState)(false);
1230
+ const state = props.useSmoothStreamCard((snapshot) => snapshot);
1231
+ const blocked = !state.dirty || state.saving || state.status !== "ready";
1232
+ const versionLabel = state.version === void 0 ? null : t(state.installation === "development" ? "developmentVersion" : "version").replace("{version}", state.version);
1233
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1234
+ className: open ? `${SmoothStreamCard_module_css_default.card} ${SmoothStreamCard_module_css_default.cardOpen}` : SmoothStreamCard_module_css_default.card,
1235
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1236
+ type: "button",
1237
+ className: SmoothStreamCard_module_css_default.header,
1238
+ "aria-expanded": open,
1239
+ onClick: () => {
1240
+ setOpen(!open);
1241
+ },
1242
+ children: [
1243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1244
+ className: SmoothStreamCard_module_css_default.headText,
1245
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1246
+ className: SmoothStreamCard_module_css_default.name,
1247
+ children: t("title")
1248
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1249
+ className: SmoothStreamCard_module_css_default.description,
1250
+ children: t("description")
1251
+ })]
1252
+ }),
1253
+ versionLabel === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1254
+ className: SmoothStreamCard_module_css_default.version,
1255
+ children: versionLabel
1256
+ }),
1257
+ state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1258
+ className: SmoothStreamCard_module_css_default.pending,
1259
+ children: t("unsaved")
1260
+ }) : null,
1261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: open ? `${SmoothStreamCard_module_css_default.chevron} ${SmoothStreamCard_module_css_default.chevronOpen}` : SmoothStreamCard_module_css_default.chevron })
1262
+ ]
1263
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1264
+ className: SmoothStreamCard_module_css_default.body,
1265
+ children: [
1266
+ state.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1267
+ className: SmoothStreamCard_module_css_default.readOnly,
1268
+ role: "status",
1269
+ children: t("loading")
1270
+ }) : null,
1271
+ state.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1272
+ className: SmoothStreamCard_module_css_default.failure,
1273
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1274
+ className: SmoothStreamCard_module_css_default.readOnly,
1275
+ role: "status",
1276
+ children: t("unavailable")
1277
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1278
+ type: "button",
1279
+ className: SmoothStreamCard_module_css_default.discard,
1280
+ onClick: props.reload,
1281
+ children: t("retry")
1282
+ })]
1283
+ }) : null,
1284
+ state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1285
+ !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1286
+ className: SmoothStreamCard_module_css_default.readOnly,
1287
+ role: "status",
1288
+ children: t("readOnly")
1289
+ }) : null,
1290
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1291
+ className: SmoothStreamCard_module_css_default.field,
1292
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1293
+ className: SmoothStreamCard_module_css_default.fieldHead,
1294
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1295
+ className: SmoothStreamCard_module_css_default.label,
1296
+ children: t("thinkAutoExpand")
1297
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1298
+ type: "checkbox",
1299
+ className: SmoothStreamCard_module_css_default.toggle,
1300
+ checked: state.thinkAutoExpand,
1301
+ disabled: !state.writable,
1302
+ onChange: (event) => {
1303
+ props.edit(event.target.checked);
1304
+ }
1305
+ })]
1306
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1307
+ className: SmoothStreamCard_module_css_default.hint,
1308
+ children: t("thinkAutoExpandHint")
1309
+ })]
1310
+ }),
1311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1312
+ className: SmoothStreamCard_module_css_default.updateRow,
1313
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1314
+ className: SmoothStreamCard_module_css_default.updateCopy,
1315
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1316
+ className: SmoothStreamCard_module_css_default.label,
1317
+ children: t("updates")
1318
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1319
+ className: SmoothStreamCard_module_css_default.hint,
1320
+ children: state.restartRequired ? t("restartRequired") : state.installation === "npm" ? t("updateHint") : state.installation === "development" ? t("developmentBuild") : t("updateUnavailable")
1321
+ })]
1322
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1323
+ type: "button",
1324
+ className: SmoothStreamCard_module_css_default.update,
1325
+ disabled: !state.canUpgrade || state.upgrading || state.restartRequired,
1326
+ title: state.canUpgrade ? void 0 : t("updateUnavailable"),
1327
+ onClick: props.upgrade,
1328
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1329
+ "aria-hidden": "true",
1330
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, {})
1331
+ }), t(state.upgrading ? "updating" : "update")]
1332
+ })]
1333
+ }),
1334
+ state.upgradeFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1335
+ className: SmoothStreamCard_module_css_default.failed,
1336
+ role: "status",
1337
+ children: t("updateFailed")
1338
+ }) : null,
1339
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1340
+ className: SmoothStreamCard_module_css_default.footer,
1341
+ children: [
1342
+ state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1343
+ className: SmoothStreamCard_module_css_default.failed,
1344
+ role: "status",
1345
+ children: t("saveFailed")
1346
+ }) : null,
1347
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1348
+ type: "button",
1349
+ className: SmoothStreamCard_module_css_default.discard,
1350
+ disabled: !state.dirty || state.saving,
1351
+ onClick: props.discard,
1352
+ children: t("discard")
1353
+ }),
1354
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1355
+ type: "button",
1356
+ className: SmoothStreamCard_module_css_default.save,
1357
+ disabled: blocked,
1358
+ onClick: props.save,
1359
+ children: t(state.saving ? "saving" : "save")
1360
+ })
1361
+ ]
1362
+ })
1363
+ ] }) : null
1364
+ ]
1365
+ }) : null]
1366
+ });
1367
+ }
1368
+ //#endregion
1369
+ //#region src/client/smooth-stream-card-controller.ts
1370
+ /** Staged form state for the plugin-owned smooth-stream settings RPC. */
1371
+ /** Bridges the plugin's protected Host interface onto a staged settings form. */
1372
+ var SmoothStreamCardController = class {
1373
+ api;
1374
+ store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(this.projection());
1375
+ loaded;
1376
+ staged;
1377
+ saving = false;
1378
+ failed = false;
1379
+ upgrading = false;
1380
+ upgradeFailed = false;
1381
+ restartRequired = false;
1382
+ loadGeneration = 0;
1383
+ loadStatus = "loading";
1384
+ constructor(api) {
1385
+ this.api = api;
1386
+ }
1387
+ /** Begin the background read after the card has been registered. */
1388
+ start() {
1389
+ this.load();
1390
+ }
1391
+ /** Ignore a late response after the surrounding optional services unload. */
1392
+ stop() {
1393
+ this.loadGeneration += 1;
1394
+ }
1395
+ /** Current card snapshot, also consumed by the streaming preference cell. */
1396
+ getSnapshot() {
1397
+ return this.store.getSnapshot();
1398
+ }
1399
+ /** Subscribe to state changes. */
1400
+ subscribe(listener) {
1401
+ return this.store.subscribe(listener);
1402
+ }
1403
+ /** Build the face consumed by the settings slot renderer. */
1404
+ inject() {
1405
+ return {
1406
+ hooks: { smoothStreamCard: this.store },
1407
+ edit: (value) => {
1408
+ this.staged = value;
1409
+ this.failed = false;
1410
+ this.publish();
1411
+ },
1412
+ save: () => {
1413
+ this.save();
1414
+ },
1415
+ discard: () => {
1416
+ if (this.staged === void 0 && !this.failed) return;
1417
+ this.staged = void 0;
1418
+ this.failed = false;
1419
+ this.publish();
1420
+ },
1421
+ reload: () => {
1422
+ this.load();
1423
+ },
1424
+ upgrade: () => {
1425
+ this.upgrade();
1426
+ }
1427
+ };
1428
+ }
1429
+ projection() {
1430
+ return {
1431
+ status: this.loadStatus,
1432
+ writable: this.loaded?.writable ?? false,
1433
+ dirty: this.staged !== void 0,
1434
+ saving: this.saving,
1435
+ failed: this.failed,
1436
+ thinkAutoExpand: this.staged ?? this.loaded?.thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand,
1437
+ version: this.loaded?.version,
1438
+ installation: this.loaded?.installation ?? "unmanaged",
1439
+ canUpgrade: this.loaded?.canUpgrade ?? false,
1440
+ upgrading: this.upgrading,
1441
+ upgradeFailed: this.upgradeFailed,
1442
+ restartRequired: this.restartRequired
1443
+ };
1444
+ }
1445
+ async load() {
1446
+ const generation = ++this.loadGeneration;
1447
+ this.loadStatus = "loading";
1448
+ this.loaded = void 0;
1449
+ this.publish();
1450
+ try {
1451
+ const view = await this.api.read();
1452
+ if (generation !== this.loadGeneration) return;
1453
+ this.loaded = view;
1454
+ this.loadStatus = "ready";
1455
+ } catch {
1456
+ if (generation !== this.loadGeneration) return;
1457
+ this.loaded = void 0;
1458
+ this.loadStatus = "unavailable";
1459
+ }
1460
+ this.publish();
1461
+ }
1462
+ async save() {
1463
+ if (this.staged === void 0 || this.saving || this.loaded?.writable !== true) return;
1464
+ const value = this.staged;
1465
+ this.saving = true;
1466
+ this.failed = false;
1467
+ this.publish();
1468
+ try {
1469
+ this.loaded = await this.api.write(value);
1470
+ this.staged = void 0;
1471
+ } catch {
1472
+ this.failed = true;
1473
+ }
1474
+ this.saving = false;
1475
+ this.publish();
1476
+ }
1477
+ async upgrade() {
1478
+ if (this.loaded?.canUpgrade !== true || this.upgrading) return;
1479
+ this.upgrading = true;
1480
+ this.upgradeFailed = false;
1481
+ this.restartRequired = false;
1482
+ this.publish();
1483
+ try {
1484
+ const result = await this.api.upgrade();
1485
+ this.restartRequired = result.restartRequired;
1486
+ } catch {
1487
+ this.upgradeFailed = true;
1488
+ }
1489
+ this.upgrading = false;
1490
+ this.publish();
1491
+ }
1492
+ publish() {
1493
+ this.store.set(this.projection());
1494
+ }
1495
+ };
1496
+ //#endregion
1497
+ //#region src/settings-api.ts
1498
+ /** Shared wire vocabulary for the plugin-owned settings RPC channel. */
1499
+ /** Dedicated, loopback-only RPC channel registered by the Host half. */
1500
+ const STREAM_SETTINGS_RPC_CHANNEL = "/smooth-stream";
1501
+ /** Endpoints accepted by {@link STREAM_SETTINGS_RPC_CHANNEL}. */
1502
+ const STREAM_SETTINGS_RPC = {
1503
+ read: "settings.read",
1504
+ write: "settings.write",
1505
+ upgrade: "plugin.upgrade"
1506
+ };
1507
+ //#endregion
1508
+ //#region src/client/smooth-stream-settings-api.ts
1509
+ function record(value) {
1510
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1511
+ }
1512
+ function settingsView(value) {
1513
+ const data = record(value);
1514
+ if (data === void 0 || typeof data.version !== "string" || ![
1515
+ "npm",
1516
+ "development",
1517
+ "unmanaged"
1518
+ ].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.thinkAutoExpand !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-smooth-stream: malformed settings response");
1519
+ return data;
1520
+ }
1521
+ function upgradeView(value) {
1522
+ if (record(value)?.restartRequired !== true) throw new Error("dsh-smooth-stream: malformed update response");
1523
+ return { restartRequired: true };
1524
+ }
1525
+ function accepted(result) {
1526
+ if (!result.ok) throw new Error(result.error.message);
1527
+ return result.value;
1528
+ }
1529
+ /** Build the typed facade over the generic Connection RPC service. */
1530
+ function createSmoothStreamSettingsApi(connection) {
1531
+ return {
1532
+ async read() {
1533
+ return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.read, {})));
1534
+ },
1535
+ async write(thinkAutoExpand) {
1536
+ return settingsView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.write, { thinkAutoExpand })));
1537
+ },
1538
+ async upgrade() {
1539
+ return upgradeView(accepted(await connection.rpc.call(STREAM_SETTINGS_RPC_CHANNEL, STREAM_SETTINGS_RPC.upgrade, {})));
1540
+ }
1541
+ };
1542
+ }
1543
+ //#endregion
1544
+ //#region src/client/locales.ts
1545
+ /** Locale bundles for the smooth-stream plugin configuration card. */
1546
+ /** Dictionary namespace owned by this plugin's settings card. */
1547
+ const NS = "settings.smoothStream";
1548
+ /** English copy. */
1549
+ const en = {
1550
+ title: "Smooth stream",
1551
+ description: "How replies are revealed while they stream.",
1552
+ thinkAutoExpand: "Auto-expand thinking",
1553
+ thinkAutoExpandHint: "Open the thinking block while it streams. Turn off to keep it collapsed.",
1554
+ readOnly: "This deployment stores settings read-only.",
1555
+ loading: "Loading plugin settings…",
1556
+ unavailable: "Plugin settings are unavailable in this connection.",
1557
+ retry: "Retry",
1558
+ version: "Version {version}",
1559
+ developmentVersion: "Development version {version}",
1560
+ updates: "Updates",
1561
+ updateHint: "Install the newest npm version, then restart Harness.",
1562
+ developmentBuild: "Linked source; updates are managed in the checkout.",
1563
+ updateUnavailable: "Updates are available only for an npm profile installation.",
1564
+ update: "Update",
1565
+ updating: "Updating…",
1566
+ restartRequired: "Updated. Restart Harness to load the new version.",
1567
+ updateFailed: "The package update failed; your current version is unchanged.",
1568
+ save: "Save",
1569
+ saving: "Saving…",
1570
+ discard: "Discard",
1571
+ unsaved: "Unsaved",
1572
+ saveFailed: "The deployment did not accept these values; they were left for you to correct."
1573
+ };
1574
+ /** Simplified Chinese copy. */
1575
+ const zh = {
1576
+ title: "丝滑流式",
1577
+ description: "回复在流式输出时如何逐字展现。",
1578
+ thinkAutoExpand: "自动展开思考",
1579
+ thinkAutoExpandHint: "思考块在流式时自动展开;关闭后保持折叠,可手动展开。",
1580
+ readOnly: "本部署的设置为只读。",
1581
+ loading: "正在加载插件设置…",
1582
+ unavailable: "当前连接无法访问插件设置。",
1583
+ retry: "重试",
1584
+ version: "版本 {version}",
1585
+ developmentVersion: "开发版本 {version}",
1586
+ updates: "更新",
1587
+ updateHint: "安装最新 npm 版本后重启 Harness。",
1588
+ developmentBuild: "当前为本地链接版本,请在源码目录管理更新。",
1589
+ updateUnavailable: "只有 profile 使用 npm 包时才能更新。",
1590
+ update: "更新",
1591
+ updating: "更新中…",
1592
+ restartRequired: "已更新;重启 Harness 后加载新版本。",
1593
+ updateFailed: "包更新失败,当前版本未改变。",
1594
+ save: "保存",
1595
+ saving: "保存中…",
1596
+ discard: "放弃修改",
1597
+ unsaved: "未保存",
1598
+ saveFailed: "本部署没有接受这些值,已保留供你修改。"
1599
+ };
1600
+ //#endregion
1175
1601
  //#region src/client/index.ts
1176
- /** Cordis services required by the browser half. */
1602
+ /**
1603
+ * Cordis services required by the browser half. Only `slots` is load-bearing
1604
+ * for the stream itself; locale and Connection power the configuration card
1605
+ * and are wired through `ctx.inject` below so a deployment without them still
1606
+ * streams with defaults.
1607
+ */
1177
1608
  const inject = ["slots"];
1178
1609
  const STREAM_MODES = ["typewriter", "teleprompter"];
1179
1610
  const STREAM_PRESETS = [
@@ -1182,7 +1613,7 @@ window.__ModuleLoader__.load({
1182
1613
  "silky"
1183
1614
  ];
1184
1615
  /** Human-authored rows stay on the built-in renderer; `assistant-step` is replaced. */
1185
- const SKIP_WRAP = /* @__PURE__ */ new Set([
1616
+ const SKIP_WRAP = new Set([
1186
1617
  "assistant-step",
1187
1618
  "user",
1188
1619
  "steering",
@@ -1244,23 +1675,93 @@ window.__ModuleLoader__.load({
1244
1675
  };
1245
1676
  }
1246
1677
  /**
1678
+ * A live preference cell read by `useSyncExternalStore`. It starts on the
1679
+ * shared default and, once the plugin-owned settings controller is attached,
1680
+ * tracks the resolved `thinkAutoExpand` value. A composition (or test) that
1681
+ * ships no settings surface keeps the cell on the default.
1682
+ */
1683
+ var PreferenceCell = class {
1684
+ listeners = /* @__PURE__ */ new Set();
1685
+ card;
1686
+ value = DEFAULT_STREAM_SETTINGS.thinkAutoExpand;
1687
+ /** Re-point the cell at the plugin-owned settings controller. */
1688
+ attach(card) {
1689
+ this.card = card;
1690
+ this.refresh();
1691
+ const unsubscribe = card.subscribe(() => {
1692
+ this.refresh();
1693
+ });
1694
+ return () => {
1695
+ unsubscribe();
1696
+ if (this.card !== card) return;
1697
+ this.card = void 0;
1698
+ this.refresh();
1699
+ };
1700
+ }
1701
+ read() {
1702
+ return this.card?.getSnapshot().thinkAutoExpand ?? DEFAULT_STREAM_SETTINGS.thinkAutoExpand;
1703
+ }
1704
+ refresh() {
1705
+ const next = this.read();
1706
+ if (next === this.value) return;
1707
+ this.value = next;
1708
+ for (const listener of this.listeners) listener();
1709
+ }
1710
+ getSnapshot = () => this.value;
1711
+ subscribe = (listener) => {
1712
+ this.listeners.add(listener);
1713
+ return () => {
1714
+ this.listeners.delete(listener);
1715
+ };
1716
+ };
1717
+ };
1718
+ /**
1247
1719
  * Register the typewriter renderer after the conversation package declares the
1248
1720
  * keyed Chat node seat. A lower priority shadows the built-in assistant row;
1249
1721
  * every other growing row is wrapped in place so Tool cards, retries, and
1250
1722
  * workflow runs share conversation follow. The Host-bridged configuration
1251
- * selects the render direction, smoothing preset, and glide speed.
1723
+ * selects the render direction, smoothing preset, and glide speed; the
1724
+ * plugin-owned settings RPC supplies the live auto-expand preference when the
1725
+ * settings surface is composed.
1252
1726
  * @param ctx - Browser context carrying the shared slot registry.
1253
1727
  */
1254
1728
  function apply(ctx) {
1255
1729
  const config = readBootConfig();
1730
+ const preference = new PreferenceCell();
1731
+ ctx.inject([
1732
+ "slots",
1733
+ "locale",
1734
+ "connection"
1735
+ ], (settingsCtx) => {
1736
+ const card = new SmoothStreamCardController(createSmoothStreamSettingsApi(settingsCtx.get("connection")));
1737
+ const detachPreference = preference.attach(card);
1738
+ card.start();
1739
+ settingsCtx.effect(() => settingsCtx.locale.register(NS, {
1740
+ zh,
1741
+ en
1742
+ }), "dsh-smooth-stream: settings dictionaries");
1743
+ settingsCtx.slots.inject("settings.plugin.item", () => settingsCtx.slots.register({
1744
+ name: "settings.plugin.item",
1745
+ id: "smooth-stream",
1746
+ order: 30,
1747
+ locale: NS,
1748
+ inject: () => card.inject()
1749
+ }, SmoothStreamCard));
1750
+ return () => {
1751
+ card.stop();
1752
+ detachPreference();
1753
+ };
1754
+ });
1256
1755
  const configured = function StreamConfiguredView(props) {
1756
+ const thinkAutoExpand = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot, preference.getSnapshot);
1257
1757
  return (0, react.createElement)(TypewriterAssistantNodeView, {
1258
1758
  ...props,
1259
1759
  mode: config.mode,
1260
1760
  preset: config.preset,
1261
1761
  revealCharsPerSec: config.revealCharsPerSec,
1262
1762
  scrollSpeedPxPerSec: config.scrollSpeedPxPerSec,
1263
- maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec
1763
+ maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
1764
+ thinkAutoExpand
1264
1765
  });
1265
1766
  };
1266
1767
  ctx.slots.inject("conversation.chat.node", () => {