@jcy2387/dsh-conversation-ui 0.2.0-rc.2 → 0.2.1-rc.2

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.
Files changed (41) hide show
  1. package/README.en.md +27 -26
  2. package/README.md +27 -26
  3. package/cordis.patch.yml +1 -1
  4. package/lib/client.js +1156 -1879
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +102 -28
  7. package/locale/en.json +1 -1
  8. package/locale/zh.json +1 -1
  9. package/package.json +34 -35
  10. package/src/channel-bridge.ts +8 -7
  11. package/src/client/ConversationCard.module.css +47 -0
  12. package/src/client/ConversationCard.tsx +57 -1
  13. package/src/client/DeliverablesCard.module.css +1 -5
  14. package/src/client/DeliverablesCard.tsx +67 -50
  15. package/src/client/NativeAssistantEnhancement.tsx +148 -0
  16. package/src/client/conversation-ui-card-controller.ts +15 -8
  17. package/src/client/conversation-ui-settings-api.ts +7 -3
  18. package/src/client/deliverables.ts +123 -27
  19. package/src/client/developer-tools.ts +51 -0
  20. package/src/client/index.ts +52 -147
  21. package/src/client/locales.ts +50 -2
  22. package/src/client/useConversationContent.ts +15 -2
  23. package/src/config.ts +1 -1
  24. package/src/package-meta.ts +3 -0
  25. package/src/plugin.ts +63 -16
  26. package/src/profile-installation.ts +22 -3
  27. package/src/settings-api.ts +3 -3
  28. package/src/settings.ts +17 -15
  29. package/src/client/AnimatedDisclosure.tsx +0 -89
  30. package/src/client/CodexTurnProcessNodeView.tsx +0 -127
  31. package/src/client/FollowHost.tsx +0 -31
  32. package/src/client/ToolActivityGroup.tsx +0 -303
  33. package/src/client/TranscriptViewBridge.tsx +0 -26
  34. package/src/client/TypewriterAssistantNodeView.module.css +0 -1226
  35. package/src/client/TypewriterAssistantNodeView.tsx +0 -422
  36. package/src/client/TypewriterToolNodeView.tsx +0 -831
  37. package/src/client/turnElapsed.ts +0 -23
  38. package/src/client/turnProcessFallback.ts +0 -91
  39. package/src/client/useChatSeatHidden.ts +0 -91
  40. package/src/client/useFpsGuard.ts +0 -84
  41. package/src/client/useSearchableHidden.ts +0 -23
package/lib/client.js CHANGED
@@ -4,61 +4,9 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- //#region \0rolldown/runtime.js
8
- var __create = Object.create;
9
- var __defProp = Object.defineProperty;
10
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
- var __getOwnPropNames = Object.getOwnPropertyNames;
12
- var __getProtoOf = Object.getPrototypeOf;
13
- var __hasOwnProp = Object.prototype.hasOwnProperty;
14
- var __copyProps = (to, from, except, desc) => {
15
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
16
- key = keys[i];
17
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
18
- get: ((k) => from[k]).bind(null, key),
19
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
- });
21
- }
22
- return to;
23
- };
24
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
25
- value: mod,
26
- enumerable: true
27
- }) : target, mod));
28
- //#endregion
29
7
  let react = require("react");
30
- react = __toESM(react, 1);
31
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
32
9
  let react_jsx_runtime = require("react/jsx-runtime");
33
- let react_dom = require("react-dom");
34
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.0-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
35
- /**
36
- * Browser-safe Workspace path and display helpers.
37
- * @module @deepseek-ai/dsh-util-workspace-path
38
- */
39
- /** Whether a path uses a Windows drive or UNC prefix. */
40
- function isWindowsStylePath(value) {
41
- return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
42
- }
43
- /**
44
- * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
45
- * @param path - the path to classify.
46
- * @returns `true` for an absolute path; `false` for a Workspace-relative one.
47
- */
48
- function isAbsoluteWorkspacePath(path) {
49
- return path.startsWith("/") || isWindowsStylePath(path);
50
- }
51
- /**
52
- * Address a decoded absolute file path through the authenticated file route.
53
- * @param base - HTTP(S) application base, including its deployment prefix, or `dsh-app://app/`.
54
- * @param path - Native file path; URL escapes in authored Markdown must already be decoded.
55
- * @returns File URL, or undefined for unsupported transports and non-absolute paths.
56
- */
57
- function fileMediaUrl(base, path) {
58
- if (!/^https?:/u.test(base) && !base.startsWith("dsh-app://app/") || !isAbsoluteWorkspacePath(path) || /^[/\\]{2}/u.test(path) || /[\u0000-\u001f\u007f]/u.test(path)) return void 0;
59
- return new URL(`api/file?path=${encodeURIComponent(path)}`, base).href;
60
- }
61
- //#endregion
62
10
  //#region src/client/useConversationContent.ts
63
11
  /**
64
12
  * Stream-smoothing reveal hook.
@@ -192,7 +140,7 @@ window.__ModuleLoader__.load({
192
140
  * @param options - Preset, guard, and steady-rate wiring.
193
141
  * @returns The displayed content, revealed at the smoothed cadence.
194
142
  */
195
- function useConversationContent(content, { enabled = true, preset = "balanced", shouldHoldBack, steadyCps, defaultCps, speedCpsRef } = {}) {
143
+ function useConversationContent(content, { enabled = true, preset = "balanced", shouldHoldBack, steadyCps, settleCps, defaultCps, speedCpsRef } = {}) {
196
144
  const config = PRESET_CONFIG[preset];
197
145
  const seedCps = defaultCps ?? config.defaultCps;
198
146
  const [displayedContent, setDisplayedContent] = (0, react.useState)(content);
@@ -210,7 +158,9 @@ window.__ModuleLoader__.load({
210
158
  const lastFrameTsRef = (0, react.useRef)(null);
211
159
  const wakeTimerRef = (0, react.useRef)(null);
212
160
  const holdBackRef = (0, react.useRef)(shouldHoldBack);
161
+ const settleCpsRef = (0, react.useRef)(settleCps);
213
162
  const speedOutRef = (0, react.useRef)(speedCpsRef);
163
+ settleCpsRef.current = settleCps;
214
164
  speedOutRef.current = speedCpsRef;
215
165
  (0, react.useEffect)(() => {
216
166
  holdBackRef.current = shouldHoldBack;
@@ -286,6 +236,7 @@ window.__ModuleLoader__.load({
286
236
  const settling = !inputActive && idleMs >= config.settleAfterMs;
287
237
  let revealChars;
288
238
  if (steadyCps !== void 0) {
239
+ const effectiveSteadyCps = inputActive ? steadyCps : settleCpsRef.current ?? steadyCps;
289
240
  const step = computeRevealStep(config, {
290
241
  backlog,
291
242
  chunkSizeEma: chunkSizeEmaRef.current,
@@ -293,7 +244,7 @@ window.__ModuleLoader__.load({
293
244
  emaCps: emaCpsRef.current,
294
245
  inputActive,
295
246
  settling,
296
- steadyCps
247
+ steadyCps: effectiveSteadyCps
297
248
  }, dtSeconds);
298
249
  revealChars = Math.min(step.revealChars, backlog);
299
250
  } else revealChars = computeQueueReveal(backlog, frameIntervalMs);
@@ -377,1590 +328,166 @@ window.__ModuleLoader__.load({
377
328
  return displayedContent;
378
329
  }
379
330
  //#endregion
380
- //#region src/client/useFpsGuard.ts
381
- /**
382
- * Performance guard for the streaming reveal.
383
- *
384
- * Feeds an EMA-smoothed frame-rate monitor from a rAF loop while streaming
385
- * and tracks whether the reply is on-screen. The returned `shouldHoldBack`
386
- * predicate is true only while the frame rate is below the threshold AND the
387
- * reply is offscreen — exactly the spec's "skip offscreen DOM updates when
388
- * FPS < 30" rule. The smoother consumes the predicate as its commit veto.
389
- */
390
- const FPS_THRESHOLD = 30;
391
- const FPS_ALPHA = .12;
392
- const RECOVER_FRAMES = 6;
393
- const MAX_FRAME_MS = 100;
394
- function useFpsGuard(active) {
395
- const fpsRef = (0, react.useRef)({
396
- emaMs: 0,
397
- lastMs: 0,
398
- healthyRun: 0,
399
- degraded: false
400
- });
401
- const visibleRef = (0, react.useRef)(true);
402
- const elementRef = (0, react.useRef)(null);
403
- (0, react.useEffect)(() => {
404
- if (!active) return;
405
- let rafId = 0;
406
- const frame = (now) => {
407
- rafId = requestAnimationFrame(frame);
408
- const fps = fpsRef.current;
409
- if (fps.lastMs === 0) {
410
- fps.lastMs = now;
411
- return;
412
- }
413
- const delta = Math.min(MAX_FRAME_MS, Math.max(1, now - fps.lastMs));
414
- fps.lastMs = now;
415
- fps.emaMs = fps.emaMs === 0 ? delta : fps.emaMs + FPS_ALPHA * (delta - fps.emaMs);
416
- if (1e3 / fps.emaMs < FPS_THRESHOLD) {
417
- fps.healthyRun = 0;
418
- fps.degraded = true;
419
- } else if (fps.degraded) {
420
- fps.healthyRun += 1;
421
- if (fps.healthyRun >= RECOVER_FRAMES) fps.degraded = false;
422
- }
423
- };
424
- rafId = requestAnimationFrame(frame);
425
- return () => {
426
- cancelAnimationFrame(rafId);
427
- fpsRef.current = {
428
- emaMs: 0,
429
- lastMs: 0,
430
- healthyRun: 0,
431
- degraded: false
432
- };
433
- };
434
- }, [active]);
435
- const ref = (0, react.useCallback)((element) => {
436
- elementRef.current = element;
437
- }, []);
438
- (0, react.useEffect)(() => {
439
- const element = elementRef.current;
440
- if (element === null || typeof IntersectionObserver === "undefined") return;
441
- const observer = new IntersectionObserver((entries) => {
442
- for (const entry of entries) visibleRef.current = entry.isIntersecting;
443
- }, { rootMargin: "120px 0px" });
444
- observer.observe(element);
445
- return () => observer.disconnect();
446
- });
447
- return {
448
- ref,
449
- shouldHoldBack: (0, react.useCallback)(() => {
450
- return active && fpsRef.current.degraded && !visibleRef.current;
451
- }, [active])
331
+ //#region src/client/NativeAssistantEnhancement.tsx
332
+ const REDUCED_MOTION = "(prefers-reduced-motion: reduce)";
333
+ function reducedMotion() {
334
+ return window.matchMedia?.(REDUCED_MOTION).matches === true;
335
+ }
336
+ function subscribeMotion(listener) {
337
+ const query = window.matchMedia?.(REDUCED_MOTION);
338
+ query?.addEventListener("change", listener);
339
+ return () => {
340
+ query?.removeEventListener("change", listener);
452
341
  };
453
342
  }
454
- //#endregion
455
- //#region \0dsh-css:/home/runner/work/dsh-codex-suite/dsh-codex-suite/packages/conversation-ui/src/client/TypewriterAssistantNodeView.module.css.mjs
456
- const css$2 = ".Sp5wgW_root{min-width:0;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:16px;line-height:28px;display:flex}.Sp5wgW_root[data-streaming=true]{animation:.14s ease-out both Sp5wgW_dsh-codex-stream-enter}[data-chat-anchor-key][hidden=until-found][data-codex-process-hidden],[data-chat-anchor-key][hidden=until-found][data-turn-process-hidden]{clip-path:inset(50%)!important;content-visibility:hidden!important;contain-intrinsic-size:0 0!important;pointer-events:none!important;block-size:1px!important;min-block-size:0!important;inline-size:1px!important;min-inline-size:0!important;position:absolute!important;overflow:hidden!important}.Sp5wgW_body{flex-direction:column;gap:16px;min-width:0;display:flex}.Sp5wgW_markdownFlow{min-width:0}.Sp5wgW_root .Sp5wgW_markdownFlow pre{overflow-wrap:normal;white-space:pre;word-break:normal;tab-size:2;max-height:min(60vh,480px);overflow:auto}.Sp5wgW_root .Sp5wgW_markdownFlow pre code{white-space:inherit;word-break:inherit}.Sp5wgW_markdownFlow>div>:where(ul,ol)+p{margin-block-start:24px}.Sp5wgW_root[data-stream-phase=streaming],.Sp5wgW_root[data-stream-phase=process]{color:var(--dsw-alias-label-primary)}.Sp5wgW_root[data-stream-phase=streaming] .Sp5wgW_markdownFlow>div,.Sp5wgW_root[data-stream-phase=process] .Sp5wgW_markdownFlow>div{font-family:inherit;font-size:16px;font-weight:400;line-height:28px}.Sp5wgW_root[data-stream-phase=streaming] .Sp5wgW_markdownFlow strong,.Sp5wgW_root[data-stream-phase=process] .Sp5wgW_markdownFlow strong{font-weight:400!important}.Sp5wgW_root[data-stream-phase=final]{margin-block-start:2px}.Sp5wgW_turnFoldRow{color:var(--dsw-alias-label-secondary);flex-direction:column;align-items:stretch;gap:6px;margin-block:0 14px;font-size:14px;line-height:24px;display:flex}.Sp5wgW_turnFoldRow:after{background:var(--dsw-alias-border-l2);content:\"\";flex:none;width:100%;height:1px}.Sp5wgW_turnFoldButton{color:inherit;cursor:pointer;font:inherit;background:0 0;border:0;border-radius:4px;flex:none;align-self:flex-start;align-items:center;gap:6px;padding:2px 0;display:inline-flex}.Sp5wgW_turnFoldButton:hover{color:var(--dsw-alias-label-primary)}.Sp5wgW_turnFoldButton:disabled{color:inherit;cursor:default}.Sp5wgW_turnFoldButton:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:3px}.Sp5wgW_turnFoldButton>svg{flex:none}[data-chat-anchor-key]:has([data-tool-group-proxy]){display:none!important}.Sp5wgW_root[data-stream-phase=interrupted]{color:var(--dsw-alias-label-secondary)}.Sp5wgW_eventRow{min-width:0;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:24px;animation:.14s ease-out both Sp5wgW_dsh-codex-stream-enter}.Sp5wgW_eventRow[data-stream-node=tool-call],.Sp5wgW_eventRow[data-stream-node=workflow-run],.Sp5wgW_eventRow[data-stream-node=model-retry],.Sp5wgW_eventRow[data-stream-node=compaction],.Sp5wgW_eventRow[data-stream-node=manual-compaction]{margin-block:2px}.Sp5wgW_eventRow[data-stream-state=running]{color:var(--dsw-alias-label-tertiary)}.Sp5wgW_eventRow[data-stream-node=compaction] [data-compaction-disclosure],.Sp5wgW_eventRow[data-stream-node=manual-compaction] [data-compaction-disclosure]{display:none!important}.Sp5wgW_eventRow[data-stream-node=compaction] button:has([data-compaction-icon]),.Sp5wgW_eventRow[data-stream-node=manual-compaction] button:has([data-compaction-icon]){cursor:default}.Sp5wgW_eventRow[data-stream-node=compaction] button:has([data-compaction-icon]):hover,.Sp5wgW_eventRow[data-stream-node=compaction] button:has([data-compaction-icon]):focus-visible,.Sp5wgW_eventRow[data-stream-node=manual-compaction] button:has([data-compaction-icon]):hover,.Sp5wgW_eventRow[data-stream-node=manual-compaction] button:has([data-compaction-icon]):focus-visible{background:0 0!important}.Sp5wgW_eventRow[data-stream-node=compaction] [data-compaction-icon],.Sp5wgW_eventRow[data-stream-node=manual-compaction] [data-compaction-icon]{color:var(--dsw-alias-label-secondary)!important;opacity:1!important}.Sp5wgW_eventRow[data-stream-node=compaction] button:has([data-compaction-icon]),.Sp5wgW_eventRow[data-stream-node=manual-compaction] button:has([data-compaction-icon]){color:var(--dsw-alias-label-secondary)!important}.Sp5wgW_eventRow[data-stream-node=compaction] button:has([data-compaction-icon])>span,.Sp5wgW_eventRow[data-stream-node=manual-compaction] button:has([data-compaction-icon])>span{color:var(--dsw-alias-label-secondary)!important}button:has([data-compaction-icon]),button:has([data-compaction-icon])>span{color:var(--dsw-alias-label-secondary)!important;opacity:1!important}.Sp5wgW_toolGroupAnchor{min-width:0}.Sp5wgW_toolGroupFrame{background:0 0;min-width:0;margin-block:2px 8px;overflow:hidden}.Sp5wgW_toolGroupHeader{width:100%;min-height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;text-align:start;background:0 0;border:0;align-items:center;gap:6px;padding:2px 8px 2px 22px;font-size:14px;line-height:24px;display:flex;position:relative}.Sp5wgW_toolGroupHeader:hover{color:var(--dsw-alias-label-primary);background:0 0}.Sp5wgW_toolGroupHeader:focus-visible{background:0 0;outline:none}.Sp5wgW_toolGroupHeaderIcon{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex;position:absolute;inset-block-start:50%;inset-inline-start:0;transform:translateY(-50%)}.Sp5wgW_toolGroupHeaderGlyph{justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}.Sp5wgW_toolGroupHeaderLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Sp5wgW_toolGroupHeaderChevron{color:var(--dsw-alias-label-tertiary);flex:none;margin-inline-start:0;display:inline-flex}.Sp5wgW_editStats{color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none;margin-inline-start:4px;font-size:14px;line-height:24px}.Sp5wgW_toolGroupContent{min-width:0}.Sp5wgW_toolGroupFrame .Sp5wgW_eventRow[data-stream-tool-group-member]{background:0 0;border:0;border-radius:0;margin-block:0;padding-inline:8px 6px}.Sp5wgW_toolGroupFrame .Sp5wgW_toolSemanticIcon,.Sp5wgW_toolGroupFrame .Sp5wgW_streamSemanticIcon{inset-block-start:4px;transform:none}.Sp5wgW_eventRow [data-disclosure-row]{min-width:0}.Sp5wgW_eventRow[data-stream-tool-row]{position:relative}.Sp5wgW_eventRow[data-stream-tool-row][data-stream-progress=active]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row],.Sp5wgW_eventRow[data-stream-tool-row][data-stream-progress=active]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash]{position:relative;overflow:hidden}.Sp5wgW_eventRow[data-stream-tool-row][data-stream-progress=active]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row]:after,.Sp5wgW_eventRow[data-stream-tool-row][data-stream-progress=active]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash]:after{inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);content:\"\";pointer-events:none;width:300px;animation:2.6s ease-out infinite Sp5wgW_dsh-conversation-ui-think-sweep;position:absolute;left:-300px}.Sp5wgW_toolSemanticIcon,.Sp5wgW_streamSemanticIcon{z-index:1;width:14px;height:14px;color:var(--dsw-alias-label-tertiary);pointer-events:none;justify-content:center;align-items:center;display:inline-flex;position:absolute;inset-block-start:4px;inset-inline-start:0}.Sp5wgW_eventRow[data-stream-semantic-icon-owned]{position:relative}.Sp5wgW_eventRow[data-stream-native-disclosure]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded]>:first-child{display:contents}.Sp5wgW_eventRow[data-stream-native-disclosure]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded]>:first-child>:first-child:not(:last-child){opacity:1}.Sp5wgW_eventRow[data-stream-semantic-icon-owned]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded]{padding-inline-start:22px}.Sp5wgW_eventRow[data-stream-semantic-icon-owned]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded]>:first-child>:first-child:not(:last-child){display:none}.Sp5wgW_eventRow[data-stream-native-disclosure]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded]>:first-child>svg{opacity:0;color:var(--dsw-alias-label-secondary);flex:none;order:4;margin:0 0 0 6px;transition:opacity .1s,transform .12s;position:static}.Sp5wgW_eventRow[data-stream-native-disclosure]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded]:hover>:first-child>svg,.Sp5wgW_eventRow[data-stream-native-disclosure]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded]:focus-visible>:first-child>svg{opacity:1}.Sp5wgW_eventRow[data-stream-native-disclosure]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded=false]>:first-child>svg{transform:rotate(-90deg)}.Sp5wgW_eventRow[data-stream-native-disclosure]>:not(.Sp5wgW_streamSemanticIcon)>[data-disclosure-row][aria-expanded=true]>:first-child>svg{transform:rotate(0)}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row]{padding-inline-start:22px}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row]:not([aria-expanded])>:first-child{display:none}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]>:first-child{display:contents}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]>:first-child>:first-child:not(:last-child){display:none}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]>:nth-child(n+3){flex-grow:0}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]>:first-child>svg{opacity:0;color:var(--dsw-alias-label-secondary);flex:none;order:4;margin:0 0 0 6px;transition:opacity .1s,transform .12s;position:static}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]:hover>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]:focus-visible>:first-child>svg{opacity:1}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded=false]>:first-child>svg{transform:rotate(-90deg)}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded=true]>:first-child>svg{transform:rotate(0)}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash]{padding-inline-start:22px}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash]:not([aria-expanded])>:first-child{display:none}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]>:first-child{display:contents}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]>:first-child>:first-child:not(:last-child){display:none}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]>:nth-child(n+3){flex-grow:0}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]>:first-child>svg{opacity:0;color:var(--dsw-alias-label-secondary);flex:none;order:4;margin:0 0 0 6px;transition:opacity .1s,transform .12s;position:static}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]:hover>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]:focus-visible>:first-child>svg{opacity:1}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded=false]>:first-child>svg{transform:rotate(-90deg)}.Sp5wgW_eventRow[data-stream-tool-row]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded=true]>:first-child>svg{transform:rotate(0)}.Sp5wgW_nestedToolSemanticIcon{z-index:1;width:14px;height:14px;color:var(--dsw-alias-label-tertiary);pointer-events:none;justify-content:center;align-items:center;display:inline-flex;position:absolute;inset-block-start:5px;inset-inline-start:1px}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]:has(>.Sp5wgW_nestedToolSemanticIcon),.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]:has(>.Sp5wgW_nestedToolSemanticIcon){padding-inline-start:22px}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]:has(>.Sp5wgW_nestedToolSemanticIcon)>:first-child>:first-child:not(:last-child),.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]:has(>.Sp5wgW_nestedToolSemanticIcon)>:first-child>:first-child:not(:last-child){display:none}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]>:first-child,.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]>:first-child{display:contents}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]>:nth-child(n+3),.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]>:nth-child(n+3){flex-grow:0}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]>:first-child>svg{opacity:0;color:var(--dsw-alias-label-secondary);flex:none;order:4;margin:0 0 0 6px;transition:opacity .1s,transform .12s;position:static}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]:hover>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded]:focus-visible>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]:hover>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded]:focus-visible>:first-child>svg{opacity:1}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded=false]>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded=false]>:first-child>svg{transform:rotate(-90deg)}.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row][aria-expanded=true]>:first-child>svg,.Sp5wgW_eventRow[data-stream-tool-row] [data-subcalls]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash][aria-expanded=true]>:first-child>svg{transform:rotate(0)}@keyframes Sp5wgW_dsh-codex-stream-enter{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.Sp5wgW_body>[data-turn-process-inline][hidden]{margin-bottom:-16px}.Sp5wgW_reasoningDisclosure{margin:0}.Sp5wgW_reasoningSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:24px;list-style-position:inside}.Sp5wgW_reasoningSummary:hover{color:var(--dsw-alias-label-primary)}.Sp5wgW_reasoningText{min-width:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:24px}.Sp5wgW_reasoningText>div{font-family:inherit;font-size:14px;font-weight:400;line-height:24px}.Sp5wgW_reasoningText p,.Sp5wgW_reasoningText li,.Sp5wgW_reasoningText blockquote,.Sp5wgW_reasoningText strong,.Sp5wgW_reasoningText h1,.Sp5wgW_reasoningText h2,.Sp5wgW_reasoningText h3,.Sp5wgW_reasoningText h4,.Sp5wgW_reasoningText h5,.Sp5wgW_reasoningText h6{font-size:14px;font-weight:400;line-height:24px}.Sp5wgW_reasoningText strong{font-weight:400!important}.Sp5wgW_reasoningText h1,.Sp5wgW_reasoningText h2,.Sp5wgW_reasoningText h3,.Sp5wgW_reasoningText h4,.Sp5wgW_reasoningText h5,.Sp5wgW_reasoningText h6{margin-block:8px}.Sp5wgW_reasoningText[data-stream-state=running]{color:var(--dsw-alias-label-secondary)}.Sp5wgW_think{flex-direction:column;display:flex}.Sp5wgW_thinkRow{position:relative;overflow:hidden}.Sp5wgW_think[data-state=running] .Sp5wgW_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 Sp5wgW_dsh-conversation-ui-think-sweep;position:absolute;left:0}@keyframes Sp5wgW_dsh-conversation-ui-think-sweep{0%{left:-300px}90%,to{left:100%}}.Sp5wgW_thinkLeading{flex-shrink:0}.Sp5wgW_thinkChevron{color:var(--dsw-alias-label-secondary)}.Sp5wgW_thinkTitle{font-weight:400}.Sp5wgW_thinkSeparator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.Sp5wgW_thinkSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;font-size:14px;line-height:24px;overflow:hidden}.Sp5wgW_thinkSummary[data-follow-end]{text-overflow:clip}.Sp5wgW_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}.Sp5wgW_disclosureRoot{flex-direction:column;width:100%;min-width:0;display:flex}.Sp5wgW_disclosureRow{cursor:pointer;align-items:center;min-width:0;height:24px;display:flex;position:relative;overflow:hidden}.Sp5wgW_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}.Sp5wgW_disclosureTitle{color:var(--dsw-alias-label-secondary);flex:none;font-size:14px;line-height:24px}.Sp5wgW_disclosureTrailing{opacity:0;width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-left:6px;transition:opacity .1s;display:inline-flex}.Sp5wgW_disclosureRow:hover .Sp5wgW_disclosureTrailing,.Sp5wgW_disclosureRow:focus-visible .Sp5wgW_disclosureTrailing{opacity:1}.Sp5wgW_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}.Sp5wgW_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}.Sp5wgW_disclosureContent>*{min-height:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.Sp5wgW_root,.Sp5wgW_eventRow,.Sp5wgW_think[data-state=running] .Sp5wgW_thinkRow:after{animation:none}.Sp5wgW_eventRow[data-stream-tool-row][data-stream-progress=active]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>[data-tool]>*>[data-disclosure-row]:after,.Sp5wgW_eventRow[data-stream-tool-row][data-stream-progress=active]>[data-chat-call-id]>[data-slot=\"tool.call.toolview\"]>*>[data-sample=bash]:after{animation:none}.Sp5wgW_disclosureContent,.Sp5wgW_disclosureContent[data-collapsed]{transition:none}}.Sp5wgW_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}.Sp5wgW_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}.Sp5wgW_follow{min-width:0}@supports (text-box-trim:trim-both){.Sp5wgW_root :is(p,h1,h2,h3,h4,h5,h6,blockquote):not(:has(code)){text-box-trim:trim-both;text-box-edge:text}}";
457
- const tagId$2 = "@jcy2387/dsh-conversation-ui/TypewriterAssistantNodeView.module.css";
458
- if (typeof document !== "undefined") {
459
- let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]");
460
- if (tag === null) {
461
- tag = document.createElement("style");
462
- tag.dataset.plugin = "@jcy2387/dsh-conversation-ui";
463
- tag.dataset.pluginCss = tagId$2;
464
- document.head.appendChild(tag);
465
- }
466
- tag.textContent = css$2;
467
- }
468
- var TypewriterAssistantNodeView_module_css_default = {
469
- "toolGroupHeaderChevron": "Sp5wgW_toolGroupHeaderChevron",
470
- "thinkBody": "Sp5wgW_thinkBody",
471
- "toolGroupHeaderLabel": "Sp5wgW_toolGroupHeaderLabel",
472
- "thinkSummary": "Sp5wgW_thinkSummary",
473
- "follow": "Sp5wgW_follow",
474
- "disclosureContent": "Sp5wgW_disclosureContent",
475
- "toolSemanticIcon": "Sp5wgW_toolSemanticIcon",
476
- "body": "Sp5wgW_body",
477
- "root": "Sp5wgW_root",
478
- "editStats": "Sp5wgW_editStats",
479
- "reasoningText": "Sp5wgW_reasoningText",
480
- "disclosureRow": "Sp5wgW_disclosureRow",
481
- "disclosureLeading": "Sp5wgW_disclosureLeading",
482
- "thinkLeading": "Sp5wgW_thinkLeading",
483
- "thinkTitle": "Sp5wgW_thinkTitle",
484
- "disclosureTitle": "Sp5wgW_disclosureTitle",
485
- "streamSemanticIcon": "Sp5wgW_streamSemanticIcon",
486
- "think": "Sp5wgW_think",
487
- "disclosureTrailing": "Sp5wgW_disclosureTrailing",
488
- "toolGroupFrame": "Sp5wgW_toolGroupFrame",
489
- "nestedToolSemanticIcon": "Sp5wgW_nestedToolSemanticIcon",
490
- "disclosureRoot": "Sp5wgW_disclosureRoot",
491
- "eventRow": "Sp5wgW_eventRow",
492
- "reasoningSummary": "Sp5wgW_reasoningSummary",
493
- "toolGroupHeaderIcon": "Sp5wgW_toolGroupHeaderIcon",
494
- "dsh-conversation-ui-think-sweep": "Sp5wgW_dsh-conversation-ui-think-sweep",
495
- "stopped": "Sp5wgW_stopped",
496
- "thinkRow": "Sp5wgW_thinkRow",
497
- "visuallyHidden": "Sp5wgW_visuallyHidden",
498
- "toolGroupContent": "Sp5wgW_toolGroupContent",
499
- "markdownFlow": "Sp5wgW_markdownFlow",
500
- "turnFoldRow": "Sp5wgW_turnFoldRow",
501
- "toolGroupHeaderGlyph": "Sp5wgW_toolGroupHeaderGlyph",
502
- "toolGroupHeader": "Sp5wgW_toolGroupHeader",
503
- "toolGroupAnchor": "Sp5wgW_toolGroupAnchor",
504
- "reasoningDisclosure": "Sp5wgW_reasoningDisclosure",
505
- "turnFoldButton": "Sp5wgW_turnFoldButton",
506
- "thinkChevron": "Sp5wgW_thinkChevron",
507
- "dsh-codex-stream-enter": "Sp5wgW_dsh-codex-stream-enter",
508
- "thinkSeparator": "Sp5wgW_thinkSeparator"
509
- };
510
- //#endregion
511
- //#region src/client/FollowHost.tsx
512
343
  /**
513
- * Structural host for streaming content.
514
- *
515
- * Current DSH ChatView is the sole owner of session scroll restoration,
516
- * reader unpinning, and live bottom-follow. The plugin intentionally performs
517
- * no scrollTop or transform writes here; doing so would race ChatView's private
518
- * scroll ledger when switching between sessions.
344
+ * Enhance assistant input while delegating every visual and owner prop to the
345
+ * registered renderer. DSH retains Markdown, images, work-details policy,
346
+ * disclosure resets, interrupted output, and final-answer actions.
347
+ * @param NativeAssistant - existing assistant renderer, including memo components.
348
+ * @param preference - live reveal and auto-expand preferences.
349
+ * @returns a renderer with no additional DOM or styles.
519
350
  */
520
- function FollowHost({ active, speedCpsRef, minSpeedPxPerSec, maxSpeedPxPerSec, children }) {
521
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
522
- className: TypewriterAssistantNodeView_module_css_default.follow,
523
- "data-stream-follow": active || void 0,
524
- children
525
- });
526
- }
527
- //#endregion
528
- //#region src/config.ts
529
- /** Defaults shared by the Host schema and the client-side fallback. */
530
- const DEFAULT_CONVERSATION_CONFIG = {
531
- mode: "teleprompter",
532
- preset: "balanced",
533
- revealCharsPerSec: 80,
534
- scrollSpeedPxPerSec: 48,
535
- maxScrollSpeedPxPerSec: 1e3
536
- };
537
- /**
538
- * Window global the Host writes into the served index HTML. The browser boot
539
- * graph carries no per-entry config, so this inline script is the only
540
- * Host-to-client configuration channel for a composed web plugin.
541
- */
542
- const CONVERSATION_BOOT_GLOBAL = "__DSH_CONVERSATION_UI_CONFIG__";
543
- //#endregion
544
- //#region src/settings.ts
545
- /** Defaults shared by the Host schema and the client-side fallback. */
546
- const DEFAULT_CONVERSATION_SETTINGS = { thinkAutoExpand: true };
547
- //#endregion
548
- //#region src/client/useSearchableHidden.ts
549
- /** Keep a hidden subtree mounted and reveal it for focus or browser find. */
550
- function useSearchableHidden(hidden, reveal) {
551
- const ref = (0, react.useRef)(null);
552
- (0, react.useLayoutEffect)(() => {
553
- const element = ref.current;
554
- if (element === null) return;
555
- if (hidden && element.contains(element.ownerDocument.activeElement)) {
556
- reveal();
557
- return;
558
- }
559
- if (hidden) element.setAttribute("hidden", "until-found");
560
- else element.removeAttribute("hidden");
561
- }, [hidden, reveal]);
562
- (0, react.useEffect)(() => {
563
- const element = ref.current;
564
- if (element === null) return;
565
- element.addEventListener("beforematch", reveal);
566
- return () => {
567
- element.removeEventListener("beforematch", reveal);
568
- };
569
- }, [reveal]);
570
- return ref;
571
- }
572
- //#endregion
573
- //#region src/client/useChatSeatHidden.ts
574
- const OWNER_ATTR = "data-codex-process-hidden";
575
- /**
576
- * Apply searchable hiding to the outer Chat row for a proven paginated-history
577
- * fallback. Resolving the seat on every render follows rows moved by Tool-group
578
- * portals without leaving ownership or beforematch listeners on stale seats.
579
- */
580
- function useChatSeatHidden(hidden, reveal) {
581
- const rootRef = (0, react.useRef)(null);
582
- const revealRef = (0, react.useRef)(reveal);
583
- revealRef.current = reveal;
584
- const ref = (0, react.useCallback)((element) => {
585
- rootRef.current = element;
586
- }, []);
587
- (0, react.useLayoutEffect)(() => {
588
- const seat = rootRef.current?.closest("[data-chat-anchor-key]");
589
- if (seat === null || seat === void 0) return;
590
- if (!hidden) {
591
- if (seat.hasAttribute(OWNER_ATTR)) {
592
- seat.removeAttribute(OWNER_ATTR);
593
- seat.removeAttribute("hidden");
351
+ function enhanceNativeAssistant(NativeAssistant, preference) {
352
+ function EnhancedAssistant(props) {
353
+ const live = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot, preference.getSnapshot);
354
+ const autoExpand = live.thinkAutoExpand;
355
+ const reduced = (0, react.useSyncExternalStore)(subscribeMotion, reducedMotion, () => false);
356
+ const running = props.node.data.status === "running";
357
+ const blocks = props.node.data.blocks;
358
+ const source = (0, react.useMemo)(() => blocks.flatMap((block) => {
359
+ if (block.kind !== "text" && block.kind !== "reasoning") return [];
360
+ if (props.groupPart === "reasoning" && block.kind !== "reasoning") return [];
361
+ if (props.groupPart === "response" && block.kind === "reasoning") return [];
362
+ return [block.text];
363
+ }).join(""), [blocks, props.groupPart]);
364
+ const typewriter = live.mode === "typewriter" && !reduced;
365
+ const [draining, setDraining] = (0, react.useState)(running);
366
+ const revealing = typewriter && (running || draining);
367
+ const displayed = useConversationContent(source, {
368
+ enabled: revealing,
369
+ preset: live.preset,
370
+ steadyCps: live.revealCharsPerSec,
371
+ settleCps: Math.max(live.revealCharsPerSec * 4, 240)
372
+ });
373
+ (0, react.useEffect)(() => {
374
+ if (!typewriter) {
375
+ if (draining) setDraining(false);
376
+ return;
594
377
  }
595
- return;
596
- }
597
- const beforeMatch = () => {
598
- revealRef.current();
599
- };
600
- seat.addEventListener("beforematch", beforeMatch);
601
- const applyHidden = () => {
602
- if (seat.contains(seat.ownerDocument.activeElement)) {
603
- revealRef.current();
378
+ if (running) {
379
+ if (!draining) setDraining(true);
604
380
  return;
605
381
  }
606
- if (seat.getAttribute("hidden") !== "until-found") seat.setAttribute("hidden", "until-found");
607
- seat.setAttribute(OWNER_ATTR, "");
608
- };
609
- applyHidden();
610
- const observer = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(() => {
611
- applyHidden();
612
- });
613
- observer?.observe(seat, {
614
- attributes: true,
615
- attributeFilter: ["hidden"]
616
- });
617
- return () => {
618
- observer?.disconnect();
619
- seat.removeEventListener("beforematch", beforeMatch);
620
- if (!seat.hasAttribute(OWNER_ATTR)) return;
621
- seat.removeAttribute(OWNER_ATTR);
622
- seat.removeAttribute("hidden");
623
- };
624
- });
625
- return ref;
626
- }
627
- const VISIBLE_OWNER_ATTR = "data-codex-process-control";
628
- /** Keep DSH's normally inactive turn-process seat visible for the fallback. */
629
- function useChatSeatVisible(visible) {
630
- const rootRef = (0, react.useRef)(null);
631
- const ref = (0, react.useCallback)((element) => {
632
- rootRef.current = element;
633
- }, []);
634
- (0, react.useLayoutEffect)(() => {
635
- if (!visible) return;
636
- const seat = rootRef.current?.closest("[data-chat-anchor-key]");
637
- if (seat === null || seat === void 0) return;
638
- const applyVisible = () => {
639
- seat.setAttribute(VISIBLE_OWNER_ATTR, "");
640
- seat.removeAttribute("hidden");
641
- };
642
- applyVisible();
643
- const observer = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(() => {
644
- applyVisible();
645
- });
646
- observer?.observe(seat, {
647
- attributes: true,
648
- attributeFilter: ["hidden"]
649
- });
650
- return () => {
651
- observer?.disconnect();
652
- seat.removeAttribute(VISIBLE_OWNER_ATTR);
653
- };
654
- });
655
- return ref;
656
- }
657
- //#endregion
658
- //#region src/client/turnProcessFallback.ts
659
- const INDEPENDENT_KINDS = /* @__PURE__ */ new Set([
660
- "system-prompt",
661
- "user",
662
- "steering",
663
- "turn-trigger",
664
- "turn-process",
665
- "turn-error",
666
- "turn-max-tokens",
667
- "turn-tail"
668
- ]);
669
- function isChatNode(value) {
670
- if (value === null || typeof value !== "object") return false;
671
- const candidate = value;
672
- return typeof candidate.kind === "string" && typeof candidate.anchorSeq === "number" && candidate.location !== void 0 && candidate.data !== void 0;
673
- }
674
- /**
675
- * Prove that this Turn's actual start event is present in the loaded location.
676
- * A Chat row does not normally anchor at `turn/start`, so scanning row anchors
677
- * cannot establish this boundary; the shared Turn location is the authority.
678
- */
679
- function loadedProcessStart(node, turnProcess) {
680
- if (!isChatNode(node) || turnProcess === void 0) return false;
681
- const location = node.location;
682
- if (location.kind !== "turn" && location.kind !== "step") return false;
683
- return location.turn.start?.seq === turnProcess.spec.processStartSeq;
684
- }
685
- /**
686
- * Current DSH disables native folding for every loaded Turn while any older
687
- * history page remains. In Compact mode Codex presentation can safely fall
688
- * back only when the loaded window includes this Turn's start boundary.
689
- */
690
- function usesTurnProcessFallback(node, turnProcess, gate) {
691
- if (!gate.compactTranscript || !gate.historyIncomplete || !gate.processStartLoaded) return false;
692
- if (!isChatNode(node) || turnProcess === void 0 || turnProcess.foldable) return false;
693
- const location = node.location;
694
- if (location.kind !== "turn" && location.kind !== "step") return false;
695
- const spec = turnProcess.spec;
696
- if (location.turn.status !== "closed" || location.turn.end?.data.reason.kind !== "completed" || location.turn.turn !== spec.turn || spec.answerAnchorSeq === null) return false;
697
- return spec.inlineReasoning || spec.messageCount > 0 || spec.toolCallCount > 0 || spec.subagentCount > 0;
698
- }
699
- /** Whether this row belongs to the fallback's bounded process window. */
700
- function isFallbackProcessMember(node, turnProcess, gate) {
701
- if (!isChatNode(node) || !usesTurnProcessFallback(node, turnProcess, gate) || turnProcess === void 0) return false;
702
- const answerAnchor = turnProcess.spec.answerAnchorSeq;
703
- return answerAnchor !== null && !INDEPENDENT_KINDS.has(node.kind) && node.anchorSeq >= turnProcess.spec.processStartSeq && node.anchorSeq < answerAnchor;
704
- }
705
- /** Whether this Assistant is the finalized answer that can host fallback control. */
706
- function ownsFallbackProcessControl(node, turnProcess, gate) {
707
- return usesTurnProcessFallback(node, turnProcess, gate) && turnProcess?.spec.answerStep === node.data.step;
708
- }
709
- //#endregion
710
- //#region src/client/TranscriptViewBridge.tsx
711
- const CompactTranscriptContext = (0, react.createContext)(false);
712
- /** Read the authoritative DSH transcript preference inside Chat node seats. */
713
- function useCompactTranscript() {
714
- return (0, react.useContext)(CompactTranscriptContext);
715
- }
716
- /** Preserve the native Chat view while exposing its injected preference to descendants. */
717
- function wrapTranscriptView(Inner) {
718
- return function TranscriptViewBridge(props) {
719
- const compact = props.usePresentation((policy) => !policy.liveProcessDetail && policy.foldCompletedTurns);
720
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactTranscriptContext.Provider, {
721
- value: compact,
722
- children: (0, react.createElement)(Inner, props)
723
- });
724
- };
725
- }
726
- //#endregion
727
- //#region src/client/TypewriterAssistantNodeView.tsx
728
- function localPathMediaUrl(base, value) {
729
- let path;
730
- try {
731
- path = decodeURIComponent(value.split(/[?#]/u)[0] ?? "");
732
- } catch {
733
- return;
734
- }
735
- return fileMediaUrl(base, path);
736
- }
737
- function usePrefersReducedMotion() {
738
- const [reduced, setReduced] = (0, react.useState)(() => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true);
739
- (0, react.useEffect)(() => {
740
- if (typeof window === "undefined" || window.matchMedia === void 0) return;
741
- const query = window.matchMedia("(prefers-reduced-motion: reduce)");
742
- const onChange = () => setReduced(query.matches);
743
- query.addEventListener("change", onChange);
744
- return () => query.removeEventListener("change", onChange);
745
- }, []);
746
- return reduced;
747
- }
748
- /**
749
- * Smooth streaming text arm. While the reply runs, the accumulated source is
750
- * revealed through the smoother at a rate that tracks the model's arrival
751
- * and rendered by the Harness `MarkdownText`
752
- * streaming arm (incremental parse, frozen non-tail blocks), so there is no
753
- * raw-text tail and no text-to-markdown swap: the tree stays markdown
754
- * throughout. DSH ChatView remains the sole owner of viewport follow while
755
- * this component only smooths content reveal. Once the stream closes and the
756
- * reveal queue drains, the settled full parse (KaTeX math, fence highlighting, file
757
- * mentions) swaps in exactly once.
758
- */
759
- function AnimatedMarkdownText({ text, labels, fileMentions, pathImages, streaming, announce, ownFollow, mode, preset, revealCharsPerSec, scrollSpeedPxPerSec, maxScrollSpeedPxPerSec, shouldHoldBack }) {
760
- const reduced = usePrefersReducedMotion();
761
- const [typing, setTyping] = (0, react.useState)(streaming);
762
- const speedCpsRef = (0, react.useRef)(35);
763
- const displayed = useConversationContent(text, {
764
- enabled: mode === "typewriter" && typing && !reduced,
765
- preset,
766
- shouldHoldBack,
767
- steadyCps: mode === "typewriter" ? revealCharsPerSec : void 0,
768
- speedCpsRef
769
- });
770
- const shown = reduced || mode === "teleprompter" ? text : displayed;
771
- const live = typing && !reduced;
772
- (0, react.useEffect)(() => {
773
- if (typing && !streaming && shown.length === text.length) setTyping(false);
774
- }, [
775
- shown,
776
- streaming,
777
- text,
778
- typing
779
- ]);
780
- if (live) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [announce && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
781
- className: TypewriterAssistantNodeView_module_css_default.visuallyHidden,
782
- "aria-live": "polite",
783
- children: text
784
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowHost, {
785
- active: ownFollow,
786
- speedCpsRef,
787
- minSpeedPxPerSec: scrollSpeedPxPerSec,
788
- maxSpeedPxPerSec: maxScrollSpeedPxPerSec,
789
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
790
- className: TypewriterAssistantNodeView_module_css_default.markdownFlow,
791
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
792
- text: shown,
793
- streaming: true,
794
- labels,
795
- pathImages
796
- })
797
- })
798
- })] });
799
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
800
- className: TypewriterAssistantNodeView_module_css_default.markdownFlow,
801
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
802
- text,
803
- labels,
804
- fileMentions,
805
- pathImages
806
- })
807
- });
808
- }
809
- /**
810
- * Think is part of the Turn document flow rather than a second disclosure
811
- * card. It uses the same streaming smoother as ordinary assistant prose, so
812
- * reasoning reads as a continuous body while the surrounding Turn still owns
813
- * process folding and viewport follow.
814
- */
815
- function AnimatedReasoning({ text, labels, running, mode, preset, revealCharsPerSec, shouldHoldBack }) {
816
- const reduced = usePrefersReducedMotion();
817
- const [typing, setTyping] = (0, react.useState)(running);
818
- const speedCpsRef = (0, react.useRef)(35);
819
- const displayed = useConversationContent(text, {
820
- enabled: mode === "typewriter" && typing && !reduced,
821
- preset,
822
- shouldHoldBack,
823
- steadyCps: mode === "typewriter" ? revealCharsPerSec : void 0,
824
- speedCpsRef
825
- });
826
- const shown = reduced || mode === "teleprompter" ? text : displayed;
827
- (0, react.useEffect)(() => {
828
- if (typing && !running && shown.length === text.length) setTyping(false);
829
- }, [
830
- shown,
831
- running,
832
- text,
833
- typing
834
- ]);
835
- const live = typing && !reduced;
836
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
837
- className: TypewriterAssistantNodeView_module_css_default.reasoningText,
838
- "data-stream-node": "reasoning",
839
- "data-stream-state": running ? "running" : "settled",
840
- "data-stream-reasoning": "",
841
- children: live ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
842
- text: shown,
843
- streaming: true,
844
- labels
845
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
846
- text,
847
- labels
848
- })
849
- });
850
- }
851
- function ProcessReasoning({ hidden, reveal, autoExpand, t, children }) {
852
- const ref = useSearchableHidden(hidden, reveal);
853
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
854
- ref,
855
- "data-turn-process-inline": hidden || void 0,
856
- children: autoExpand ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
857
- className: TypewriterAssistantNodeView_module_css_default.reasoningDisclosure,
858
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
859
- className: TypewriterAssistantNodeView_module_css_default.reasoningSummary,
860
- children: t("message.turnProcess.thoughtForAWhile")
861
- }), children]
862
- })
863
- });
864
- }
865
- /**
866
- * Assistant renderer for the Codex-style event stream. Teleprompter mode
867
- * publishes each latest model snapshot immediately; typewriter mode reveals
868
- * complete grapheme clusters at the configured rate. Reasoning stays in the
869
- * same document flow as ordinary prose, and the final Assistant message is marked separately
870
- * from intermediate process updates. The FPS guard holds offscreen typewriter
871
- * commits when the frame rate is degraded. Settled text uses the full Markdown
872
- * pipeline.
873
- */
874
- const TypewriterAssistantNodeView = (0, react.memo)(function TypewriterAssistantNodeView({ mode = DEFAULT_CONVERSATION_CONFIG.mode, preset = DEFAULT_CONVERSATION_CONFIG.preset, revealCharsPerSec = DEFAULT_CONVERSATION_CONFIG.revealCharsPerSec, scrollSpeedPxPerSec = DEFAULT_CONVERSATION_CONFIG.scrollSpeedPxPerSec, maxScrollSpeedPxPerSec = DEFAULT_CONVERSATION_CONFIG.maxScrollSpeedPxPerSec, thinkAutoExpand = DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand, node, groupPart, turnProcess, useTurnData, useSession, openFile, renderMessageImages, fileMentions, t }) {
875
- const data = node.data;
876
- const streaming = data.status === "running";
877
- const reducedMotion = usePrefersReducedMotion();
878
- const { ref: guardRef, shouldHoldBack } = useFpsGuard(streaming);
879
- const rootSpeedRef = (0, react.useRef)(35);
880
- const turn = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn : void 0;
881
- const fallbackGate = {
882
- compactTranscript: useCompactTranscript(),
883
- historyIncomplete: useSession((snapshot) => snapshot.hasMore),
884
- processStartLoaded: loadedProcessStart(node, turnProcess)
885
- };
886
- const tail = useTurnData("turn-tail");
887
- const finalAnswer = data.status === "settled" && data.finalNode !== void 0 && tail?.closing?.finalNode.seq === data.finalNode.seq;
888
- const phase = streaming ? "streaming" : data.status === "interrupted" ? "interrupted" : finalAnswer ? "final" : "process";
889
- const owner = (0, react.useMemo)(() => {
890
- if (turn?.status !== "closed" || data.finalNode === void 0) return void 0;
891
- if (tail?.closing?.finalNode.seq !== data.finalNode.seq) return void 0;
892
- return {
893
- turn,
894
- seq: data.finalNode.seq,
895
- openFile
896
- };
897
- }, [
898
- data.finalNode,
899
- openFile,
900
- tail,
901
- turn
902
- ]);
903
- const mentions = (0, react.useMemo)(() => owner === void 0 ? void 0 : fileMentions(owner), [fileMentions, owner]);
904
- const labels = (0, react.useMemo)(() => ({
905
- code: {
906
- copyLabel: t("copy"),
907
- copiedLabel: t("copied")
908
- },
909
- footnotes: t("markdown.footnotes")
910
- }), [t]);
911
- const pathImages = (0, react.useMemo)(() => ({ resolve: (value) => localPathMediaUrl(document.baseURI, value) }), []);
912
- const blocks = (0, react.useMemo)(() => groupPart === "reasoning" ? data.blocks.filter((block) => block.kind === "reasoning") : groupPart === "response" ? data.blocks.filter((block) => block.kind !== "reasoning") : data.blocks, [data.blocks, groupPart]);
913
- const hasVisible = streaming || data.status === "interrupted" || blocks.some((block) => block.kind !== "tool-call");
914
- const fallbackControl = ownsFallbackProcessControl(node, turnProcess, fallbackGate);
915
- const fallbackMember = isFallbackProcessMember(node, turnProcess, fallbackGate);
916
- const reasoningHidden = (turnProcess?.foldable === true || fallbackControl) && turnProcess?.spec.answerStep === data.step && turnProcess.spec.inlineReasoning && !turnProcess.open;
917
- const fallbackHidden = fallbackMember && turnProcess?.open === false;
918
- const revealProcess = (0, react.useCallback)(() => {
919
- turnProcess?.setOpen(true);
920
- }, [turnProcess]);
921
- const seatRef = useChatSeatHidden(fallbackHidden, revealProcess);
922
- const rootRef = (0, react.useCallback)((element) => {
923
- guardRef(element);
924
- seatRef(element);
925
- }, [guardRef, seatRef]);
926
- if (!hasVisible) return null;
927
- const rendered = [];
928
- const last = blocks.length - 1;
929
- let lastFollow = -1;
930
- for (let index = 0; index < blocks.length; index += 1) {
931
- const kind = blocks[index]?.kind;
932
- if (kind === "text" || kind === "reasoning") lastFollow = index;
933
- }
934
- let nodeTailBlock;
935
- for (let index = data.blocks.length - 1; index >= 0; index -= 1) {
936
- const kind = data.blocks[index]?.kind;
937
- if (kind === "text" || kind === "reasoning") {
938
- nodeTailBlock = data.blocks[index];
939
- break;
940
- }
941
- }
942
- const followsTail = lastFollow >= 0 && lastFollow === last && blocks[lastFollow] === nodeTailBlock;
943
- const stoppedHere = data.status === "interrupted" && (groupPart === void 0 || groupPart === "response" || !data.blocks.some((block) => block !== void 0 && block.kind !== "reasoning" && block.kind !== "tool-call"));
944
- for (let index = 0; index < blocks.length; index += 1) {
945
- const block = blocks[index];
946
- if (block === void 0) continue;
947
- switch (block.kind) {
948
- case "text":
949
- rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedMarkdownText, {
950
- text: block.text,
951
- labels,
952
- fileMentions: mentions,
953
- pathImages,
954
- streaming,
955
- announce: index === last,
956
- ownFollow: !streaming && index === lastFollow,
957
- mode,
958
- preset,
959
- revealCharsPerSec,
960
- scrollSpeedPxPerSec,
961
- maxScrollSpeedPxPerSec,
962
- shouldHoldBack
963
- }, index));
964
- break;
965
- case "reasoning":
966
- rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProcessReasoning, {
967
- hidden: reasoningHidden,
968
- reveal: revealProcess,
969
- autoExpand: thinkAutoExpand || !streaming,
970
- t,
971
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedReasoning, {
972
- text: block.text,
973
- labels,
974
- running: streaming && index === last,
975
- mode,
976
- preset,
977
- revealCharsPerSec,
978
- shouldHoldBack
382
+ if (draining && displayed.length >= source.length) setDraining(false);
383
+ }, [
384
+ displayed.length,
385
+ draining,
386
+ running,
387
+ source.length,
388
+ typewriter
389
+ ]);
390
+ const shown = revealing ? displayed : source;
391
+ const node = (0, react.useMemo)(() => {
392
+ if (shown === source) return props.node;
393
+ let remaining = shown.length;
394
+ return {
395
+ ...props.node,
396
+ data: {
397
+ ...props.node.data,
398
+ blocks: blocks.map((block) => {
399
+ if (block.kind !== "text" && block.kind !== "reasoning") return block;
400
+ if (props.groupPart === "reasoning" && block.kind !== "reasoning") return block;
401
+ if (props.groupPart === "response" && block.kind === "reasoning") return block;
402
+ const text = block.text.slice(0, remaining);
403
+ remaining = Math.max(0, remaining - block.text.length);
404
+ return text === block.text ? block : {
405
+ ...block,
406
+ text
407
+ };
979
408
  })
980
- }, index));
981
- break;
982
- case "image": {
983
- const start = index;
984
- const group = [block];
985
- while (index + 1 < blocks.length) {
986
- const next = blocks[index + 1];
987
- if (next === void 0 || next.kind !== "image") break;
988
- group.push(next);
989
- index += 1;
990
409
  }
991
- rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: renderMessageImages({
992
- images: group,
993
- align: "start"
994
- }) }, start));
995
- break;
996
- }
997
- case "tool-call": break;
998
- case "other": rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
999
- label: t("message.unknownBlock"),
1000
- payload: block.block,
1001
- truncatedLabel: (total) => t("json.truncated", { total })
1002
- }, index));
1003
- }
1004
- }
1005
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1006
- ref: rootRef,
1007
- className: TypewriterAssistantNodeView_module_css_default.root,
1008
- "data-stream-node": "assistant-step",
1009
- "data-stream-phase": phase,
1010
- "data-streaming": streaming || void 0,
1011
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowHost, {
1012
- active: streaming && !reducedMotion && followsTail,
1013
- speedCpsRef: rootSpeedRef,
1014
- minSpeedPxPerSec: scrollSpeedPxPerSec,
1015
- maxSpeedPxPerSec: maxScrollSpeedPxPerSec,
1016
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1017
- className: TypewriterAssistantNodeView_module_css_default.body,
1018
- children: [rendered, stoppedHere && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1019
- className: TypewriterAssistantNodeView_module_css_default.stopped,
1020
- children: t("message.stopped")
1021
- })]
1022
- })
1023
- })
1024
- });
1025
- });
1026
- //#endregion
1027
- //#region src/client/turnElapsed.ts
1028
- function formatRunDuration(ms, t) {
1029
- const total = Math.max(0, Math.floor(ms / 1e3));
1030
- const hours = Math.floor(total / 3600);
1031
- const minutes = Math.floor(total / 60) % 60;
1032
- const seconds = total % 60;
1033
- const parts = [];
1034
- if (hours > 0) parts.push(String(hours), t("duration.hourUnit"));
1035
- if (total >= 60) parts.push(String(minutes), t("duration.minuteUnit"));
1036
- parts.push(String(seconds), t("duration.secondUnit"));
1037
- return parts.join("");
1038
- }
1039
- function formatTurnElapsed(ms, t) {
1040
- const duration = formatRunDuration(ms, t);
1041
- const label = t("message.turnProcess.took");
1042
- return `${label === "已完成,用时 " ? "耗时 " : label}${duration}`;
1043
- }
1044
- //#endregion
1045
- //#region src/client/CodexTurnProcessNodeView.tsx
1046
- /** Shared Codex disclosure button for native and paginated-history fallback seats. */
1047
- function CodexTurnProcessControl({ turn, messageCount, toolCallCount, subagentCount, elapsedMs, reason, canCollapse, turnProcess, t, fallback = false, rootRef }) {
1048
- const label = reason === "aborted" ? t("message.stopped") : reason === "error" ? t("message.turnProcess.failed") : elapsedMs === void 0 ? t("message.turnProcess.worked") : formatTurnElapsed(Math.max(1e3, elapsedMs), t);
1049
- const announcement = reason === "aborted" ? t("message.stopped") : reason === "error" ? t("message.turnProcess.failed") : t("message.turnProcess.worked");
1050
- const open = !canCollapse || turnProcess.open;
1051
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1052
- className: TypewriterAssistantNodeView_module_css_default.visuallyHidden,
1053
- role: "status",
1054
- "aria-live": "polite",
1055
- "aria-atomic": "true",
1056
- children: announcement
1057
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1058
- ref: rootRef,
1059
- className: TypewriterAssistantNodeView_module_css_default.turnFoldRow,
1060
- "data-turn-fold-row": "",
1061
- "data-turn-fold-state": reason === "aborted" ? "stopped" : reason === "error" ? "failed" : "completed",
1062
- "data-turn-process-fallback": fallback || void 0,
1063
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1064
- type: "button",
1065
- className: TypewriterAssistantNodeView_module_css_default.turnFoldButton,
1066
- "data-open": open || void 0,
1067
- "data-turn-process": turn,
1068
- "data-turn-process-messages": messageCount,
1069
- "data-turn-process-tool-calls": toolCallCount,
1070
- "data-turn-process-subagents": subagentCount,
1071
- disabled: !canCollapse,
1072
- "aria-expanded": turnProcess.hasContent || fallback ? open : void 0,
1073
- onClick: (event) => {
1074
- event.currentTarget.focus();
1075
- turnProcess.setOpen(!open);
1076
- },
1077
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), canCollapse && (open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineMedium, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineMedium, {}))]
1078
- })
1079
- })] });
1080
- }
1081
- /** Codex-style presentation over DSH's authoritative Turn-process state. */
1082
- const CodexTurnProcessNodeView = (0, react.memo)(function CodexTurnProcessNodeView({ node, turnProcess, useSession, t }) {
1083
- if (turnProcess === void 0) throw new Error("turn-process node requires DSH turnProcess owner state");
1084
- const fallback = usesTurnProcessFallback(node, turnProcess, {
1085
- compactTranscript: useCompactTranscript(),
1086
- historyIncomplete: useSession((snapshot) => snapshot.hasMore),
1087
- processStartLoaded: loadedProcessStart(node, turnProcess)
1088
- });
1089
- const seatRef = useChatSeatVisible(fallback);
1090
- const location = node.location;
1091
- const turn = location.kind === "turn" || location.kind === "step" ? location.turn : void 0;
1092
- if (turn?.status !== "closed") return null;
1093
- const reason = turn.end?.data.reason.kind;
1094
- const canCollapse = turnProcess.foldable && turnProcess.hasContent && reason !== "aborted" && reason !== "error" || fallback;
1095
- const elapsedMs = turn?.start !== void 0 && turn.end !== void 0 ? turn.end.time - turn.start.time : void 0;
1096
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexTurnProcessControl, {
1097
- turn: node.data.turn,
1098
- messageCount: node.data.messageCount,
1099
- toolCallCount: node.data.toolCallCount,
1100
- subagentCount: node.data.subagentCount,
1101
- elapsedMs,
1102
- reason,
1103
- canCollapse,
1104
- turnProcess,
1105
- t,
1106
- fallback,
1107
- rootRef: seatRef
1108
- });
1109
- });
1110
- //#endregion
1111
- //#region ../../node_modules/.pnpm/react-icons@5.5.0_react@18.3.1/node_modules/react-icons/lib/iconContext.mjs
1112
- var DefaultContext = {
1113
- color: void 0,
1114
- size: void 0,
1115
- className: void 0,
1116
- style: void 0,
1117
- attr: void 0
1118
- };
1119
- var IconContext = react.default.createContext && /*#__PURE__*/ react.default.createContext(DefaultContext);
1120
- //#endregion
1121
- //#region ../../node_modules/.pnpm/react-icons@5.5.0_react@18.3.1/node_modules/react-icons/lib/iconBase.mjs
1122
- var _excluded = [
1123
- "attr",
1124
- "size",
1125
- "title"
1126
- ];
1127
- function _objectWithoutProperties(source, excluded) {
1128
- if (source == null) return {};
1129
- var target = _objectWithoutPropertiesLoose(source, excluded);
1130
- var key, i;
1131
- if (Object.getOwnPropertySymbols) {
1132
- var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
1133
- for (i = 0; i < sourceSymbolKeys.length; i++) {
1134
- key = sourceSymbolKeys[i];
1135
- if (excluded.indexOf(key) >= 0) continue;
1136
- if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
1137
- target[key] = source[key];
1138
- }
1139
- }
1140
- return target;
1141
- }
1142
- function _objectWithoutPropertiesLoose(source, excluded) {
1143
- if (source == null) return {};
1144
- var target = {};
1145
- for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) {
1146
- if (excluded.indexOf(key) >= 0) continue;
1147
- target[key] = source[key];
1148
- }
1149
- return target;
1150
- }
1151
- function _extends() {
1152
- _extends = Object.assign ? Object.assign.bind() : function(target) {
1153
- for (var i = 1; i < arguments.length; i++) {
1154
- var source = arguments[i];
1155
- for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
1156
- }
1157
- return target;
1158
- };
1159
- return _extends.apply(this, arguments);
1160
- }
1161
- function ownKeys(e, r) {
1162
- var t = Object.keys(e);
1163
- if (Object.getOwnPropertySymbols) {
1164
- var o = Object.getOwnPropertySymbols(e);
1165
- r && (o = o.filter(function(r) {
1166
- return Object.getOwnPropertyDescriptor(e, r).enumerable;
1167
- })), t.push.apply(t, o);
1168
- }
1169
- return t;
1170
- }
1171
- function _objectSpread(e) {
1172
- for (var r = 1; r < arguments.length; r++) {
1173
- var t = null != arguments[r] ? arguments[r] : {};
1174
- r % 2 ? ownKeys(Object(t), !0).forEach(function(r) {
1175
- _defineProperty(e, r, t[r]);
1176
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r) {
1177
- Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
410
+ };
411
+ }, [
412
+ blocks,
413
+ props.groupPart,
414
+ props.node,
415
+ shown,
416
+ source
417
+ ]);
418
+ const originalDisclosure = props.useDisclosure;
419
+ const useDisclosure = (0, react.useCallback)(() => {
420
+ const disclosure = originalDisclosure();
421
+ const { setExpanded } = disclosure;
422
+ (0, react.useEffect)(() => {
423
+ if (running) setExpanded(autoExpand);
424
+ }, [
425
+ autoExpand,
426
+ running,
427
+ setExpanded
428
+ ]);
429
+ return disclosure;
430
+ }, [
431
+ autoExpand,
432
+ originalDisclosure,
433
+ running
434
+ ]);
435
+ return (0, react.createElement)(NativeAssistant, {
436
+ ...props,
437
+ node,
438
+ useDisclosure
1178
439
  });
1179
440
  }
1180
- return e;
1181
- }
1182
- function _defineProperty(obj, key, value) {
1183
- key = _toPropertyKey(key);
1184
- if (key in obj) Object.defineProperty(obj, key, {
1185
- value,
1186
- enumerable: true,
1187
- configurable: true,
1188
- writable: true
1189
- });
1190
- else obj[key] = value;
1191
- return obj;
1192
- }
1193
- function _toPropertyKey(t) {
1194
- var i = _toPrimitive(t, "string");
1195
- return "symbol" == typeof i ? i : i + "";
1196
- }
1197
- function _toPrimitive(t, r) {
1198
- if ("object" != typeof t || !t) return t;
1199
- var e = t[Symbol.toPrimitive];
1200
- if (void 0 !== e) {
1201
- var i = e.call(t, r || "default");
1202
- if ("object" != typeof i) return i;
1203
- throw new TypeError("@@toPrimitive must return a primitive value.");
1204
- }
1205
- return ("string" === r ? String : Number)(t);
1206
- }
1207
- function Tree2Element(tree) {
1208
- return tree && tree.map((node, i) => /*#__PURE__*/ react.default.createElement(node.tag, _objectSpread({ key: i }, node.attr), Tree2Element(node.child)));
1209
- }
1210
- function GenIcon(data) {
1211
- return (props) => /*#__PURE__*/ react.default.createElement(IconBase, _extends({ attr: _objectSpread({}, data.attr) }, props), Tree2Element(data.child));
1212
- }
1213
- function IconBase(props) {
1214
- var elem = (conf) => {
1215
- var { attr, size, title } = props, svgProps = _objectWithoutProperties(props, _excluded);
1216
- var computedSize = size || conf.size || "1em";
1217
- var className;
1218
- if (conf.className) className = conf.className;
1219
- if (props.className) className = (className ? className + " " : "") + props.className;
1220
- return /*#__PURE__*/ react.default.createElement("svg", _extends({
1221
- stroke: "currentColor",
1222
- fill: "currentColor",
1223
- strokeWidth: "0"
1224
- }, conf.attr, attr, svgProps, {
1225
- className,
1226
- style: _objectSpread(_objectSpread({ color: props.color || conf.color }, conf.style), props.style),
1227
- height: computedSize,
1228
- width: computedSize,
1229
- xmlns: "http://www.w3.org/2000/svg"
1230
- }), title && /*#__PURE__*/ react.default.createElement("title", null, title), props.children);
1231
- };
1232
- return IconContext !== void 0 ? /*#__PURE__*/ react.default.createElement(IconContext.Consumer, null, (conf) => elem(conf)) : elem(DefaultContext);
1233
- }
1234
- //#endregion
1235
- //#region ../../node_modules/.pnpm/react-icons@5.5.0_react@18.3.1/node_modules/react-icons/fi/index.mjs
1236
- function FiTool(props) {
1237
- return GenIcon({
1238
- "tag": "svg",
1239
- "attr": {
1240
- "viewBox": "0 0 24 24",
1241
- "fill": "none",
1242
- "stroke": "currentColor",
1243
- "strokeWidth": "2",
1244
- "strokeLinecap": "round",
1245
- "strokeLinejoin": "round"
1246
- },
1247
- "child": [{
1248
- "tag": "path",
1249
- "attr": { "d": "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" },
1250
- "child": []
1251
- }]
1252
- })(props);
1253
- }
1254
- //#endregion
1255
- //#region src/client/ToolActivityGroup.tsx
1256
- const groupStates = /* @__PURE__ */ new Map();
1257
- const groupMounts = /* @__PURE__ */ new Map();
1258
- function groupState(id) {
1259
- const current = groupStates.get(id);
1260
- if (current !== void 0) return current;
1261
- const created = {
1262
- collapsed: false,
1263
- listeners: /* @__PURE__ */ new Set()
1264
- };
1265
- groupStates.set(id, created);
1266
- return created;
1267
- }
1268
- function groupMount(id) {
1269
- const current = groupMounts.get(id);
1270
- if (current !== void 0) return current;
1271
- const created = {
1272
- target: null,
1273
- listeners: /* @__PURE__ */ new Set()
1274
- };
1275
- groupMounts.set(id, created);
1276
- return created;
1277
- }
1278
- function notify(listeners) {
1279
- for (const listener of listeners) listener();
1280
- }
1281
- function subscribeGroup(id, listener) {
1282
- const state = groupState(id);
1283
- state.listeners.add(listener);
1284
- return () => {
1285
- state.listeners.delete(listener);
1286
- if (state.listeners.size === 0) groupStates.delete(id);
1287
- };
1288
- }
1289
- function subscribeMount(id, listener) {
1290
- const mount = groupMount(id);
1291
- mount.listeners.add(listener);
1292
- return () => {
1293
- mount.listeners.delete(listener);
1294
- if (mount.listeners.size === 0 && mount.target === null) groupMounts.delete(id);
1295
- };
1296
- }
1297
- function setMount(id, target) {
1298
- const mount = groupMount(id);
1299
- if (mount.target === target) return;
1300
- mount.target = target;
1301
- notify(mount.listeners);
1302
- }
1303
- function hash(value) {
1304
- let result = 2166136261;
1305
- for (let index = 0; index < value.length; index += 1) {
1306
- result ^= value.charCodeAt(index);
1307
- result = Math.imul(result, 16777619);
1308
- }
1309
- return (result >>> 0).toString(36);
1310
- }
1311
- /** Stable identity for one contiguous semantic tool group in a Turn. */
1312
- function toolActivityGroupId(sessionId, turn, semantic, firstNodeKey) {
1313
- return `dsh-tool-group-${hash(`${sessionId}\u0000${turn}\u0000${semantic}\u0000${firstNodeKey}`)}`;
1314
- }
1315
- function useToolActivityFold(id) {
1316
- const subscribe = (0, react.useCallback)((listener) => subscribeGroup(id, listener), [id]);
1317
- return {
1318
- collapsed: (0, react.useSyncExternalStore)(subscribe, () => groupState(id).collapsed, () => false),
1319
- toggle: (0, react.useCallback)(() => {
1320
- const state = groupState(id);
1321
- state.collapsed = !state.collapsed;
1322
- notify(state.listeners);
1323
- }, [id]),
1324
- collapse: (0, react.useCallback)(() => {
1325
- const state = groupState(id);
1326
- if (state.collapsed) return;
1327
- state.collapsed = true;
1328
- notify(state.listeners);
1329
- }, [id]),
1330
- expand: (0, react.useCallback)(() => {
1331
- const state = groupState(id);
1332
- if (!state.collapsed) return;
1333
- state.collapsed = false;
1334
- notify(state.listeners);
1335
- }, [id])
1336
- };
1337
- }
1338
- function useToolActivityMount(id) {
1339
- const subscribe = (0, react.useCallback)((listener) => subscribeMount(id, listener), [id]);
1340
- return (0, react.useSyncExternalStore)(subscribe, () => groupMount(id).target, () => null);
1341
- }
1342
- function ToolActivityGroupFrame({ group, collapsed, toggle, header, children }) {
1343
- const register = (0, react.useCallback)((element) => {
1344
- setMount(group.id, element);
1345
- }, [group.id]);
1346
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1347
- ref: register,
1348
- id: group.domId,
1349
- className: TypewriterAssistantNodeView_module_css_default.toolGroupFrame,
1350
- "data-stream-tool-group": group.semantic,
1351
- "data-stream-tool-group-frame": "",
1352
- "data-stream-tool-group-position": group.position,
1353
- "data-stream-tool-group-count": group.count,
1354
- "data-stream-tool-group-collapsed": collapsed || void 0,
1355
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1356
- type: "button",
1357
- className: TypewriterAssistantNodeView_module_css_default.toolGroupHeader,
1358
- "aria-expanded": !collapsed,
1359
- "aria-controls": group.domId,
1360
- "data-stream-tool-group-toggle": "",
1361
- onClick: toggle,
1362
- children: [header, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1363
- className: TypewriterAssistantNodeView_module_css_default.toolGroupHeaderChevron,
1364
- "aria-hidden": true,
1365
- children: collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineMedium, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineMedium, { size: 14 })
1366
- })]
1367
- }), children]
1368
- });
1369
- }
1370
- function SearchableActivityContent({ hidden, reveal, member = false, children }) {
1371
- const ref = useSearchableHidden(hidden, reveal);
1372
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1373
- ref,
1374
- className: TypewriterAssistantNodeView_module_css_default.toolGroupContent,
1375
- "data-tool-group-member": member || void 0,
1376
- "data-tool-group-hidden": hidden || void 0,
1377
- children
1378
- });
1379
- }
1380
- /**
1381
- * Mount the first member as a group host and portal subsequent tool members
1382
- * into the same host. DSH's outer Chat Node seat owns Turn visibility while a
1383
- * contiguous run of commands keeps its own Codex-style disclosure block.
1384
- */
1385
- function ToolActivityGroupMember({ group, memberRef, eventAttributes, header, children }) {
1386
- const activity = useToolActivityFold(group.id);
1387
- const mount = useToolActivityMount(group.id);
1388
- (0, react.useEffect)(() => {
1389
- if (group.position === "start" && !group.hasGrowing) activity.collapse();
1390
- }, [
1391
- activity.collapse,
1392
- group.hasGrowing,
1393
- group.position
1394
- ]);
1395
- if (group.position === "start") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1396
- className: TypewriterAssistantNodeView_module_css_default.toolGroupAnchor,
1397
- "data-stream-tool-group-anchor": "",
1398
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolActivityGroupFrame, {
1399
- group,
1400
- collapsed: activity.collapsed,
1401
- toggle: activity.toggle,
1402
- header,
1403
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchableActivityContent, {
1404
- hidden: activity.collapsed,
1405
- reveal: activity.expand,
1406
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1407
- ...eventAttributes,
1408
- ref: memberRef,
1409
- className: TypewriterAssistantNodeView_module_css_default.eventRow,
1410
- "data-stream-tool-group-member": "",
1411
- "data-stream-tool-group-position": "start",
1412
- "data-stream-tool-group-collapsed": activity.collapsed || void 0,
1413
- children
1414
- })
1415
- })
1416
- })
1417
- });
1418
- const member = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchableActivityContent, {
1419
- hidden: activity.collapsed,
1420
- reveal: activity.expand,
1421
- member: true,
1422
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1423
- ...eventAttributes,
1424
- ref: memberRef,
1425
- className: TypewriterAssistantNodeView_module_css_default.eventRow,
1426
- "data-stream-tool-group-member": "",
1427
- "data-stream-tool-group-position": group.position,
1428
- "data-stream-tool-group-collapsed": activity.collapsed || void 0,
1429
- children
1430
- })
1431
- });
1432
- if (mount === null) return member;
1433
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1434
- "data-tool-group-proxy": "",
1435
- "aria-hidden": true,
1436
- children: (0, react_dom.createPortal)(member, mount)
441
+ return (props) => (0, react.createElement)(EnhancedAssistant, {
442
+ ...props,
443
+ key: `${props.sessionId}:${props.node.key}:${props.groupPart ?? ""}`
1437
444
  });
1438
445
  }
1439
- /** Render the group heading with the semantic icon supplied by the tool view. */
1440
- function ToolActivityGroupHeader({ label, icon }) {
1441
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1442
- className: TypewriterAssistantNodeView_module_css_default.toolGroupHeaderIcon,
1443
- "aria-hidden": true,
1444
- children: icon
1445
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1446
- className: TypewriterAssistantNodeView_module_css_default.toolGroupHeaderLabel,
1447
- children: label
1448
- })] });
1449
- }
1450
- //#endregion
1451
- //#region src/client/TypewriterToolNodeView.tsx
1452
- const TOOL_ICONS = {
1453
- search: _deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineMedium,
1454
- read: _deepseek_ai_dsh_client_ui_primitives.IconBrowseOutlineMedium,
1455
- edit: _deepseek_ai_dsh_client_ui_primitives.IconEditOutlineMedium,
1456
- terminal: _deepseek_ai_dsh_client_ui_primitives.IconApiOutlineMedium,
1457
- data: _deepseek_ai_dsh_client_ui_primitives.IconDataOutlineMedium,
1458
- web: _deepseek_ai_dsh_client_ui_primitives.IconGlobeOutlineMedium,
1459
- code: FiTool,
1460
- skill: _deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineMedium,
1461
- agent: _deepseek_ai_dsh_client_ui_primitives.IconAgentPresetOutlineMedium,
1462
- plugin: _deepseek_ai_dsh_client_ui_primitives.IconCordisPluginOutlineMedium,
1463
- question: _deepseek_ai_dsh_client_ui_primitives.IconQuestionOutlineMedium,
1464
- settings: _deepseek_ai_dsh_client_ui_primitives.IconSettingsOutlineMedium,
1465
- checklist: _deepseek_ai_dsh_client_ui_primitives.IconChecklistOutlineMedium,
1466
- other: FiTool
1467
- };
1468
- const CONVERSATION_NODE_ICONS = { context: _deepseek_ai_dsh_client_ui_primitives.IconBrowseOutlineMedium };
1469
- function toolTokens(toolName) {
1470
- return toolName.replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z\d]+/).filter(Boolean);
1471
- }
1472
446
  /**
1473
- * Map a wire Tool name onto the closest Harness icon family. Custom plugins
1474
- * do not have to register UI code: descriptive names such as
1475
- * `smops_knowledge_search` still receive a meaningful search glyph.
447
+ * Enhance the winning assistant renderer in place. dsh 0.2.1 gives the native
448
+ * assistant entry its own child slots (reasoning body, message images), which
449
+ * a separate shadowing registration could not carry; swapping the entry's
450
+ * component keeps children, inject, store, and locale seats untouched while
451
+ * the enhancement delegates every visual to the native renderer.
452
+ * @param ctx - browser context with the slot registry.
453
+ * @param preference - live reveal and auto-expand preferences.
454
+ * @returns disposer restoring the original component.
1476
455
  */
1477
- function semanticToolKind(toolName) {
1478
- const tokens = toolTokens(toolName);
1479
- const has = (...terms) => terms.some((term) => tokens.includes(term));
1480
- if (has("skill", "skills")) return "skill";
1481
- if (has("agent", "subagent", "delegate")) return "agent";
1482
- if (has("cordis", "plugin", "plugins")) return "plugin";
1483
- if (has("question", "ask", "clarify", "input")) return "question";
1484
- if (has("scope", "config", "settings", "setting")) return "settings";
1485
- if (has("database", "db", "sql", "schema", "table", "record", "records", "dataset", "memory")) return "data";
1486
- if (has("plan", "todo", "task", "checklist", "goal")) return "checklist";
1487
- if (has("edit", "write", "patch", "replace", "create", "delete", "remove", "move", "copy")) return "edit";
1488
- if (has("bash", "shell", "terminal", "exec", "command", "cmd", "powershell", "pwsh")) return "terminal";
1489
- if (has("web", "browser", "url", "http", "https", "crawl", "scrape")) return "web";
1490
- if (has("read", "load", "open", "file", "files")) return "read";
1491
- if (has("search", "find", "grep", "glob", "query", "lookup", "knowledge", "retrieve", "index", "log", "logs", "trace")) return "search";
1492
- if (has("code", "codegraph", "graph", "python", "javascript", "typescript", "node", "program")) return "code";
1493
- return "other";
1494
- }
1495
- function toolPresentation(node) {
1496
- if (node === null || typeof node !== "object" || !("data" in node)) return null;
1497
- const data = node.data;
1498
- if (data === null || typeof data !== "object" || !("root" in data)) return null;
1499
- const root = data.root;
1500
- if (root === null || typeof root !== "object") return null;
1501
- const name = "name" in root && typeof root.name === "string" ? root.name : "call" in root && root.call !== null && typeof root.call === "object" && "name" in root.call && typeof root.call.name === "string" ? root.call.name : "";
1502
- if (name === "") return null;
1503
- const isError = "isError" in root && root.isError === true || "error" in root && root.error !== null && root.error !== void 0;
1504
- const errorCode = "error" in root && root.error !== null && typeof root.error === "object" && "code" in root.error && typeof root.error.code === "string" ? root.error.code.toLowerCase() : "";
1505
- const stopped = /cancel|interrupt|abort|stop/.test(errorCode);
1506
- return {
1507
- name,
1508
- semantic: semanticToolKind(name),
1509
- state: stopped ? "stopped" : isError ? "error" : null
1510
- };
1511
- }
1512
- function diffLineCount(value) {
1513
- if (typeof value !== "string" || value === "") return 0;
1514
- const body = value.endsWith("\n") ? value.slice(0, -1) : value.endsWith("\\n") ? value.slice(0, -2) : value;
1515
- return body === "" ? 0 : body.split(/\r?\n|\\n/).length;
1516
- }
1517
- /**
1518
- * Derive the compact +/- suffix shown beside an edit file path. The native
1519
- * edit card already owns the actual diff body; this only mirrors its counts
1520
- * into the collapsed summary row.
1521
- */
1522
- function editDiffFilePath(node) {
1523
- const presentation = toolPresentation(node);
1524
- if (presentation?.semantic !== "edit" || presentation.state !== null || node === null || typeof node !== "object") return void 0;
1525
- const data = node.data;
1526
- if (data === null || typeof data !== "object" || !("root" in data)) return void 0;
1527
- const root = data.root;
1528
- if (root === null || typeof root !== "object") return void 0;
1529
- const view = "resultView" in root ? root.resultView ?? root.callView : "callView" in root ? root.callView : void 0;
1530
- if (view === null || typeof view !== "object" || view.card !== "diff") return void 0;
1531
- const diffs = view.diffs;
1532
- if (!Array.isArray(diffs)) return void 0;
1533
- const first = diffs.find((diff) => diff !== null && typeof diff === "object" && typeof diff.path === "string");
1534
- return first === void 0 ? void 0 : first.path;
1535
- }
1536
- function editDiffStats(node) {
1537
- const presentation = toolPresentation(node);
1538
- if (presentation?.semantic !== "edit" || presentation.state !== null || node === null || typeof node !== "object") return void 0;
1539
- const data = node.data;
1540
- if (data === null || typeof data !== "object" || !("root" in data)) return void 0;
1541
- const root = data.root;
1542
- if (root === null || typeof root !== "object") return void 0;
1543
- const view = "resultView" in root ? root.resultView ?? root.callView : "callView" in root ? root.callView : void 0;
1544
- if (view === null || typeof view !== "object" || view.card !== "diff") return void 0;
1545
- const diffs = view.diffs;
1546
- if (!Array.isArray(diffs)) return void 0;
1547
- let added = 0;
1548
- let removed = 0;
1549
- for (const diff of diffs) {
1550
- if (diff === null || typeof diff !== "object") continue;
1551
- added += diffLineCount(diff.newText);
1552
- removed += diffLineCount(diff.oldText);
1553
- }
1554
- return added === 0 && removed === 0 ? void 0 : `+${added} -${removed}`;
1555
- }
1556
- const TOOL_GROUP_LABELS = {
1557
- search: "搜索",
1558
- read: "读取了文件",
1559
- edit: "编辑了文件",
1560
- terminal: "运行了命令",
1561
- data: "查询了数据",
1562
- web: "浏览了网页",
1563
- code: "运行了代码",
1564
- skill: "使用了技能",
1565
- agent: "调用了智能体",
1566
- plugin: "调用了插件",
1567
- question: "发起了提问",
1568
- settings: "更新了设置",
1569
- checklist: "更新了清单",
1570
- other: "执行了操作"
1571
- };
1572
- function visibleNode(node) {
1573
- if (node === null || typeof node !== "object") return false;
1574
- return !("visibility" in node) || node.visibility === "visible";
1575
- }
1576
- function chatParts(snapshot) {
1577
- if (snapshot === null || typeof snapshot !== "object") return void 0;
1578
- const candidate = "chat" in snapshot ? snapshot.chat : snapshot;
1579
- if (candidate === null || typeof candidate !== "object" || !("order" in candidate) || !("nodes" in candidate)) return void 0;
1580
- const order = candidate.order;
1581
- const nodes = candidate.nodes;
1582
- if (!Array.isArray(order) || nodes === null || typeof nodes !== "object" || !("get" in nodes)) return void 0;
1583
- const get = nodes.get;
1584
- if (typeof get !== "function") return void 0;
1585
- return {
1586
- order,
1587
- get: (key) => get.call(nodes, key),
1588
- timeline: "timeline" in candidate ? candidate.timeline : void 0
1589
- };
1590
- }
1591
- function sameActivityGroupNode(node, nodeKind, turn, semantic, requiresTurn) {
1592
- return visibleNode(node) && chatNodeKind(node) === nodeKind && (!requiresTurn || turnDetailsFromNode(node)?.turn === turn) && (nodeKind !== "tool-call" || toolPresentation(node)?.semantic === semantic);
1593
- }
1594
- function activityGroup(snapshot, currentKey, sessionId, turn, nodeKind, semantic, label, requiresTurn) {
1595
- const chat = chatParts(snapshot);
1596
- if (chat === void 0) return void 0;
1597
- const currentIndex = chat.order.indexOf(currentKey);
1598
- if (currentIndex === -1) return void 0;
1599
- const read = (index) => {
1600
- const key = chat.order[index];
1601
- return typeof key === "string" ? chat.get(key) : void 0;
456
+ function registerAssistantEnhancement(ctx, preference) {
457
+ let target;
458
+ let original;
459
+ const restore = () => {
460
+ if (target !== void 0) target.component = original;
461
+ target = void 0;
462
+ original = void 0;
1602
463
  };
1603
- const same = (node) => sameActivityGroupNode(node, nodeKind, turn, semantic, requiresTurn);
1604
- if (!same(read(currentIndex))) return void 0;
1605
- let first = currentIndex;
1606
- let last = currentIndex;
1607
- while (first > 0 && same(read(first - 1))) first -= 1;
1608
- while (last + 1 < chat.order.length && same(read(last + 1))) last += 1;
1609
- const count = last - first + 1;
1610
- if (count < 2) return void 0;
1611
- const firstKey = chat.order[first];
1612
- if (typeof firstKey !== "string") return void 0;
1613
- let hasGrowing = false;
1614
- for (let index = first; index <= last; index += 1) if (isGrowingChatNode(read(index))) {
1615
- hasGrowing = true;
1616
- break;
1617
- }
1618
- const groupId = toolActivityGroupId(sessionId, turn, semantic, firstKey);
1619
- return {
1620
- id: groupId,
1621
- domId: `${groupId}-frame`,
1622
- semantic,
1623
- label,
1624
- count,
1625
- position: currentIndex === first ? "start" : currentIndex === last ? "end" : "middle",
1626
- hasGrowing
464
+ const reconcile = () => {
465
+ const winner = ctx.slots.entries("conversation.chat.node").find((entry) => entry.options.key === "assistant-step");
466
+ if (winner === target) return;
467
+ restore();
468
+ if (winner === void 0 || winner.component === null || typeof winner.component !== "function" && typeof winner.component !== "object") return;
469
+ target = winner;
470
+ original = winner.component;
471
+ winner.component = enhanceNativeAssistant(original, preference);
1627
472
  };
1628
- }
1629
- /** Find one contiguous same-kind Tool run inside a Turn. */
1630
- function toolActivityGroup(snapshot, currentKey, sessionId, turn, semantic) {
1631
- return activityGroup(snapshot, currentKey, sessionId, turn, "tool-call", semantic, TOOL_GROUP_LABELS[semantic], true);
1632
- }
1633
- function ToolSemanticIcon({ presentation, className = TypewriterAssistantNodeView_module_css_default.toolSemanticIcon }) {
1634
- const Icon = TOOL_ICONS[presentation.semantic];
1635
- const glyph = presentation.semantic === "code" || presentation.semantic === "other" ? "wrench" : presentation.semantic;
1636
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1637
- className,
1638
- "data-tool-semantic-icon": presentation.semantic,
1639
- "data-tool-glyph": glyph,
1640
- "aria-hidden": true,
1641
- children: presentation.state === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "error" }) : presentation.state === "stopped" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 14 })
473
+ const off = ctx.on("slots/changed", (name) => {
474
+ if (name === "conversation.chat.node") reconcile();
1642
475
  });
1643
- }
1644
- function sameNestedToolIconTargets(current, next) {
1645
- return current.length === next.length && current.every((item, index) => {
1646
- const candidate = next[index];
1647
- return candidate !== void 0 && item.key === candidate.key && item.target === candidate.target && item.presentation.name === candidate.presentation.name && item.presentation.semantic === candidate.presentation.semantic && item.presentation.state === candidate.presentation.state;
1648
- });
1649
- }
1650
- /** Find expandable nested Tool headers rendered by the Harness Tool tree. */
1651
- function nestedToolIconTargets(root) {
1652
- const targets = [];
1653
- root.querySelectorAll("[data-subcalls] [data-chat-call-id]").forEach((callRow, index) => {
1654
- const slot = Array.from(callRow.children).find((child) => child instanceof HTMLElement && child.matches("[data-slot=\"tool.call.toolview\"]"));
1655
- if (slot === void 0) return;
1656
- const toolRoot = slot.querySelector(":scope > [data-tool]");
1657
- const bashRoot = slot.querySelector(":scope > * > [data-sample=\"bash\"]");
1658
- const target = toolRoot?.querySelector(":scope > * > [data-disclosure-row][aria-expanded]") ?? (bashRoot?.matches("[aria-expanded]") === true ? bashRoot : null);
1659
- if (target === null) return;
1660
- const name = toolRoot?.getAttribute("data-tool") ?? "bash";
1661
- const stateName = toolRoot?.getAttribute("data-state") ?? bashRoot?.getAttribute("data-state");
1662
- const state = stateName === "error" ? "error" : stateName === "stopped" ? "stopped" : null;
1663
- targets.push({
1664
- key: callRow.getAttribute("data-chat-call-id") ?? `${name}-${index}`,
1665
- target,
1666
- presentation: {
1667
- name,
1668
- semantic: semanticToolKind(name),
1669
- state
1670
- }
1671
- });
1672
- });
1673
- return targets;
1674
- }
1675
- /**
1676
- * Nested DisclosureRows replace their leading icon with a chevron when open.
1677
- * A portal owned by this plugin keeps a semantic icon mounted in each header
1678
- * across that internal state change while Harness continues to own the body.
1679
- */
1680
- function NestedToolSemanticIcons({ owner, revision }) {
1681
- const [targets, setTargets] = (0, react.useState)([]);
1682
- (0, react.useLayoutEffect)(() => {
1683
- const next = owner === null ? [] : nestedToolIconTargets(owner);
1684
- setTargets((current) => sameNestedToolIconTargets(current, next) ? current : next);
1685
- }, [owner, revision]);
1686
- return targets.map(({ key, target, presentation }) => {
1687
- const Icon = TOOL_ICONS[presentation.semantic];
1688
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1689
- className: TypewriterAssistantNodeView_module_css_default.nestedToolSemanticIcon,
1690
- "data-stream-nested-tool-icon": presentation.semantic,
1691
- "aria-hidden": true,
1692
- children: presentation.state === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "error" }) : presentation.state === "stopped" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 14 })
1693
- }), target, key);
1694
- });
1695
- }
1696
- function ConversationNodeSemanticIcon({ kind, className = TypewriterAssistantNodeView_module_css_default.streamSemanticIcon }) {
1697
- const Icon = CONVERSATION_NODE_ICONS[kind];
1698
- if (Icon === void 0) return null;
1699
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1700
- className,
1701
- "data-stream-semantic-icon": kind,
1702
- "aria-hidden": true,
1703
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 14 })
1704
- });
1705
- }
1706
- function chatNodeKind(node) {
1707
- if (node === null || typeof node !== "object" || !("kind" in node)) return "unknown";
1708
- const kind = node.kind;
1709
- return typeof kind === "string" ? kind : "unknown";
1710
- }
1711
- const KEEP_VISIBLE_KINDS = /* @__PURE__ */ new Set([
1712
- "turn-tail",
1713
- "turn-error",
1714
- "turn-max-tokens"
1715
- ]);
1716
- function turnDetailsFromNode(node) {
1717
- if (node === null || typeof node !== "object" || !("location" in node)) return void 0;
1718
- const location = node.location;
1719
- if (location === null || typeof location !== "object" || !("kind" in location)) return void 0;
1720
- const kind = location.kind;
1721
- if (kind !== "turn" && kind !== "step") return void 0;
1722
- const turn = location.turn;
1723
- if (turn === null || typeof turn !== "object" || !("turn" in turn)) return void 0;
1724
- const turnNumber = turn.turn;
1725
- if (typeof turnNumber !== "number") return void 0;
1726
- return { turn: turnNumber };
1727
- }
1728
- function processAddress(node) {
1729
- const details = turnDetailsFromNode(node);
1730
- return details === void 0 ? void 0 : { turn: details.turn };
1731
- }
1732
- function nodeKey(node) {
1733
- if (node === null || typeof node !== "object" || !("key" in node)) return void 0;
1734
- const key = node.key;
1735
- return typeof key === "string" ? key : void 0;
1736
- }
1737
- /**
1738
- * Keep the latest settled Tool visibly active while the Agent is still
1739
- * running but has not published its next Chat row yet. A later visible row
1740
- * ends this handoff state; `turn-tail` is presentation metadata rather than
1741
- * the next piece of model/process output.
1742
- */
1743
- function isAwaitingNextTurnNode(snapshot, running, currentKey, turnNumber) {
1744
- if (!running) return false;
1745
- const chat = chatParts(snapshot);
1746
- if (chat === void 0) return false;
1747
- if (chat.timeline !== void 0) {
1748
- const timeline = chat.timeline;
1749
- if (timeline !== null && typeof timeline === "object" && "turns" in timeline) {
1750
- const turns = timeline.turns;
1751
- if (turns instanceof Map) {
1752
- const turn = turns.get(turnNumber);
1753
- if (turn !== void 0 && turn !== null && typeof turn === "object" && "status" in turn && turn.status !== "open") return false;
1754
- }
1755
- }
1756
- }
1757
- const currentIndex = chat.order.indexOf(currentKey);
1758
- if (currentIndex === -1) return false;
1759
- for (let index = currentIndex + 1; index < chat.order.length; index += 1) {
1760
- const key = chat.order[index];
1761
- if (typeof key !== "string") continue;
1762
- const candidate = chat.get(key);
1763
- if (candidate === null || typeof candidate !== "object") continue;
1764
- if ("visibility" in candidate && candidate.visibility !== "visible") continue;
1765
- if ("kind" in candidate && candidate.kind === "turn-tail") continue;
1766
- return false;
1767
- }
1768
- return true;
1769
- }
1770
- /**
1771
- * True while a Chat node is still growing: an assistant/workflow `status:
1772
- * 'running'` payload, a Tool root that has not settled (`kind` absent), or a
1773
- * model-retry whose current attempt is still `scheduled`.
1774
- * @param node - The Chat node's view `node` prop.
1775
- * @returns whether this row should own conversation follow.
1776
- */
1777
- function isGrowingChatNode(node) {
1778
- if (node === null || typeof node !== "object" || !("data" in node)) return false;
1779
- const data = node.data;
1780
- if (data === null || typeof data !== "object") return false;
1781
- if ("status" in data && data.status === "running") return true;
1782
- if ("root" in data) {
1783
- const root = data.root;
1784
- if (root !== null && typeof root === "object" && !("kind" in root)) return true;
1785
- }
1786
- if ("current" in data) {
1787
- const current = data.current;
1788
- if (current !== null && typeof current === "object" && "retryState" in current && current.retryState === "scheduled") return true;
476
+ try {
477
+ reconcile();
478
+ } catch (error) {
479
+ off();
480
+ restore();
481
+ throw error;
1789
482
  }
1790
- return false;
1791
- }
1792
- /**
1793
- * Wrap a prior Chat node renderer with Codex activity presentation while DSH
1794
- * retains exclusive scroll ownership. Kit seats (`renderSlot`, locale, inject)
1795
- * pass through unchanged.
1796
- * @param Inner - The already-registered row component.
1797
- * @returns A structurally hosted row.
1798
- */
1799
- function wrapFollowNodeView(Inner, motion = {}) {
1800
- return function TypewriterFollowNodeView(props) {
1801
- const kind = chatNodeKind(props.node);
1802
- const directAddress = KEEP_VISIBLE_KINDS.has(kind) ? void 0 : processAddress(props.node);
1803
- const key = nodeKey(props.node);
1804
- const useChat = props.useChat;
1805
- const useSession = props.useSession;
1806
- if (directAddress !== void 0 && kind === "tool-call" && key !== void 0 && typeof useChat === "function" && typeof useSession === "function") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TailAwareToolFollowNodeView, {
1807
- Inner,
1808
- motion,
1809
- props,
1810
- address: directAddress,
1811
- nodeKey: key,
1812
- useChat,
1813
- useSession
1814
- });
1815
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowNodeView, {
1816
- Inner,
1817
- motion,
1818
- props,
1819
- address: directAddress
1820
- });
1821
- };
1822
- }
1823
- function TailAwareToolFollowNodeView({ Inner, motion, props, address, nodeKey: currentKey, useChat, useSession }) {
1824
- const running = useSession((snapshot) => snapshot !== null && typeof snapshot === "object" && "running" in snapshot && snapshot.running === true);
1825
- const awaitingNext = useChat((snapshot) => isAwaitingNextTurnNode(snapshot, running, currentKey, address.turn));
1826
- const presentation = toolPresentation(props.node);
1827
- const group = useChat((snapshot) => typeof props.sessionId === "string" && presentation !== null ? toolActivityGroup(snapshot, currentKey, props.sessionId, address.turn, presentation.semantic) : void 0);
1828
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowNodeView, {
1829
- Inner,
1830
- motion,
1831
- props,
1832
- address,
1833
- awaitingNext,
1834
- toolGroup: group
1835
- });
1836
- }
1837
- function FollowNodeView({ Inner, motion, props, address, awaitingNext = false, toolGroup }) {
1838
- const speedCpsRef = (0, react.useRef)(35);
1839
- const turnProcess = props.turnProcess;
1840
- const fallbackGate = {
1841
- compactTranscript: useCompactTranscript(),
1842
- historyIncomplete: typeof props.useSession === "function" && props.useSession((snapshot) => snapshot.hasMore === true),
1843
- processStartLoaded: loadedProcessStart(props.node, turnProcess)
1844
- };
1845
- const seatRef = useChatSeatHidden(isFallbackProcessMember(props.node, turnProcess, fallbackGate) && turnProcess?.open === false, (0, react.useCallback)(() => {
1846
- turnProcess?.setOpen(true);
1847
- }, [turnProcess]));
1848
- const [eventElement, setEventElement] = (0, react.useState)(null);
1849
- const eventRef = (0, react.useCallback)((element) => {
1850
- seatRef(element);
1851
- setEventElement(element);
1852
- }, [seatRef]);
1853
- const growing = isGrowingChatNode(props.node);
1854
- const kind = chatNodeKind(props.node);
1855
- const staticCompaction = kind === "compaction" || kind === "manual-compaction";
1856
- (0, react.useLayoutEffect)(() => {
1857
- if (!staticCompaction || eventElement === null) return;
1858
- const button = eventElement.querySelector("[data-compaction-disclosure]")?.closest("button");
1859
- if (button === null || button === void 0) return;
1860
- const previousDisabled = button.disabled;
1861
- const previousExpanded = button.getAttribute("aria-expanded");
1862
- button.disabled = true;
1863
- button.removeAttribute("aria-expanded");
1864
- return () => {
1865
- button.disabled = previousDisabled;
1866
- if (previousExpanded === null) button.removeAttribute("aria-expanded");
1867
- else button.setAttribute("aria-expanded", previousExpanded);
1868
- };
1869
- }, [
1870
- eventElement,
1871
- props.node,
1872
- staticCompaction
1873
- ]);
1874
- const preventCompactionExpand = staticCompaction ? (event) => {
1875
- event.preventDefault();
1876
- event.stopPropagation();
1877
- } : void 0;
1878
- const tool = kind === "tool-call" ? toolPresentation(props.node) : null;
1879
- const editStats = tool === null ? void 0 : editDiffStats(props.node);
1880
- const [editStatsRow, setEditStatsRow] = (0, react.useState)(null);
1881
- (0, react.useLayoutEffect)(() => {
1882
- const row = editStats === void 0 || eventElement === null ? null : eventElement.querySelector("[data-disclosure-row]");
1883
- setEditStatsRow((previous) => previous === row ? previous : row);
1884
- }, [
1885
- editStats,
1886
- eventElement,
1887
- props.node
1888
- ]);
1889
- const ownsStreamSemanticIcon = tool === null && CONVERSATION_NODE_ICONS[kind] !== void 0;
1890
- const toolProgressActive = tool !== null && (growing || awaitingNext);
1891
- const inner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1892
- tool !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolSemanticIcon, { presentation: tool }),
1893
- ownsStreamSemanticIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationNodeSemanticIcon, { kind }),
1894
- (0, react.createElement)(Inner, props),
1895
- tool !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NestedToolSemanticIcons, {
1896
- owner: eventElement,
1897
- revision: props.node
1898
- }),
1899
- editStats !== void 0 && editStatsRow !== null && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1900
- className: TypewriterAssistantNodeView_module_css_default.editStats,
1901
- "data-stream-edit-stats": "",
1902
- "data-stream-edit-file": editDiffFilePath(props.node),
1903
- onClick: (event) => {
1904
- event.stopPropagation();
1905
- event.currentTarget.closest("[data-disclosure-row][data-expandable]")?.click();
1906
- },
1907
- children: editStats
1908
- }), editStatsRow)
1909
- ] });
1910
- const activityRunning = growing || toolGroup?.hasGrowing === true;
1911
- const headerIcon = tool !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolSemanticIcon, {
1912
- presentation: tool,
1913
- className: TypewriterAssistantNodeView_module_css_default.toolGroupHeaderGlyph
1914
- }) : ownsStreamSemanticIcon ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationNodeSemanticIcon, {
1915
- kind,
1916
- className: TypewriterAssistantNodeView_module_css_default.toolGroupHeaderGlyph
1917
- }) : null;
1918
- const eventAttributes = {
1919
- "data-stream-node": kind,
1920
- "data-stream-turn": address === void 0 ? void 0 : String(address.turn),
1921
- "data-stream-state": activityRunning ? "running" : "settled",
1922
- "data-stream-progress": toolProgressActive ? "active" : void 0,
1923
- "data-stream-tool-row": tool === null ? void 0 : "",
1924
- "data-stream-native-disclosure": tool === null ? "" : void 0,
1925
- "data-stream-semantic-icon-owned": ownsStreamSemanticIcon ? kind : void 0,
1926
- "data-tool-semantic": tool?.semantic,
1927
- "data-tool-name": tool?.name
483
+ return () => {
484
+ off();
485
+ restore();
1928
486
  };
1929
- const activityHeader = toolGroup !== void 0 && headerIcon !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolActivityGroupHeader, {
1930
- label: toolGroup.label,
1931
- icon: headerIcon
1932
- }) : null;
1933
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FollowHost, {
1934
- active: activityRunning,
1935
- speedCpsRef,
1936
- minSpeedPxPerSec: motion.minSpeedPxPerSec,
1937
- maxSpeedPxPerSec: motion.maxSpeedPxPerSec,
1938
- children: address !== void 0 && toolGroup !== void 0 && activityHeader !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolActivityGroupMember, {
1939
- group: toolGroup,
1940
- memberRef: eventRef,
1941
- eventAttributes,
1942
- header: activityHeader,
1943
- children: inner
1944
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1945
- ref: eventRef,
1946
- className: TypewriterAssistantNodeView_module_css_default.eventRow,
1947
- onClickCapture: preventCompactionExpand,
1948
- "data-stream-node": kind,
1949
- "data-stream-turn": address?.turn,
1950
- "data-stream-state": growing ? "running" : "settled",
1951
- "data-stream-progress": toolProgressActive ? "active" : void 0,
1952
- "data-stream-tool-row": tool === null ? void 0 : "",
1953
- "data-stream-native-disclosure": tool === null ? "" : void 0,
1954
- "data-stream-semantic-icon-owned": ownsStreamSemanticIcon ? kind : void 0,
1955
- "data-tool-semantic": tool?.semantic,
1956
- "data-tool-name": tool?.name,
1957
- children: inner
1958
- })
1959
- });
1960
487
  }
1961
488
  //#endregion
1962
489
  //#region \0dsh-css:/home/runner/work/dsh-codex-suite/dsh-codex-suite/packages/conversation-ui/src/client/ConversationCard.module.css.mjs
1963
- const css$1 = ".XFnqVW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;min-width:0;list-style:none;transition:border-color .16s,background .16s;display:flex}.XFnqVW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.XFnqVW_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.XFnqVW_header{flex-wrap:wrap;align-items:flex-start;gap:12px;padding:14px 16px;display:flex}.XFnqVW_headText{overflow-wrap:anywhere;flex-direction:column;flex:16rem;gap:4px;min-width:0;display:flex}.XFnqVW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.XFnqVW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.XFnqVW_metadata{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;max-width:100%;display:flex}.XFnqVW_version{overflow-wrap:anywhere;min-width:0;max-width:100%;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.XFnqVW_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}.XFnqVW_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;margin:0 16px;padding-bottom:8px;display:flex}.XFnqVW_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.XFnqVW_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.XFnqVW_fieldHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.XFnqVW_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.XFnqVW_toggle{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.XFnqVW_toggle:disabled{cursor:default}.XFnqVW_toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.XFnqVW_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.XFnqVW_failure{justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XFnqVW_updateRow{border-top:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XFnqVW_updateCopy{flex-direction:column;gap:4px;min-width:0;display:flex}.XFnqVW_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}.XFnqVW_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.XFnqVW_save,.XFnqVW_discard,.XFnqVW_update{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.XFnqVW_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.XFnqVW_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.XFnqVW_discard:hover:not(:disabled),.XFnqVW_update:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}.XFnqVW_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}.XFnqVW_discard:disabled,.XFnqVW_save:disabled,.XFnqVW_update:disabled{opacity:.4;cursor:default}.XFnqVW_discard:focus-visible,.XFnqVW_save:focus-visible,.XFnqVW_update:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
490
+ const css$1 = ".XFnqVW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;min-width:0;list-style:none;transition:border-color .16s,background .16s;display:flex}.XFnqVW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.XFnqVW_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.XFnqVW_header{flex-wrap:wrap;align-items:flex-start;gap:12px;padding:14px 16px;display:flex}.XFnqVW_headText{overflow-wrap:anywhere;flex-direction:column;flex:16rem;gap:4px;min-width:0;display:flex}.XFnqVW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.XFnqVW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.XFnqVW_metadata{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;max-width:100%;display:flex}.XFnqVW_version{overflow-wrap:anywhere;min-width:0;max-width:100%;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.XFnqVW_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}.XFnqVW_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;margin:0 16px;padding-bottom:8px;display:flex}.XFnqVW_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.XFnqVW_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.XFnqVW_fieldHead{justify-content:space-between;align-items:center;gap:8px;display:flex}.XFnqVW_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.XFnqVW_toggle{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.XFnqVW_toggle:disabled{cursor:default}.XFnqVW_toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.XFnqVW_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);min-width:9.5rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:6px;flex:none;padding:3px 8px;font-size:12px;line-height:1.5}.XFnqVW_select:disabled{opacity:.5;cursor:default}.XFnqVW_select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.XFnqVW_value{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;font-size:12px;line-height:1.5}.XFnqVW_range{width:100%;accent-color:var(--dsw-alias-brand-primary);cursor:pointer}.XFnqVW_range:disabled{opacity:.4;cursor:default}.XFnqVW_range:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.XFnqVW_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.XFnqVW_failure{justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XFnqVW_updateRow{border-top:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:12px;padding:12px 0;display:flex}.XFnqVW_updateCopy{flex-direction:column;gap:4px;min-width:0;display:flex}.XFnqVW_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}.XFnqVW_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.XFnqVW_save,.XFnqVW_discard,.XFnqVW_update{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.XFnqVW_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.XFnqVW_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.XFnqVW_discard:hover:not(:disabled),.XFnqVW_update:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-primary)}.XFnqVW_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}.XFnqVW_discard:disabled,.XFnqVW_save:disabled,.XFnqVW_update:disabled{opacity:.4;cursor:default}.XFnqVW_discard:focus-visible,.XFnqVW_save:focus-visible,.XFnqVW_update:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
1964
491
  const tagId$1 = "@jcy2387/dsh-conversation-ui/ConversationCard.module.css";
1965
492
  if (typeof document !== "undefined") {
1966
493
  let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]");
@@ -1973,30 +500,33 @@ window.__ModuleLoader__.load({
1973
500
  tag.textContent = css$1;
1974
501
  }
1975
502
  var ConversationCard_module_css_default = {
1976
- "readOnly": "XFnqVW_readOnly",
1977
- "field": "XFnqVW_field",
1978
- "updateRow": "XFnqVW_updateRow",
503
+ "metadata": "XFnqVW_metadata",
504
+ "headText": "XFnqVW_headText",
505
+ "fieldHead": "XFnqVW_fieldHead",
1979
506
  "toggle": "XFnqVW_toggle",
1980
- "updateCopy": "XFnqVW_updateCopy",
507
+ "pending": "XFnqVW_pending",
508
+ "cardOpen": "XFnqVW_cardOpen",
509
+ "field": "XFnqVW_field",
1981
510
  "label": "XFnqVW_label",
511
+ "value": "XFnqVW_value",
512
+ "updateCopy": "XFnqVW_updateCopy",
513
+ "select": "XFnqVW_select",
1982
514
  "description": "XFnqVW_description",
1983
- "name": "XFnqVW_name",
1984
- "fieldHead": "XFnqVW_fieldHead",
1985
- "hint": "XFnqVW_hint",
515
+ "readOnly": "XFnqVW_readOnly",
1986
516
  "failure": "XFnqVW_failure",
1987
- "headText": "XFnqVW_headText",
1988
- "version": "XFnqVW_version",
1989
- "metadata": "XFnqVW_metadata",
1990
- "body": "XFnqVW_body",
517
+ "updateRow": "XFnqVW_updateRow",
1991
518
  "footer": "XFnqVW_footer",
1992
- "pending": "XFnqVW_pending",
1993
- "cardOpen": "XFnqVW_cardOpen",
1994
- "header": "XFnqVW_header",
1995
519
  "failed": "XFnqVW_failed",
520
+ "header": "XFnqVW_header",
1996
521
  "save": "XFnqVW_save",
1997
522
  "discard": "XFnqVW_discard",
523
+ "card": "XFnqVW_card",
1998
524
  "update": "XFnqVW_update",
1999
- "card": "XFnqVW_card"
525
+ "range": "XFnqVW_range",
526
+ "version": "XFnqVW_version",
527
+ "name": "XFnqVW_name",
528
+ "body": "XFnqVW_body",
529
+ "hint": "XFnqVW_hint"
2000
530
  };
2001
531
  //#endregion
2002
532
  //#region src/client/ConversationCard.tsx
@@ -2057,6 +587,103 @@ window.__ModuleLoader__.load({
2057
587
  role: "status",
2058
588
  children: t("readOnly")
2059
589
  }) : null,
590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
591
+ className: ConversationCard_module_css_default.field,
592
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
593
+ className: ConversationCard_module_css_default.fieldHead,
594
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
595
+ className: ConversationCard_module_css_default.label,
596
+ children: t("mode")
597
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
598
+ className: ConversationCard_module_css_default.select,
599
+ value: state.mode,
600
+ disabled: !state.writable,
601
+ onChange: (event) => {
602
+ props.edit({ mode: event.target.value });
603
+ },
604
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
605
+ value: "typewriter",
606
+ children: t("modeTypewriter")
607
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
608
+ value: "teleprompter",
609
+ children: t("modeTeleprompter")
610
+ })]
611
+ })]
612
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
613
+ className: ConversationCard_module_css_default.hint,
614
+ children: t("modeHint")
615
+ })]
616
+ }),
617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
618
+ className: ConversationCard_module_css_default.field,
619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
620
+ className: ConversationCard_module_css_default.fieldHead,
621
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
622
+ className: ConversationCard_module_css_default.label,
623
+ children: t("preset")
624
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
625
+ className: ConversationCard_module_css_default.select,
626
+ value: state.preset,
627
+ disabled: !state.writable,
628
+ onChange: (event) => {
629
+ props.edit({ preset: event.target.value });
630
+ },
631
+ children: [
632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
633
+ value: "realtime",
634
+ children: t("presetRealtime")
635
+ }),
636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
637
+ value: "balanced",
638
+ children: t("presetBalanced")
639
+ }),
640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
641
+ value: "silky",
642
+ children: t("presetSilky")
643
+ })
644
+ ]
645
+ })]
646
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
647
+ className: ConversationCard_module_css_default.hint,
648
+ children: t("presetHint")
649
+ })]
650
+ }),
651
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
652
+ className: ConversationCard_module_css_default.field,
653
+ children: [
654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
655
+ className: ConversationCard_module_css_default.fieldHead,
656
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
657
+ className: ConversationCard_module_css_default.label,
658
+ children: t("revealSpeed")
659
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
660
+ className: ConversationCard_module_css_default.value,
661
+ children: [
662
+ state.revealCharsPerSec,
663
+ " ",
664
+ t("charsPerSecond")
665
+ ]
666
+ })]
667
+ }),
668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
669
+ type: "range",
670
+ className: ConversationCard_module_css_default.range,
671
+ min: 5,
672
+ max: 200,
673
+ step: 5,
674
+ value: state.revealCharsPerSec,
675
+ disabled: !state.writable || state.mode !== "typewriter",
676
+ onChange: (event) => {
677
+ const value = Number(event.target.value);
678
+ if (Number.isFinite(value)) props.edit({ revealCharsPerSec: value });
679
+ }
680
+ }),
681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
682
+ className: ConversationCard_module_css_default.hint,
683
+ children: t("revealSpeedHint")
684
+ })
685
+ ]
686
+ }),
2060
687
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2061
688
  className: ConversationCard_module_css_default.field,
2062
689
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -2070,7 +697,7 @@ window.__ModuleLoader__.load({
2070
697
  checked: state.thinkAutoExpand,
2071
698
  disabled: !state.writable,
2072
699
  onChange: (event) => {
2073
- props.edit(event.target.checked);
700
+ props.edit({ thinkAutoExpand: event.target.checked });
2074
701
  }
2075
702
  })]
2076
703
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -2690,7 +1317,7 @@ window.__ModuleLoader__.load({
2690
1317
  immer.createDraft.bind(immer);
2691
1318
  immer.finishDraft.bind(immer);
2692
1319
  //#endregion
2693
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-client-store@0.2.0-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-client-store/lib/index.js
1320
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-client-store@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-client-store/lib/index.js
2694
1321
  /**
2695
1322
  * React-free snapshot store engine (zustand vanilla + immer + subscribeWithSelector +
2696
1323
  * rafFlush middleware + opt-in persist + dev freeze) plus the declarative
@@ -2807,6 +1434,38 @@ window.__ModuleLoader__.load({
2807
1434
  return freeze(value, true);
2808
1435
  }
2809
1436
  //#endregion
1437
+ //#region src/config.ts
1438
+ /** Defaults shared by the Host schema and the client-side fallback. */
1439
+ const DEFAULT_CONVERSATION_CONFIG = {
1440
+ mode: "typewriter",
1441
+ preset: "balanced",
1442
+ revealCharsPerSec: 80,
1443
+ scrollSpeedPxPerSec: 48,
1444
+ maxScrollSpeedPxPerSec: 1e3
1445
+ };
1446
+ /**
1447
+ * Window global the Host writes into the served index HTML. The browser boot
1448
+ * graph carries no per-entry config, so this inline script is the only
1449
+ * Host-to-client configuration channel for a composed web plugin.
1450
+ */
1451
+ const CONVERSATION_BOOT_GLOBAL = "__DSH_CONVERSATION_UI_CONFIG__";
1452
+ //#endregion
1453
+ //#region src/settings.ts
1454
+ /**
1455
+ * User-owned settings for the conversation-ui plugin, exposed to the Host
1456
+ * settings service and edited from the Web Settings "plugin configuration"
1457
+ * page. The profile overlay still supplies deployment defaults (including the
1458
+ * deprecated scroll fields), but these fields are the live, durable
1459
+ * user-facing preferences.
1460
+ */
1461
+ /** Defaults shared by the Host schema, the client fallback, and the boot bridge. */
1462
+ const DEFAULT_CONVERSATION_SETTINGS = {
1463
+ thinkAutoExpand: true,
1464
+ mode: DEFAULT_CONVERSATION_CONFIG.mode,
1465
+ preset: DEFAULT_CONVERSATION_CONFIG.preset,
1466
+ revealCharsPerSec: DEFAULT_CONVERSATION_CONFIG.revealCharsPerSec
1467
+ };
1468
+ //#endregion
2810
1469
  //#region src/client/conversation-ui-card-controller.ts
2811
1470
  /** Staged form state for the plugin-owned conversation-ui settings RPC. */
2812
1471
  /** Bridges the plugin's protected Host interface onto a staged settings form. */
@@ -2845,8 +1504,12 @@ window.__ModuleLoader__.load({
2845
1504
  inject() {
2846
1505
  return {
2847
1506
  hooks: { conversationUiCard: this.store },
2848
- edit: (value) => {
2849
- this.staged = value;
1507
+ edit: (patch) => {
1508
+ if (Object.keys(patch).length === 0) return;
1509
+ this.staged = {
1510
+ ...this.staged,
1511
+ ...patch
1512
+ };
2850
1513
  this.failed = false;
2851
1514
  this.publish();
2852
1515
  },
@@ -2871,10 +1534,13 @@ window.__ModuleLoader__.load({
2871
1534
  return {
2872
1535
  status: this.loadStatus,
2873
1536
  writable: this.loaded?.writable ?? false,
2874
- dirty: this.staged !== void 0,
1537
+ dirty: this.staged !== void 0 && Object.keys(this.staged).length > 0,
2875
1538
  saving: this.saving,
2876
1539
  failed: this.failed,
2877
- thinkAutoExpand: this.staged ?? this.loaded?.thinkAutoExpand ?? DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand,
1540
+ thinkAutoExpand: this.staged?.thinkAutoExpand ?? this.loaded?.thinkAutoExpand ?? DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand,
1541
+ mode: this.staged?.mode ?? this.loaded?.mode ?? DEFAULT_CONVERSATION_SETTINGS.mode,
1542
+ preset: this.staged?.preset ?? this.loaded?.preset ?? DEFAULT_CONVERSATION_SETTINGS.preset,
1543
+ revealCharsPerSec: this.staged?.revealCharsPerSec ?? this.loaded?.revealCharsPerSec ?? DEFAULT_CONVERSATION_SETTINGS.revealCharsPerSec,
2878
1544
  version: this.loaded?.version,
2879
1545
  installation: this.loaded?.installation ?? "unmanaged",
2880
1546
  canUpgrade: this.loaded?.canUpgrade ?? false,
@@ -2901,7 +1567,7 @@ window.__ModuleLoader__.load({
2901
1567
  this.publish();
2902
1568
  }
2903
1569
  async save() {
2904
- if (this.staged === void 0 || this.saving || this.loaded?.writable !== true) return;
1570
+ if (this.staged === void 0 || Object.keys(this.staged).length === 0 || this.saving || this.loaded?.writable !== true) return;
2905
1571
  const value = this.staged;
2906
1572
  this.saving = true;
2907
1573
  this.failed = false;
@@ -2935,7 +1601,524 @@ window.__ModuleLoader__.load({
2935
1601
  }
2936
1602
  };
2937
1603
  //#endregion
2938
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-session@0.2.0-rc.2_@deepseek-ai+cordis@4.0.4_@deepseek-ai+dsh-scope@0._c9dee8deba3bd498ce58eb9c566c157f/node_modules/@deepseek-ai/dsh-session/lib/types/surface.js
1604
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js
1605
+ var Diff = class {
1606
+ diff(oldStr, newStr, options = {}) {
1607
+ let callback;
1608
+ if (typeof options === "function") {
1609
+ callback = options;
1610
+ options = {};
1611
+ } else if ("callback" in options) callback = options.callback;
1612
+ const oldString = this.castInput(oldStr, options);
1613
+ const newString = this.castInput(newStr, options);
1614
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
1615
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
1616
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
1617
+ }
1618
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
1619
+ var _a;
1620
+ const done = (value) => {
1621
+ value = this.postProcess(value, options);
1622
+ if (callback) {
1623
+ setTimeout(function() {
1624
+ callback(value);
1625
+ }, 0);
1626
+ return;
1627
+ } else return value;
1628
+ };
1629
+ const newLen = newTokens.length, oldLen = oldTokens.length;
1630
+ let editLength = 1;
1631
+ let maxEditLength = newLen + oldLen;
1632
+ if (options.maxEditLength != null) maxEditLength = Math.min(maxEditLength, options.maxEditLength);
1633
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
1634
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
1635
+ const bestPath = [{
1636
+ oldPos: -1,
1637
+ lastComponent: void 0
1638
+ }];
1639
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
1640
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
1641
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
1642
+ const execEditLength = () => {
1643
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
1644
+ let basePath;
1645
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
1646
+ if (removePath) bestPath[diagonalPath - 1] = void 0;
1647
+ let canAdd = false;
1648
+ if (addPath) {
1649
+ const addPathNewPos = addPath.oldPos - diagonalPath;
1650
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
1651
+ }
1652
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
1653
+ if (!canAdd && !canRemove) {
1654
+ bestPath[diagonalPath] = void 0;
1655
+ continue;
1656
+ }
1657
+ if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) basePath = this.addToPath(addPath, true, false, 0, options);
1658
+ else basePath = this.addToPath(removePath, false, true, 1, options);
1659
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
1660
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
1661
+ else {
1662
+ bestPath[diagonalPath] = basePath;
1663
+ if (basePath.oldPos + 1 >= oldLen) maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
1664
+ if (newPos + 1 >= newLen) minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
1665
+ }
1666
+ }
1667
+ editLength++;
1668
+ };
1669
+ if (callback) (function exec() {
1670
+ setTimeout(function() {
1671
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) return callback(void 0);
1672
+ if (!execEditLength()) exec();
1673
+ }, 0);
1674
+ })();
1675
+ else while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
1676
+ const ret = execEditLength();
1677
+ if (ret) return ret;
1678
+ }
1679
+ }
1680
+ addToPath(path, added, removed, oldPosInc, options) {
1681
+ const last = path.lastComponent;
1682
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) return {
1683
+ oldPos: path.oldPos + oldPosInc,
1684
+ lastComponent: {
1685
+ count: last.count + 1,
1686
+ added,
1687
+ removed,
1688
+ previousComponent: last.previousComponent
1689
+ }
1690
+ };
1691
+ else return {
1692
+ oldPos: path.oldPos + oldPosInc,
1693
+ lastComponent: {
1694
+ count: 1,
1695
+ added,
1696
+ removed,
1697
+ previousComponent: last
1698
+ }
1699
+ };
1700
+ }
1701
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
1702
+ const newLen = newTokens.length, oldLen = oldTokens.length;
1703
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
1704
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
1705
+ newPos++;
1706
+ oldPos++;
1707
+ commonCount++;
1708
+ if (options.oneChangePerToken) basePath.lastComponent = {
1709
+ count: 1,
1710
+ previousComponent: basePath.lastComponent,
1711
+ added: false,
1712
+ removed: false
1713
+ };
1714
+ }
1715
+ if (commonCount && !options.oneChangePerToken) basePath.lastComponent = {
1716
+ count: commonCount,
1717
+ previousComponent: basePath.lastComponent,
1718
+ added: false,
1719
+ removed: false
1720
+ };
1721
+ basePath.oldPos = oldPos;
1722
+ return newPos;
1723
+ }
1724
+ equals(left, right, options) {
1725
+ if (options.comparator) return options.comparator(left, right);
1726
+ else return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
1727
+ }
1728
+ removeEmpty(array) {
1729
+ const ret = [];
1730
+ for (let i = 0; i < array.length; i++) if (array[i]) ret.push(array[i]);
1731
+ return ret;
1732
+ }
1733
+ castInput(value, options) {
1734
+ return value;
1735
+ }
1736
+ tokenize(value, options) {
1737
+ return Array.from(value);
1738
+ }
1739
+ join(chars) {
1740
+ return chars.join("");
1741
+ }
1742
+ postProcess(changeObjects, options) {
1743
+ return changeObjects;
1744
+ }
1745
+ get useLongestToken() {
1746
+ return false;
1747
+ }
1748
+ buildValues(lastComponent, newTokens, oldTokens) {
1749
+ const components = [];
1750
+ let nextComponent;
1751
+ while (lastComponent) {
1752
+ components.push(lastComponent);
1753
+ nextComponent = lastComponent.previousComponent;
1754
+ delete lastComponent.previousComponent;
1755
+ lastComponent = nextComponent;
1756
+ }
1757
+ components.reverse();
1758
+ const componentLen = components.length;
1759
+ let componentPos = 0, newPos = 0, oldPos = 0;
1760
+ for (; componentPos < componentLen; componentPos++) {
1761
+ const component = components[componentPos];
1762
+ if (!component.removed) {
1763
+ if (!component.added && this.useLongestToken) {
1764
+ let value = newTokens.slice(newPos, newPos + component.count);
1765
+ value = value.map(function(value, i) {
1766
+ const oldValue = oldTokens[oldPos + i];
1767
+ return oldValue.length > value.length ? oldValue : value;
1768
+ });
1769
+ component.value = this.join(value);
1770
+ } else component.value = this.join(newTokens.slice(newPos, newPos + component.count));
1771
+ newPos += component.count;
1772
+ if (!component.added) oldPos += component.count;
1773
+ } else {
1774
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
1775
+ oldPos += component.count;
1776
+ }
1777
+ }
1778
+ return components;
1779
+ }
1780
+ };
1781
+ //#endregion
1782
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/character.js
1783
+ var CharacterDiff = class extends Diff {};
1784
+ new CharacterDiff();
1785
+ //#endregion
1786
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/util/string.js
1787
+ function longestCommonPrefix(str1, str2) {
1788
+ let i;
1789
+ for (i = 0; i < str1.length && i < str2.length; i++) if (str1[i] != str2[i]) return str1.slice(0, i);
1790
+ return str1.slice(0, i);
1791
+ }
1792
+ function longestCommonSuffix(str1, str2) {
1793
+ let i;
1794
+ if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) return "";
1795
+ for (i = 0; i < str1.length && i < str2.length; i++) if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) return str1.slice(-i);
1796
+ return str1.slice(-i);
1797
+ }
1798
+ function replacePrefix(string, oldPrefix, newPrefix) {
1799
+ if (string.slice(0, oldPrefix.length) != oldPrefix) throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
1800
+ return newPrefix + string.slice(oldPrefix.length);
1801
+ }
1802
+ function replaceSuffix(string, oldSuffix, newSuffix) {
1803
+ if (!oldSuffix) return string + newSuffix;
1804
+ if (string.slice(-oldSuffix.length) != oldSuffix) throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
1805
+ return string.slice(0, -oldSuffix.length) + newSuffix;
1806
+ }
1807
+ function removePrefix(string, oldPrefix) {
1808
+ return replacePrefix(string, oldPrefix, "");
1809
+ }
1810
+ function removeSuffix(string, oldSuffix) {
1811
+ return replaceSuffix(string, oldSuffix, "");
1812
+ }
1813
+ function maximumOverlap(string1, string2) {
1814
+ return string2.slice(0, overlapCount(string1, string2));
1815
+ }
1816
+ function overlapCount(a, b) {
1817
+ let startA = 0;
1818
+ if (a.length > b.length) startA = a.length - b.length;
1819
+ let endB = b.length;
1820
+ if (a.length < b.length) endB = a.length;
1821
+ const map = Array(endB);
1822
+ let k = 0;
1823
+ map[0] = 0;
1824
+ for (let j = 1; j < endB; j++) {
1825
+ if (b[j] == b[k]) map[j] = map[k];
1826
+ else map[j] = k;
1827
+ while (k > 0 && b[j] != b[k]) k = map[k];
1828
+ if (b[j] == b[k]) k++;
1829
+ }
1830
+ k = 0;
1831
+ for (let i = startA; i < a.length; i++) {
1832
+ while (k > 0 && a[i] != b[k]) k = map[k];
1833
+ if (a[i] == b[k]) k++;
1834
+ }
1835
+ return k;
1836
+ }
1837
+ /**
1838
+ * Split a string into segments using a word segmenter, merging consecutive
1839
+ * segments if they are both whitespace segments. Whitespace segments can
1840
+ * appear adjacent to one another for two reasons:
1841
+ * - newlines always get their own segment
1842
+ * - where a diacritic is attached to a whitespace character in the text, the
1843
+ * segment ends after the diacritic, so e.g. " \u0300 " becomes two segments.
1844
+ * This function therefore runs the segmenter's .segment() method and then
1845
+ * merges consecutive segments of whitespace into a single part.
1846
+ */
1847
+ function segment(string, segmenter) {
1848
+ const parts = [];
1849
+ for (const segmentObj of Array.from(segmenter.segment(string))) {
1850
+ const segment = segmentObj.segment;
1851
+ if (parts.length && /\s/.test(parts[parts.length - 1]) && /\s/.test(segment)) parts[parts.length - 1] += segment;
1852
+ else parts.push(segment);
1853
+ }
1854
+ return parts;
1855
+ }
1856
+ function trailingWs(string, segmenter) {
1857
+ if (segmenter) return leadingAndTrailingWs(string, segmenter)[1];
1858
+ let i;
1859
+ for (i = string.length - 1; i >= 0; i--) if (!string[i].match(/\s/)) break;
1860
+ return string.substring(i + 1);
1861
+ }
1862
+ function leadingWs(string, segmenter) {
1863
+ if (segmenter) return leadingAndTrailingWs(string, segmenter)[0];
1864
+ const match = string.match(/^\s*/);
1865
+ return match ? match[0] : "";
1866
+ }
1867
+ function leadingAndTrailingWs(string, segmenter) {
1868
+ if (!segmenter) return [leadingWs(string), trailingWs(string)];
1869
+ if (segmenter.resolvedOptions().granularity != "word") throw new Error("The segmenter passed must have a granularity of \"word\"");
1870
+ const segments = segment(string, segmenter);
1871
+ const firstSeg = segments[0];
1872
+ const lastSeg = segments[segments.length - 1];
1873
+ return [/\s/.test(firstSeg) ? firstSeg : "", /\s/.test(lastSeg) ? lastSeg : ""];
1874
+ }
1875
+ //#endregion
1876
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/word.js
1877
+ const extendedWordChars = "a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
1878
+ const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, "ug");
1879
+ var WordDiff = class extends Diff {
1880
+ equals(left, right, options) {
1881
+ if (options.ignoreCase) {
1882
+ left = left.toLowerCase();
1883
+ right = right.toLowerCase();
1884
+ }
1885
+ return left.trim() === right.trim();
1886
+ }
1887
+ tokenize(value, options = {}) {
1888
+ let parts;
1889
+ if (options.intlSegmenter) {
1890
+ const segmenter = options.intlSegmenter;
1891
+ if (segmenter.resolvedOptions().granularity != "word") throw new Error("The segmenter passed must have a granularity of \"word\"");
1892
+ parts = segment(value, segmenter);
1893
+ } else parts = value.match(tokenizeIncludingWhitespace) || [];
1894
+ const tokens = [];
1895
+ let prevPart = null;
1896
+ parts.forEach((part) => {
1897
+ if (/\s/.test(part)) {
1898
+ if (prevPart == null) tokens.push(part);
1899
+ else tokens.push(tokens.pop() + part);
1900
+ } else if (prevPart != null && /\s/.test(prevPart)) {
1901
+ if (tokens[tokens.length - 1] == prevPart) tokens.push(tokens.pop() + part);
1902
+ else tokens.push(prevPart + part);
1903
+ } else tokens.push(part);
1904
+ prevPart = part;
1905
+ });
1906
+ return tokens;
1907
+ }
1908
+ join(tokens) {
1909
+ return tokens.map((token, i) => {
1910
+ if (i == 0) return token;
1911
+ else return token.replace(/^\s+/, "");
1912
+ }).join("");
1913
+ }
1914
+ postProcess(changes, options) {
1915
+ if (!changes || options.oneChangePerToken) return changes;
1916
+ let lastKeep = null;
1917
+ let insertion = null;
1918
+ let deletion = null;
1919
+ changes.forEach((change) => {
1920
+ if (change.added) insertion = change;
1921
+ else if (change.removed) deletion = change;
1922
+ else {
1923
+ if (insertion || deletion) dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change, options.intlSegmenter);
1924
+ lastKeep = change;
1925
+ insertion = null;
1926
+ deletion = null;
1927
+ }
1928
+ });
1929
+ if (insertion || deletion) dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null, options.intlSegmenter);
1930
+ return changes;
1931
+ }
1932
+ };
1933
+ new WordDiff();
1934
+ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter) {
1935
+ if (deletion && insertion) {
1936
+ const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter);
1937
+ const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter);
1938
+ if (startKeep) {
1939
+ const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
1940
+ startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
1941
+ deletion.value = removePrefix(deletion.value, commonWsPrefix);
1942
+ insertion.value = removePrefix(insertion.value, commonWsPrefix);
1943
+ }
1944
+ if (endKeep) {
1945
+ const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
1946
+ endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
1947
+ deletion.value = removeSuffix(deletion.value, commonWsSuffix);
1948
+ insertion.value = removeSuffix(insertion.value, commonWsSuffix);
1949
+ }
1950
+ } else if (insertion) {
1951
+ if (startKeep) {
1952
+ const ws = leadingWs(insertion.value, segmenter);
1953
+ insertion.value = insertion.value.substring(ws.length);
1954
+ }
1955
+ if (endKeep) {
1956
+ const ws = leadingWs(endKeep.value, segmenter);
1957
+ endKeep.value = endKeep.value.substring(ws.length);
1958
+ }
1959
+ } else if (startKeep && endKeep) {
1960
+ const newWsFull = leadingWs(endKeep.value, segmenter), [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion.value, segmenter);
1961
+ const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
1962
+ deletion.value = removePrefix(deletion.value, newWsStart);
1963
+ const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
1964
+ deletion.value = removeSuffix(deletion.value, newWsEnd);
1965
+ endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
1966
+ startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
1967
+ } else if (endKeep) {
1968
+ const endKeepWsPrefix = leadingWs(endKeep.value, segmenter);
1969
+ const overlap = maximumOverlap(trailingWs(deletion.value, segmenter), endKeepWsPrefix);
1970
+ deletion.value = removeSuffix(deletion.value, overlap);
1971
+ } else if (startKeep) {
1972
+ const overlap = maximumOverlap(trailingWs(startKeep.value, segmenter), leadingWs(deletion.value, segmenter));
1973
+ deletion.value = removePrefix(deletion.value, overlap);
1974
+ }
1975
+ }
1976
+ var WordsWithSpaceDiff = class extends Diff {
1977
+ tokenize(value) {
1978
+ const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, "ug");
1979
+ return value.match(regex) || [];
1980
+ }
1981
+ };
1982
+ new WordsWithSpaceDiff();
1983
+ //#endregion
1984
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/line.js
1985
+ var LineDiff = class extends Diff {
1986
+ constructor() {
1987
+ super(...arguments);
1988
+ this.tokenize = tokenize;
1989
+ }
1990
+ equals(left, right, options) {
1991
+ if (options.ignoreWhitespace) {
1992
+ if (!options.newlineIsToken || !left.includes("\n")) left = left.trim();
1993
+ if (!options.newlineIsToken || !right.includes("\n")) right = right.trim();
1994
+ } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
1995
+ if (left.endsWith("\n")) left = left.slice(0, -1);
1996
+ if (right.endsWith("\n")) right = right.slice(0, -1);
1997
+ }
1998
+ return super.equals(left, right, options);
1999
+ }
2000
+ };
2001
+ const lineDiff = new LineDiff();
2002
+ function diffLines(oldStr, newStr, options) {
2003
+ return lineDiff.diff(oldStr, newStr, options);
2004
+ }
2005
+ function tokenize(value, options) {
2006
+ if (options.stripTrailingCr) value = value.replace(/\r\n/g, "\n");
2007
+ const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
2008
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) linesAndNewlines.pop();
2009
+ for (let i = 0; i < linesAndNewlines.length; i++) {
2010
+ const line = linesAndNewlines[i];
2011
+ if (i % 2 && !options.newlineIsToken) retLines[retLines.length - 1] += line;
2012
+ else retLines.push(line);
2013
+ }
2014
+ return retLines;
2015
+ }
2016
+ //#endregion
2017
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/sentence.js
2018
+ function isSentenceEndPunct(char) {
2019
+ return char == "." || char == "!" || char == "?";
2020
+ }
2021
+ var SentenceDiff = class extends Diff {
2022
+ tokenize(value) {
2023
+ var _a;
2024
+ const result = [];
2025
+ let tokenStartI = 0;
2026
+ for (let i = 0; i < value.length; i++) {
2027
+ if (i == value.length - 1) {
2028
+ result.push(value.slice(tokenStartI));
2029
+ break;
2030
+ }
2031
+ if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) {
2032
+ result.push(value.slice(tokenStartI, i + 1));
2033
+ i = tokenStartI = i + 1;
2034
+ while ((_a = value[i + 1]) === null || _a === void 0 ? void 0 : _a.match(/\s/)) i++;
2035
+ result.push(value.slice(tokenStartI, i + 1));
2036
+ tokenStartI = i + 1;
2037
+ }
2038
+ }
2039
+ return result;
2040
+ }
2041
+ };
2042
+ new SentenceDiff();
2043
+ //#endregion
2044
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/css.js
2045
+ var CssDiff = class extends Diff {
2046
+ tokenize(value) {
2047
+ return value.split(/([{}:;,]|\s+)/);
2048
+ }
2049
+ };
2050
+ new CssDiff();
2051
+ //#endregion
2052
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/json.js
2053
+ var JsonDiff = class extends Diff {
2054
+ constructor() {
2055
+ super(...arguments);
2056
+ this.tokenize = tokenize;
2057
+ }
2058
+ get useLongestToken() {
2059
+ return true;
2060
+ }
2061
+ castInput(value, options) {
2062
+ const { undefinedReplacement, stringifyReplacer = (k, v) => typeof v === "undefined" ? undefinedReplacement : v } = options;
2063
+ return typeof value === "string" ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, " ");
2064
+ }
2065
+ equals(left, right, options) {
2066
+ return super.equals(left.replace(/,([\r\n])/g, "$1"), right.replace(/,([\r\n])/g, "$1"), options);
2067
+ }
2068
+ };
2069
+ new JsonDiff();
2070
+ function canonicalize(obj, stack, replacementStack, replacer, key) {
2071
+ stack = stack || [];
2072
+ replacementStack = replacementStack || [];
2073
+ if (replacer) obj = replacer(key === void 0 ? "" : key, obj);
2074
+ let i;
2075
+ for (i = 0; i < stack.length; i += 1) if (stack[i] === obj) return replacementStack[i];
2076
+ let canonicalizedObj;
2077
+ if ("[object Array]" === Object.prototype.toString.call(obj)) {
2078
+ stack.push(obj);
2079
+ canonicalizedObj = new Array(obj.length);
2080
+ replacementStack.push(canonicalizedObj);
2081
+ for (i = 0; i < obj.length; i += 1) canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
2082
+ stack.pop();
2083
+ replacementStack.pop();
2084
+ return canonicalizedObj;
2085
+ }
2086
+ if (obj && obj.toJSON) obj = obj.toJSON();
2087
+ if (typeof obj === "object" && obj !== null) {
2088
+ stack.push(obj);
2089
+ canonicalizedObj = {};
2090
+ replacementStack.push(canonicalizedObj);
2091
+ const sortedKeys = [];
2092
+ let key;
2093
+ for (key in obj)
2094
+ /* istanbul ignore else */
2095
+ if (Object.prototype.hasOwnProperty.call(obj, key)) sortedKeys.push(key);
2096
+ sortedKeys.sort();
2097
+ for (i = 0; i < sortedKeys.length; i += 1) {
2098
+ key = sortedKeys[i];
2099
+ canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
2100
+ }
2101
+ stack.pop();
2102
+ replacementStack.pop();
2103
+ } else canonicalizedObj = obj;
2104
+ return canonicalizedObj;
2105
+ }
2106
+ //#endregion
2107
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/array.js
2108
+ var ArrayDiff = class extends Diff {
2109
+ tokenize(value) {
2110
+ return value.slice();
2111
+ }
2112
+ join(value) {
2113
+ return value;
2114
+ }
2115
+ removeEmpty(value) {
2116
+ return value;
2117
+ }
2118
+ };
2119
+ new ArrayDiff();
2120
+ //#endregion
2121
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-session@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5_@deepseek-ai+dsh-scope@0._f72072f8db4331da4f9afb922400d5de/node_modules/@deepseek-ai/dsh-session/lib/types/surface.js
2939
2122
  /** Runtime counterpart of the message-producing event union. */
2940
2123
  const SURFACE_EVENT_TYPES = /* @__PURE__ */ new Set([
2941
2124
  "system/message",
@@ -2976,11 +2159,86 @@ window.__ModuleLoader__.load({
2976
2159
  function stringValue(value) {
2977
2160
  return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
2978
2161
  }
2979
- function diffLines(value) {
2980
- const text = stringValue(value);
2162
+ /** Preserve file contents verbatim; only reject empty/non-string values. */
2163
+ function textValue(value) {
2164
+ return typeof value === "string" && value !== "" ? value : void 0;
2165
+ }
2166
+ function lineCount(value) {
2167
+ const text = textValue(value);
2981
2168
  if (text === void 0) return 0;
2982
- const body = text.endsWith("\n") ? text.slice(0, -1) : text.endsWith("\\n") ? text.slice(0, -2) : text;
2983
- return body === "" ? 0 : body.split(/\r?\n|\\n/).length;
2169
+ const body = text.endsWith("\n") ? text.slice(0, -1) : text;
2170
+ return body === "" ? 0 : body.split(/\r?\n/).length;
2171
+ }
2172
+ /**
2173
+ * Real added/removed line counts for one edited file. Metadata `diffs` carry
2174
+ * the replaced snippet, so counting the whole `oldText`/`newText` as changed
2175
+ * over-reports a one-line edit in a large block. Fall back to raw line counts
2176
+ * only when the diff engine cannot process the values.
2177
+ */
2178
+ function fileStat(oldValue, newValue) {
2179
+ const oldText = textValue(oldValue);
2180
+ const newText = textValue(newValue);
2181
+ if (oldText === void 0 && newText === void 0) return {
2182
+ added: 0,
2183
+ removed: 0
2184
+ };
2185
+ if (oldText === void 0) return {
2186
+ added: lineCount(newText),
2187
+ removed: 0
2188
+ };
2189
+ if (newText === void 0) return {
2190
+ added: 0,
2191
+ removed: lineCount(oldText)
2192
+ };
2193
+ try {
2194
+ let added = 0;
2195
+ let removed = 0;
2196
+ for (const change of diffLines(oldText, newText)) {
2197
+ const count = change.count ?? 0;
2198
+ if (change.added) added += count;
2199
+ else if (change.removed) removed += count;
2200
+ }
2201
+ return {
2202
+ added,
2203
+ removed
2204
+ };
2205
+ } catch {
2206
+ return {
2207
+ added: lineCount(newText),
2208
+ removed: lineCount(oldText)
2209
+ };
2210
+ }
2211
+ }
2212
+ /** Normalize separators, a leading `./`, and macOS's `/private` aliases. */
2213
+ function normalizePath(path) {
2214
+ let normalized = path.replaceAll("\\", "/");
2215
+ if (normalized.startsWith("./")) normalized = normalized.slice(2);
2216
+ for (const root of [
2217
+ "/private/tmp",
2218
+ "/private/var",
2219
+ "/private/etc"
2220
+ ]) {
2221
+ if (normalized === root) return root.slice(8);
2222
+ if (normalized.startsWith(`${root}/`)) return normalized.slice(8);
2223
+ }
2224
+ return normalized;
2225
+ }
2226
+ function isAbsolutePath$1(path) {
2227
+ return path.startsWith("/") || /^[A-Za-z]:\//.test(path);
2228
+ }
2229
+ /** Whether two spellings name the same file (relative vs absolute target). */
2230
+ function samePath(left, right) {
2231
+ const a = normalizePath(left);
2232
+ const b = normalizePath(right);
2233
+ if (a === b) return true;
2234
+ return a.endsWith(`/${b}`) || b.endsWith(`/${a}`);
2235
+ }
2236
+ /** Prefer the workspace-relative spelling for display and native file open. */
2237
+ function preferredPath(left, right) {
2238
+ const a = normalizePath(left);
2239
+ const b = normalizePath(right);
2240
+ if (isAbsolutePath$1(a) !== isAbsolutePath$1(b)) return isAbsolutePath$1(a) ? b : a;
2241
+ return a.length <= b.length ? a : b;
2984
2242
  }
2985
2243
  function jsonArgs(argsRaw) {
2986
2244
  if (argsRaw === void 0) return {};
@@ -3006,8 +2264,7 @@ window.__ModuleLoader__.load({
3006
2264
  if (path === void 0) return [];
3007
2265
  return [{
3008
2266
  path,
3009
- added: diffLines(item?.newText),
3010
- removed: diffLines(item?.oldText),
2267
+ ...fileStat(item?.oldText, item?.newText),
3011
2268
  kind: "file"
3012
2269
  }];
3013
2270
  });
@@ -3036,14 +2293,22 @@ window.__ModuleLoader__.load({
3036
2293
  "artifact"
3037
2294
  ].includes(token));
3038
2295
  }
2296
+ /** Read tool-result text only; metadata/args URLs are not deliverables. */
2297
+ function resultText(value) {
2298
+ if (typeof value === "string") return value;
2299
+ const message = record$1(value);
2300
+ const content = Array.isArray(message?.content) ? message.content : [];
2301
+ const parts = [];
2302
+ for (const item of content) {
2303
+ const text = textValue(record$1(item)?.text);
2304
+ if (text !== void 0) parts.push(text);
2305
+ }
2306
+ return parts.join("\n");
2307
+ }
3039
2308
  function resultWebsiteEntries(name, value) {
3040
2309
  if (!websiteTool(name)) return [];
3041
- let text = "";
3042
- try {
3043
- text = JSON.stringify(value) ?? "";
3044
- } catch {
3045
- return [];
3046
- }
2310
+ const text = resultText(value);
2311
+ if (text === "") return [];
3047
2312
  const urls = text.match(/https?:\/\/[^\s"'<>\\]+/gi) ?? [];
3048
2313
  return [...new Set(urls.map((url) => url.replace(/[),.;]+$/g, "")))].map((path) => ({
3049
2314
  path,
@@ -3068,7 +2333,7 @@ window.__ModuleLoader__.load({
3068
2333
  function appendEntries(current, next, seq) {
3069
2334
  const entries = [...current];
3070
2335
  for (const candidate of next) {
3071
- const existing = entries.findIndex((entry) => entry.path === candidate.path && entry.kind === candidate.kind);
2336
+ const existing = entries.findIndex((entry) => entry.kind === candidate.kind && (candidate.kind === "website" ? entry.path === candidate.path : samePath(entry.path, candidate.path)));
3072
2337
  if (existing === -1) {
3073
2338
  entries.push({
3074
2339
  ...candidate,
@@ -3080,7 +2345,8 @@ window.__ModuleLoader__.load({
3080
2345
  if (previous === void 0) continue;
3081
2346
  entries[existing] = {
3082
2347
  ...previous,
3083
- seq,
2348
+ ...candidate.kind === "file" ? { path: preferredPath(previous.path, candidate.path) } : {},
2349
+ seq: Math.max(previous.seq, seq),
3084
2350
  added: previous.added + candidate.added,
3085
2351
  removed: previous.removed + candidate.removed
3086
2352
  };
@@ -3093,8 +2359,7 @@ window.__ModuleLoader__.load({
3093
2359
  const args = jsonArgs(call.argsRaw);
3094
2360
  return [{
3095
2361
  path,
3096
- added: diffLines(args.new_string ?? args.new_text ?? args.content),
3097
- removed: diffLines(args.old_string ?? args.old_text),
2362
+ ...fileStat(args.old_string ?? args.old_text ?? args.old_str, args.new_string ?? args.new_text ?? args.new_str ?? args.content ?? args.file_text),
3098
2363
  kind: "file"
3099
2364
  }];
3100
2365
  }
@@ -3173,15 +2438,18 @@ window.__ModuleLoader__.load({
3173
2438
  value: { entries: context.state.entries }
3174
2439
  }
3175
2440
  };
2441
+ function nativeDeliverables(owner) {
2442
+ return record$1(owner.turn.data.get("deliverables"));
2443
+ }
3176
2444
  function nativeProducedEntries(owner) {
3177
- const produced = record$1(owner.turn.data.get("deliverables"))?.produced;
2445
+ const produced = nativeDeliverables(owner)?.produced;
3178
2446
  if (!Array.isArray(produced)) return [];
3179
2447
  const entries = [];
3180
2448
  for (const item of produced) {
3181
2449
  const value = record$1(item);
3182
2450
  const path = stringValue(value?.path);
3183
2451
  const seq = typeof value?.seq === "number" ? value.seq : Number.POSITIVE_INFINITY;
3184
- if (path === void 0 || seq > owner.seq || entries.some((entry) => entry.path === path)) continue;
2452
+ if (path === void 0 || seq > owner.seq || entries.some((entry) => samePath(entry.path, path))) continue;
3185
2453
  entries.push({
3186
2454
  path,
3187
2455
  seq,
@@ -3192,19 +2460,72 @@ window.__ModuleLoader__.load({
3192
2460
  }
3193
2461
  return entries;
3194
2462
  }
3195
- /** Select entries that were delivered before the closing assistant message. */
2463
+ /**
2464
+ * Whether DSH's own turn-tail deliverables card will claim this Turn. It
2465
+ * renders changed files when developer tools are enabled, so the plugin must
2466
+ * not draw a second file card in that case.
2467
+ */
2468
+ function nativeDeliverablesPresent(owner) {
2469
+ return nativeDeliverables(owner)?.changes !== void 0;
2470
+ }
2471
+ /**
2472
+ * Select entries that were delivered before the closing assistant message.
2473
+ * Native produced paths are merged instead of replacing the plugin's richer
2474
+ * entries, so a tool the plugin did not classify cannot hide the file.
2475
+ */
3196
2476
  function selectDeliverables(owner) {
3197
2477
  const value = owner.turn.data.get(DELIVERABLES_DATA_KEY);
3198
- if (value !== void 0) {
3199
- const entries = value.entries.filter((entry) => entry.seq <= owner.seq);
3200
- return entries.length === 0 ? null : entries;
3201
- }
3202
- const nativeEntries = nativeProducedEntries(owner);
3203
- return nativeEntries.length === 0 ? null : nativeEntries;
2478
+ let entries = [...value === void 0 ? [] : value.entries.filter((entry) => entry.seq <= owner.seq)];
2479
+ for (const entry of nativeProducedEntries(owner)) entries = appendEntries(entries, [{
2480
+ path: entry.path,
2481
+ added: entry.added,
2482
+ removed: entry.removed,
2483
+ kind: entry.kind
2484
+ }], entry.seq);
2485
+ return entries.length === 0 ? null : entries;
3204
2486
  }
3205
2487
  //#endregion
2488
+ //#region src/client/developer-tools.ts
2489
+ var DeveloperToolsPreferenceCell = class {
2490
+ listeners = /* @__PURE__ */ new Set();
2491
+ source;
2492
+ value = false;
2493
+ /** Point the cell at the settings service, or detach when it unloads. */
2494
+ attach(source) {
2495
+ this.source = source;
2496
+ this.refresh();
2497
+ const unsubscribe = source.subscribe(() => {
2498
+ this.refresh();
2499
+ });
2500
+ return () => {
2501
+ unsubscribe();
2502
+ if (this.source !== source) return;
2503
+ this.source = void 0;
2504
+ this.refresh();
2505
+ };
2506
+ }
2507
+ read() {
2508
+ return this.source?.getSnapshot() ?? false;
2509
+ }
2510
+ refresh() {
2511
+ const next = this.read();
2512
+ if (next === this.value) return;
2513
+ this.value = next;
2514
+ for (const listener of this.listeners) listener();
2515
+ }
2516
+ getSnapshot = () => this.value;
2517
+ subscribe = (listener) => {
2518
+ this.listeners.add(listener);
2519
+ return () => {
2520
+ this.listeners.delete(listener);
2521
+ };
2522
+ };
2523
+ };
2524
+ /** Shared cell consumed by the deliverables tail. */
2525
+ const developerTools = new DeveloperToolsPreferenceCell();
2526
+ //#endregion
3206
2527
  //#region \0dsh-css:/home/runner/work/dsh-codex-suite/dsh-codex-suite/packages/conversation-ui/src/client/DeliverablesCard.module.css.mjs
3207
- const css = "[data-turn-tail]:has([data-stream-deliverables-card])>:has([data-produced-files-row]){display:none!important}.sLyncq_root{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-primary);color:var(--dsw-alias-label-secondary);box-sizing:border-box;border-radius:12px;flex-direction:column;gap:0;margin-top:16px;padding:12px 12px 10px;font-size:14px;line-height:22px;display:flex}.sLyncq_header{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:flex-start;gap:12px;min-width:0;padding:4px 8px 12px;display:flex}.sLyncq_headerIcon{background:var(--dsw-alias-interactive-bg-hover);width:40px;height:40px;color:var(--dsw-alias-label-secondary);border-radius:10px;flex:none;justify-content:center;align-items:center;display:inline-flex}.sLyncq_heading{flex-direction:column;gap:1px;min-width:0;display:flex}.sLyncq_title{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:24px}.sLyncq_subtitle{color:var(--dsw-alias-label-tertiary);font:inherit;text-align:start;cursor:pointer;background:0 0;border:0;margin:0;padding:0;font-size:14px;line-height:20px}.sLyncq_subtitle:hover{color:var(--dsw-alias-label-secondary);text-decoration:underline}.sLyncq_list{flex-direction:column;min-width:0;padding:10px 8px 0;display:flex}.sLyncq_item{width:100%;min-width:0;min-height:28px;color:inherit;font:inherit;text-align:start;cursor:pointer;background:0 0;border:0;border-radius:5px;align-items:center;gap:0;margin:0;padding:2px 0;line-height:22px;display:flex}.sLyncq_item:hover{color:var(--dsw-alias-label-primary)}.sLyncq_item:focus-visible,.sLyncq_more:focus-visible,.sLyncq_subtitle:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.sLyncq_filePath,.sLyncq_websitePath{white-space:nowrap;flex:auto;min-width:0;display:flex;overflow:hidden}.sLyncq_parentPath{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.sLyncq_fileName{color:var(--dsw-alias-label-secondary);flex:none}.sLyncq_websitePath{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;overflow:hidden}.sLyncq_stats{white-space:nowrap;flex:none;gap:6px;margin-inline-start:8px;font-size:13px;line-height:22px;display:inline-flex}.sLyncq_added{color:var(--dsw-alias-label-success,#2f9e44)}.sLyncq_removed{color:var(--dsw-alias-label-danger,#d94841)}.sLyncq_external{color:var(--dsw-alias-label-tertiary);flex:none}.sLyncq_more{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-self:flex-start;align-items:center;gap:5px;margin:8px 8px 0;padding:0;line-height:22px;display:inline-flex}.sLyncq_more:hover{color:var(--dsw-alias-label-primary)}.sLyncq_moreIcon{flex:none;justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}";
2528
+ const css = ".sLyncq_root{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-primary);color:var(--dsw-alias-label-secondary);box-sizing:border-box;border-radius:12px;flex-direction:column;gap:0;margin-top:16px;padding:12px 12px 10px;font-size:14px;line-height:22px;display:flex}.sLyncq_header{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:flex-start;gap:12px;min-width:0;padding:4px 8px 12px;display:flex}.sLyncq_headerIcon{background:var(--dsw-alias-interactive-bg-hover);width:40px;height:40px;color:var(--dsw-alias-label-secondary);border-radius:10px;flex:none;justify-content:center;align-items:center;display:inline-flex}.sLyncq_heading{flex-direction:column;gap:1px;min-width:0;display:flex}.sLyncq_title{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:500;line-height:24px}.sLyncq_subtitle{color:var(--dsw-alias-label-tertiary);font:inherit;text-align:start;cursor:pointer;background:0 0;border:0;margin:0;padding:0;font-size:14px;line-height:20px}.sLyncq_subtitle:hover{color:var(--dsw-alias-label-secondary);text-decoration:underline}.sLyncq_list{flex-direction:column;min-width:0;padding:10px 8px 0;display:flex}.sLyncq_item{width:100%;min-width:0;min-height:28px;color:inherit;font:inherit;text-align:start;cursor:pointer;background:0 0;border:0;border-radius:5px;align-items:center;gap:0;margin:0;padding:2px 0;line-height:22px;display:flex}.sLyncq_item:hover{color:var(--dsw-alias-label-primary)}.sLyncq_item:focus-visible,.sLyncq_more:focus-visible,.sLyncq_subtitle:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.sLyncq_filePath,.sLyncq_websitePath{white-space:nowrap;flex:auto;min-width:0;display:flex;overflow:hidden}.sLyncq_parentPath{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.sLyncq_fileName{color:var(--dsw-alias-label-secondary);flex:none}.sLyncq_websitePath{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;overflow:hidden}.sLyncq_stats{white-space:nowrap;flex:none;gap:6px;margin-inline-start:8px;font-size:13px;line-height:22px;display:inline-flex}.sLyncq_added{color:var(--dsw-alias-label-success,#2f9e44)}.sLyncq_removed{color:var(--dsw-alias-label-danger,#d94841)}.sLyncq_external{color:var(--dsw-alias-label-tertiary);flex:none}.sLyncq_more{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-self:flex-start;align-items:center;gap:5px;margin:8px 8px 0;padding:0;line-height:22px;display:inline-flex}.sLyncq_more:hover{color:var(--dsw-alias-label-primary)}.sLyncq_moreIcon{flex:none;justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}";
3208
2529
  const tagId = "@jcy2387/dsh-conversation-ui/DeliverablesCard.module.css";
3209
2530
  if (typeof document !== "undefined") {
3210
2531
  let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]");
@@ -3217,24 +2538,24 @@ window.__ModuleLoader__.load({
3217
2538
  tag.textContent = css;
3218
2539
  }
3219
2540
  var DeliverablesCard_module_css_default = {
3220
- "list": "sLyncq_list",
3221
- "heading": "sLyncq_heading",
3222
- "root": "sLyncq_root",
3223
- "headerIcon": "sLyncq_headerIcon",
3224
- "fileName": "sLyncq_fileName",
3225
2541
  "websitePath": "sLyncq_websitePath",
3226
- "removed": "sLyncq_removed",
3227
- "stats": "sLyncq_stats",
3228
- "filePath": "sLyncq_filePath",
2542
+ "fileName": "sLyncq_fileName",
2543
+ "external": "sLyncq_external",
3229
2544
  "subtitle": "sLyncq_subtitle",
3230
2545
  "added": "sLyncq_added",
3231
- "parentPath": "sLyncq_parentPath",
3232
2546
  "moreIcon": "sLyncq_moreIcon",
3233
- "title": "sLyncq_title",
3234
- "item": "sLyncq_item",
3235
- "external": "sLyncq_external",
2547
+ "root": "sLyncq_root",
3236
2548
  "header": "sLyncq_header",
3237
- "more": "sLyncq_more"
2549
+ "heading": "sLyncq_heading",
2550
+ "item": "sLyncq_item",
2551
+ "parentPath": "sLyncq_parentPath",
2552
+ "removed": "sLyncq_removed",
2553
+ "title": "sLyncq_title",
2554
+ "more": "sLyncq_more",
2555
+ "stats": "sLyncq_stats",
2556
+ "headerIcon": "sLyncq_headerIcon",
2557
+ "list": "sLyncq_list",
2558
+ "filePath": "sLyncq_filePath"
3238
2559
  };
3239
2560
  //#endregion
3240
2561
  //#region src/client/DeliverablesCard.tsx
@@ -3255,60 +2576,67 @@ window.__ModuleLoader__.load({
3255
2576
  name: path.slice(separator + 1)
3256
2577
  };
3257
2578
  }
3258
- function hiddenKindLabel(entries) {
3259
- if (entries.every(isWebsite)) return "网站";
3260
- if (entries.every((entry) => !isWebsite(entry))) return "文件";
3261
- return "产物";
2579
+ function isAbsolutePath(path) {
2580
+ return path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path);
3262
2581
  }
3263
- function countLabel(entries) {
2582
+ /** First segments from relative entries, used to trim absolute siblings. */
2583
+ function relativePrefixes(entries) {
2584
+ const prefixes = /* @__PURE__ */ new Set();
2585
+ for (const entry of entries) {
2586
+ if (isWebsite(entry) || isAbsolutePath(entry.path)) continue;
2587
+ const first = entry.path.replaceAll("\\", "/").split("/")[0];
2588
+ if (first !== void 0 && first !== "" && first !== "." && first !== "..") prefixes.add(first);
2589
+ }
2590
+ return [...prefixes];
2591
+ }
2592
+ /**
2593
+ * Prefer the workspace-relative spelling for display. The slot owner does not
2594
+ * expose the session cwd, but a sibling relative entry identifies the first
2595
+ * path segment (`packages`, `src`, ...), which is enough to trim an absolute
2596
+ * tool-reported path without changing the path used for `openFile`.
2597
+ */
2598
+ function displayPath(path, prefixes) {
2599
+ if (!isAbsolutePath(path)) return path;
2600
+ const normalized = path.replaceAll("\\", "/");
2601
+ for (const prefix of prefixes) {
2602
+ const marker = `/${prefix}/`;
2603
+ const index = normalized.indexOf(marker);
2604
+ if (index >= 0) return normalized.slice(index + 1);
2605
+ }
2606
+ return path;
2607
+ }
2608
+ function hiddenKindLabel(entries, t) {
2609
+ if (entries.every(isWebsite)) return t("deliveriesWebsites");
2610
+ if (entries.every((entry) => !isWebsite(entry))) return t("deliveriesFiles");
2611
+ return t("deliveriesItems");
2612
+ }
2613
+ function countLabel(entries, t) {
3264
2614
  const files = entries.filter((entry) => !isWebsite(entry)).length;
3265
2615
  const websites = entries.length - files;
3266
- if (files > 0 && websites === 0) return `已编辑 ${files} 个文件`;
3267
- if (files === 0) return `交付 ${websites} 个网站`;
3268
- return `已交付 ${entries.length} 项产物`;
3269
- }
3270
- function collectEditStats(turn) {
3271
- const result = /* @__PURE__ */ new Map();
3272
- if (typeof document === "undefined") return result;
3273
- for (const element of document.querySelectorAll("[data-stream-edit-stats]")) {
3274
- if (element.closest("[data-stream-turn]")?.getAttribute("data-stream-turn") !== String(turn)) continue;
3275
- const path = element.getAttribute("data-stream-edit-file");
3276
- const match = /\+(\d+)\s+-\s*(\d+)/.exec(element.textContent ?? "");
3277
- if (path === null || match === null) continue;
3278
- result.set(path, {
3279
- added: Number(match[1]),
3280
- removed: Number(match[2])
3281
- });
3282
- }
3283
- return result;
2616
+ if (files > 0 && websites === 0) return t("deliveriesEdited").replace("{count}", String(files));
2617
+ if (files === 0) return t("deliveriesWebsitesCount").replace("{count}", String(websites));
2618
+ return t("deliveriesCount").replace("{count}", String(entries.length));
3284
2619
  }
3285
2620
  /** Render this list contribution only when its Turn has delivered files or websites. */
3286
2621
  function DeliverablesTail(props) {
3287
2622
  const matched = selectDeliverables(props);
3288
- return matched === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeliverablesCard, {
2623
+ const showNativeFiles = (0, react.useSyncExternalStore)(developerTools.subscribe, developerTools.getSnapshot, developerTools.getSnapshot) && nativeDeliverablesPresent(props);
2624
+ if (matched === null) return null;
2625
+ const entries = showNativeFiles ? matched.filter(isWebsite) : matched;
2626
+ return entries.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeliverablesCard, {
3289
2627
  ...props,
3290
- matched
2628
+ matched: entries
3291
2629
  });
3292
2630
  }
3293
2631
  /** Codex-style turn-tail card for edited files and deployed websites. */
3294
- function DeliverablesCard({ matched, turn, openFile }) {
2632
+ function DeliverablesCard({ matched: entries, openFile, t }) {
3295
2633
  const [expanded, setExpanded] = (0, react.useState)(false);
3296
- const [domStats, setDomStats] = (0, react.useState)(() => /* @__PURE__ */ new Map());
3297
- (0, react.useLayoutEffect)(() => {
3298
- setDomStats(collectEditStats(turn.turn));
3299
- }, [turn.turn, matched]);
3300
- const entries = matched.map((entry) => {
3301
- const stats = domStats.get(entry.path);
3302
- return stats === void 0 || entry.added > 0 || entry.removed > 0 ? entry : {
3303
- ...entry,
3304
- ...stats
3305
- };
3306
- });
3307
2634
  const visible = expanded ? entries : entries.slice(0, 3);
3308
2635
  const hidden = entries.length - visible.length;
3309
- const summary = countLabel(entries);
2636
+ const summary = countLabel(entries, t);
3310
2637
  const hasFiles = entries.some((entry) => !isWebsite(entry));
3311
2638
  const firstWebsite = entries.find(isWebsite);
2639
+ const prefixes = relativePrefixes(entries);
3312
2640
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3313
2641
  className: DeliverablesCard_module_css_default.root,
3314
2642
  "data-stream-deliverables-card": true,
@@ -3325,20 +2653,13 @@ window.__ModuleLoader__.load({
3325
2653
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
3326
2654
  className: DeliverablesCard_module_css_default.title,
3327
2655
  children: summary
3328
- }), hasFiles ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3329
- type: "button",
3330
- className: DeliverablesCard_module_css_default.subtitle,
3331
- onClick: () => {
3332
- setExpanded(true);
3333
- },
3334
- children: "查看更改 ↗"
3335
- }) : firstWebsite !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2656
+ }), !hasFiles && firstWebsite !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3336
2657
  type: "button",
3337
2658
  className: DeliverablesCard_module_css_default.subtitle,
3338
2659
  onClick: () => {
3339
2660
  openWebsite(firstWebsite.path);
3340
2661
  },
3341
- children: "打开网站 ↗"
2662
+ children: [t("deliveriesOpenWebsite"), " ↗"]
3342
2663
  }) : null]
3343
2664
  })]
3344
2665
  }),
@@ -3346,7 +2667,8 @@ window.__ModuleLoader__.load({
3346
2667
  className: DeliverablesCard_module_css_default.list,
3347
2668
  children: visible.map((entry) => {
3348
2669
  const website = isWebsite(entry);
3349
- const parts = pathParts(entry.path);
2670
+ const display = displayPath(entry.path, prefixes);
2671
+ const parts = pathParts(display);
3350
2672
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3351
2673
  type: "button",
3352
2674
  className: DeliverablesCard_module_css_default.item,
@@ -3365,7 +2687,7 @@ window.__ModuleLoader__.load({
3365
2687
  children: parts.parent
3366
2688
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3367
2689
  className: DeliverablesCard_module_css_default.fileName,
3368
- children: parts.name || entry.path
2690
+ children: parts.name || display
3369
2691
  })]
3370
2692
  }),
3371
2693
  !website && (entry.added > 0 || entry.removed > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -3393,17 +2715,11 @@ window.__ModuleLoader__.load({
3393
2715
  onClick: () => {
3394
2716
  setExpanded(true);
3395
2717
  },
3396
- children: [
3397
- "再显示 ",
3398
- hidden,
3399
- " 个",
3400
- hiddenKindLabel(entries.slice(visible.length)),
3401
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3402
- className: DeliverablesCard_module_css_default.moreIcon,
3403
- "aria-hidden": true,
3404
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineMedium, {})
3405
- })
3406
- ]
2718
+ children: [t("deliveriesMore").replace("{count}", String(hidden)).replace("{kind}", hiddenKindLabel(entries.slice(visible.length), t)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2719
+ className: DeliverablesCard_module_css_default.moreIcon,
2720
+ "aria-hidden": true,
2721
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineMedium, {})
2722
+ })]
3407
2723
  }),
3408
2724
  expanded && entries.length > 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3409
2725
  type: "button",
@@ -3411,18 +2727,21 @@ window.__ModuleLoader__.load({
3411
2727
  onClick: () => {
3412
2728
  setExpanded(false);
3413
2729
  },
3414
- children: ["收起 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3415
- className: DeliverablesCard_module_css_default.moreIcon,
3416
- "aria-hidden": true,
3417
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutlineMedium, {})
3418
- })]
2730
+ children: [
2731
+ t("deliveriesCollapse"),
2732
+ " ",
2733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2734
+ className: DeliverablesCard_module_css_default.moreIcon,
2735
+ "aria-hidden": true,
2736
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutlineMedium, {})
2737
+ })
2738
+ ]
3419
2739
  })
3420
2740
  ]
3421
2741
  });
3422
2742
  }
3423
2743
  //#endregion
3424
2744
  //#region src/settings-api.ts
3425
- /** Shared wire vocabulary for the plugin-owned settings RPC channel. */
3426
2745
  /** Dedicated, loopback-only RPC channel registered by the Host half. */
3427
2746
  const CONVERSATION_SETTINGS_RPC_CHANNEL = "/conversation-ui";
3428
2747
  /** Endpoints accepted by {@link CONVERSATION_SETTINGS_RPC_CHANNEL}. */
@@ -3442,7 +2761,11 @@ window.__ModuleLoader__.load({
3442
2761
  "npm",
3443
2762
  "development",
3444
2763
  "unmanaged"
3445
- ].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.thinkAutoExpand !== "boolean" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-conversation-ui: malformed settings response");
2764
+ ].includes(data.installation) || typeof data.writable !== "boolean" || typeof data.thinkAutoExpand !== "boolean" || !["typewriter", "teleprompter"].includes(data.mode) || ![
2765
+ "realtime",
2766
+ "balanced",
2767
+ "silky"
2768
+ ].includes(data.preset) || typeof data.revealCharsPerSec !== "number" || typeof data.canUpgrade !== "boolean") throw new Error("dsh-conversation-ui: malformed settings response");
3446
2769
  return data;
3447
2770
  }
3448
2771
  function upgradeView(value) {
@@ -3459,8 +2782,8 @@ window.__ModuleLoader__.load({
3459
2782
  async read() {
3460
2783
  return settingsView(accepted(await connection.rpc.call(CONVERSATION_SETTINGS_RPC_CHANNEL, CONVERSATION_SETTINGS_RPC.read, {})));
3461
2784
  },
3462
- async write(thinkAutoExpand) {
3463
- return settingsView(accepted(await connection.rpc.call(CONVERSATION_SETTINGS_RPC_CHANNEL, CONVERSATION_SETTINGS_RPC.write, { thinkAutoExpand })));
2785
+ async write(patch) {
2786
+ return settingsView(accepted(await connection.rpc.call(CONVERSATION_SETTINGS_RPC_CHANNEL, CONVERSATION_SETTINGS_RPC.write, patch)));
3464
2787
  },
3465
2788
  async upgrade() {
3466
2789
  return upgradeView(accepted(await connection.rpc.call(CONVERSATION_SETTINGS_RPC_CHANNEL, CONVERSATION_SETTINGS_RPC.upgrade, {})));
@@ -3475,9 +2798,21 @@ window.__ModuleLoader__.load({
3475
2798
  /** English copy. */
3476
2799
  const en = {
3477
2800
  title: "Conversation UI",
3478
- description: "Codex-style progress, tool activity, and final answers.",
2801
+ description: "Assistant reveal and deliveries using the native DSH conversation UI.",
3479
2802
  thinkAutoExpand: "Auto-expand thinking",
3480
2803
  thinkAutoExpandHint: "Open the thinking block while it streams. Turn off to keep it collapsed.",
2804
+ mode: "Reveal mode",
2805
+ modeHint: "Typewriter reveals each grapheme; Teleprompter shows the newest snapshot immediately.",
2806
+ modeTypewriter: "Typewriter",
2807
+ modeTeleprompter: "Teleprompter",
2808
+ preset: "Smoothing preset",
2809
+ presetHint: "Controls how the reveal cadence follows the model stream.",
2810
+ presetRealtime: "Realtime",
2811
+ presetBalanced: "Balanced",
2812
+ presetSilky: "Silky",
2813
+ revealSpeed: "Reveal speed",
2814
+ revealSpeedHint: "Graphemes per second while typewriter mode is streaming; the settle drain is faster.",
2815
+ charsPerSecond: "chars/s",
3481
2816
  readOnly: "This deployment stores settings read-only.",
3482
2817
  loading: "Loading plugin settings…",
3483
2818
  unavailable: "Plugin settings are unavailable in this connection.",
@@ -3492,6 +2827,15 @@ window.__ModuleLoader__.load({
3492
2827
  updating: "Updating…",
3493
2828
  restartRequired: "Updated. Restart Harness to load the new version.",
3494
2829
  updateFailed: "The package update failed; your current version is unchanged.",
2830
+ deliveriesFiles: "files",
2831
+ deliveriesWebsites: "websites",
2832
+ deliveriesItems: "items",
2833
+ deliveriesEdited: "Edited {count} files",
2834
+ deliveriesWebsitesCount: "Delivered {count} websites",
2835
+ deliveriesCount: "Delivered {count} items",
2836
+ deliveriesOpenWebsite: "Open website",
2837
+ deliveriesMore: "Show {count} more {kind}",
2838
+ deliveriesCollapse: "Collapse",
3495
2839
  save: "Save",
3496
2840
  saving: "Saving…",
3497
2841
  discard: "Discard",
@@ -3501,9 +2845,21 @@ window.__ModuleLoader__.load({
3501
2845
  /** Simplified Chinese copy. */
3502
2846
  const zh = {
3503
2847
  title: "对话界面增强",
3504
- description: "按 Codex 风格展示过程答复、工具活动和最终答案。",
2848
+ description: "在 DSH 原生对话界面中增强助手揭示和产物展示。",
3505
2849
  thinkAutoExpand: "自动展开思考",
3506
2850
  thinkAutoExpandHint: "思考块在流式时自动展开;关闭后保持折叠,可手动展开。",
2851
+ mode: "揭示模式",
2852
+ modeHint: "typewriter 按字素逐字揭示;teleprompter 直接显示最新快照。",
2853
+ modeTypewriter: "打字机",
2854
+ modeTeleprompter: "即时",
2855
+ preset: "平滑预设",
2856
+ presetHint: "控制揭示节奏如何跟随模型流。",
2857
+ presetRealtime: "实时",
2858
+ presetBalanced: "均衡",
2859
+ presetSilky: "丝滑",
2860
+ revealSpeed: "揭示速度",
2861
+ revealSpeedHint: "typewriter 流式时的字素数/秒;收尾排空会更快。",
2862
+ charsPerSecond: "字/秒",
3507
2863
  readOnly: "本部署的设置为只读。",
3508
2864
  loading: "正在加载插件设置…",
3509
2865
  unavailable: "当前连接无法访问插件设置。",
@@ -3518,6 +2874,15 @@ window.__ModuleLoader__.load({
3518
2874
  updating: "更新中…",
3519
2875
  restartRequired: "已更新;重启 Harness 后加载新版本。",
3520
2876
  updateFailed: "包更新失败,当前版本未改变。",
2877
+ deliveriesFiles: "文件",
2878
+ deliveriesWebsites: "网站",
2879
+ deliveriesItems: "产物",
2880
+ deliveriesEdited: "已编辑 {count} 个文件",
2881
+ deliveriesWebsitesCount: "交付 {count} 个网站",
2882
+ deliveriesCount: "已交付 {count} 项产物",
2883
+ deliveriesOpenWebsite: "打开网站",
2884
+ deliveriesMore: "再显示 {count} 个{kind}",
2885
+ deliveriesCollapse: "收起",
3521
2886
  save: "保存",
3522
2887
  saving: "保存中…",
3523
2888
  discard: "放弃修改",
@@ -3539,15 +2904,6 @@ window.__ModuleLoader__.load({
3539
2904
  "balanced",
3540
2905
  "silky"
3541
2906
  ];
3542
- /** Input rows stay native; Assistant and Turn control are replaced. */
3543
- const SKIP_WRAP = /* @__PURE__ */ new Set([
3544
- "assistant-step",
3545
- "turn-process",
3546
- "user",
3547
- "steering",
3548
- "turn-trigger",
3549
- "command-input"
3550
- ]);
3551
2907
  /**
3552
2908
  * Read the Host-bridged boot config. The inline script is produced by this
3553
2909
  * plugin's Host half from a schema-validated value, so only the structural
@@ -3567,82 +2923,20 @@ window.__ModuleLoader__.load({
3567
2923
  return raw;
3568
2924
  }
3569
2925
  /**
3570
- * Wrap every keyed Chat row except `assistant-step` in place. A second
3571
- * register with the same `children` table throws because the child slot is
3572
- * already declared, and only the winning entry receives `renderSlot`;
3573
- * swapping `entry.component` keeps the original children, locale, and inject
3574
- * seats. `assistant-step` is replaced below so text and Think use the
3575
- * Codex-style Assistant presentation.
3576
- * @param ctx - Browser context carrying the slot registry.
3577
- * @returns Restorer that puts the original components back.
3578
- */
3579
- function wrapGrowingChatRows(ctx, config) {
3580
- const restores = [];
3581
- const wrapped = /* @__PURE__ */ new WeakSet();
3582
- const wrapAll = () => {
3583
- for (const entry of ctx.slots.entries("conversation.chat.node")) {
3584
- const key = entry.options.key;
3585
- if (key === void 0 || SKIP_WRAP.has(key)) continue;
3586
- const current = entry.component;
3587
- if (typeof current !== "function" && (typeof current !== "object" || current === null) || wrapped.has(current)) continue;
3588
- const inner = current;
3589
- const next = wrapFollowNodeView(inner, {
3590
- minSpeedPxPerSec: config.scrollSpeedPxPerSec,
3591
- maxSpeedPxPerSec: config.maxScrollSpeedPxPerSec
3592
- });
3593
- wrapped.add(next);
3594
- entry.component = next;
3595
- restores.push(() => {
3596
- if (entry.component === next) entry.component = inner;
3597
- });
3598
- }
3599
- };
3600
- wrapAll();
3601
- const off = ctx.on("slots/changed", (key) => {
3602
- if (key === "conversation.chat.node") wrapAll();
3603
- });
3604
- return () => {
3605
- off();
3606
- for (const restore of restores) restore();
3607
- };
3608
- }
3609
- /** Bridge DSH's transcript preference through the native Chat view tree. */
3610
- function wrapNativeChatView(ctx) {
3611
- const restores = [];
3612
- const wrapped = /* @__PURE__ */ new WeakSet();
3613
- const wrapAll = () => {
3614
- for (const entry of ctx.slots.entries("conversation.view")) {
3615
- if (entry.options.id !== "chat") continue;
3616
- const current = entry.component;
3617
- if (typeof current !== "function" && (typeof current !== "object" || current === null) || wrapped.has(current)) continue;
3618
- const inner = current;
3619
- const next = wrapTranscriptView(inner);
3620
- wrapped.add(next);
3621
- entry.component = next;
3622
- restores.push(() => {
3623
- if (entry.component === next) entry.component = inner;
3624
- });
3625
- }
3626
- };
3627
- wrapAll();
3628
- const off = ctx.on("slots/changed", (key) => {
3629
- if (key === "conversation.view") wrapAll();
3630
- });
3631
- return () => {
3632
- off();
3633
- for (const restore of restores) restore();
3634
- };
3635
- }
3636
- /**
3637
2926
  * A live preference cell read by `useSyncExternalStore`. It starts on the
3638
- * shared default and, once the plugin-owned settings controller is attached,
3639
- * tracks the resolved `thinkAutoExpand` value. A composition (or test) that
3640
- * ships no settings surface keeps the cell on the default.
2927
+ * boot-bridged composition defaults and, once the plugin-owned settings
2928
+ * controller has loaded, tracks the durable reveal and thinking preferences.
2929
+ * A composition (or test) that ships no settings surface keeps the fallback.
3641
2930
  */
3642
2931
  var PreferenceCell = class {
2932
+ fallback;
3643
2933
  listeners = /* @__PURE__ */ new Set();
3644
2934
  card;
3645
- value = DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand;
2935
+ value;
2936
+ constructor(fallback) {
2937
+ this.fallback = fallback;
2938
+ this.value = fallback;
2939
+ }
3646
2940
  /** Re-point the cell at the plugin-owned settings controller. */
3647
2941
  attach(card) {
3648
2942
  this.card = card;
@@ -3658,11 +2952,18 @@ window.__ModuleLoader__.load({
3658
2952
  };
3659
2953
  }
3660
2954
  read() {
3661
- return this.card?.getSnapshot().thinkAutoExpand ?? DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand;
2955
+ const snapshot = this.card?.getSnapshot();
2956
+ if (snapshot === void 0 || snapshot.status !== "ready") return this.fallback;
2957
+ return {
2958
+ thinkAutoExpand: snapshot.thinkAutoExpand,
2959
+ mode: snapshot.mode,
2960
+ preset: snapshot.preset,
2961
+ revealCharsPerSec: snapshot.revealCharsPerSec
2962
+ };
3662
2963
  }
3663
2964
  refresh() {
3664
2965
  const next = this.read();
3665
- if (next === this.value) return;
2966
+ if (sameSettings(next, this.value)) return;
3666
2967
  this.value = next;
3667
2968
  for (const listener of this.listeners) listener();
3668
2969
  }
@@ -3674,29 +2975,41 @@ window.__ModuleLoader__.load({
3674
2975
  };
3675
2976
  };
3676
2977
  };
2978
+ function sameSettings(left, right) {
2979
+ return left.thinkAutoExpand === right.thinkAutoExpand && left.mode === right.mode && left.preset === right.preset && left.revealCharsPerSec === right.revealCharsPerSec;
2980
+ }
3677
2981
  /**
3678
- * Register the Codex-style renderer after the conversation package declares the
3679
- * keyed Chat node seat. A lower priority shadows the built-in assistant row;
3680
- * every other growing row is wrapped in place so Tool cards, retries, and
3681
- * workflow runs keep their Codex presentation without taking ownership of the
3682
- * DSH scrollport. The Host-bridged configuration selects immediate or
3683
- * typewriter reveal and smoothing; the plugin-owned settings RPC supplies the
3684
- * settings surface is composed.
3685
- * @param ctx - Browser context carrying the shared slot registry.
2982
+ * Add assistant reveal/preferences and independent delivery/settings cards.
2983
+ * Native ChatView, tools, Turn controls, grouping, and scroll remain untouched.
2984
+ * @param ctx - browser context carrying the shared slot registry.
3686
2985
  */
3687
2986
  function apply(ctx) {
3688
- const config = readBootConfig();
3689
- const preference = new PreferenceCell();
3690
- ctx.slots.inject("conversation.view", () => wrapNativeChatView(ctx));
2987
+ const bootConfig = readBootConfig();
2988
+ const preference = new PreferenceCell({
2989
+ ...DEFAULT_CONVERSATION_SETTINGS,
2990
+ mode: bootConfig.mode,
2991
+ preset: bootConfig.preset,
2992
+ revealCharsPerSec: bootConfig.revealCharsPerSec
2993
+ });
2994
+ ctx.inject(["configForms"], (configCtx) => {
2995
+ const source = configCtx.configForms.developerTools?.enabled;
2996
+ if (source === void 0) return;
2997
+ return developerTools.attach(source);
2998
+ });
3691
2999
  ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
3692
3000
  name: "conversation.chat.turnTail",
3693
3001
  id: "@jcy2387/dsh-conversation-ui",
3694
3002
  priority: -100,
3695
- registrant: "dsh-conversation-ui"
3003
+ registrant: "dsh-conversation-ui",
3004
+ locale: NS
3696
3005
  }, DeliverablesTail));
3697
3006
  ctx.inject(["uiConversation"], (deliverablesCtx) => {
3698
3007
  return deliverablesCtx.uiConversation.events.register(deliverablesDefinition);
3699
3008
  });
3009
+ ctx.inject(["locale"], (localeCtx) => localeCtx.effect(() => localeCtx.locale.register(NS, {
3010
+ zh,
3011
+ en
3012
+ }), "dsh-conversation-ui: dictionaries"));
3700
3013
  ctx.inject([
3701
3014
  "slots",
3702
3015
  "locale",
@@ -3705,10 +3018,6 @@ window.__ModuleLoader__.load({
3705
3018
  const card = new ConversationCardController(createConversationSettingsApi(settingsCtx.get("connection")));
3706
3019
  const detachPreference = preference.attach(card);
3707
3020
  card.start();
3708
- settingsCtx.effect(() => settingsCtx.locale.register(NS, {
3709
- zh,
3710
- en
3711
- }), "dsh-conversation-ui: settings dictionaries");
3712
3021
  for (const bundle of ["@jcy2387/dsh-conversation-ui", "@jcy2387/dsh-suite"]) settingsCtx.slots.inject("plugins.bundle.config", () => settingsCtx.slots.register({
3713
3022
  name: "plugins.bundle.config",
3714
3023
  key: bundle,
@@ -3720,39 +3029,7 @@ window.__ModuleLoader__.load({
3720
3029
  detachPreference();
3721
3030
  };
3722
3031
  });
3723
- const configured = function ConversationConfiguredView(props) {
3724
- const thinkAutoExpand = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot, preference.getSnapshot);
3725
- return (0, react.createElement)(TypewriterAssistantNodeView, {
3726
- ...props,
3727
- mode: config.mode,
3728
- preset: config.preset,
3729
- revealCharsPerSec: config.revealCharsPerSec,
3730
- scrollSpeedPxPerSec: config.scrollSpeedPxPerSec,
3731
- maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
3732
- thinkAutoExpand
3733
- });
3734
- };
3735
- ctx.slots.inject("conversation.chat.node", () => {
3736
- const unwrap = wrapGrowingChatRows(ctx, config);
3737
- const unshadow = ctx.slots.register({
3738
- name: "conversation.chat.node",
3739
- key: "assistant-step",
3740
- priority: -100,
3741
- locale: "chat",
3742
- registrant: "dsh-conversation-ui"
3743
- }, configured);
3744
- return () => {
3745
- unshadow();
3746
- unwrap();
3747
- };
3748
- });
3749
- ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
3750
- name: "conversation.chat.node",
3751
- key: "turn-process",
3752
- priority: -100,
3753
- locale: "chat",
3754
- registrant: "dsh-conversation-ui"
3755
- }, CodexTurnProcessNodeView));
3032
+ ctx.slots.inject("conversation.chat.node", () => registerAssistantEnhancement(ctx, preference));
3756
3033
  }
3757
3034
  //#endregion
3758
3035
  exports.apply = apply;