@deepseek-ai/dsh-client-ui-chat 0.1.3-alpha.2 → 0.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -9,6 +9,40 @@ window.__ModuleLoader__.load({
9
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
10
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
11
11
  let react_dom = require("react-dom");
12
+ //#region ../../util/workspace-path/src/file-address.ts
13
+ /** The scheme and type every file address opens with. */
14
+ const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
15
+ /** Component-encode one id or path segment, keeping `:` literal for drive letters. */
16
+ function encodeSegment(segment) {
17
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
18
+ }
19
+ /** Encode a `/`-separated path segment by segment. */
20
+ function encodePath(path) {
21
+ return path.split("/").map(encodeSegment).join("/");
22
+ }
23
+ /**
24
+ * Build the address of a file inside one Session's workspace.
25
+ * @param sessionId - the Session whose workspace root the path is relative to.
26
+ * @param path - workspace-relative path; backslashes are normalized to `/`, and a leading `./` or `/` is dropped.
27
+ * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
28
+ */
29
+ function sessionFileAddress(sessionId, path) {
30
+ const relative = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "").replace(/^\/+/, "");
31
+ return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(relative)}`;
32
+ }
33
+ /**
34
+ * Build the address of a file by its absolute path.
35
+ * @param path - absolute path; backslashes are normalized to `/` and the leading `/` is dropped,
36
+ * except that a UNC path (`\\server\share`) keeps one empty first segment.
37
+ * @returns the `dsh-resource://file/absolute/<path>` address.
38
+ */
39
+ function absoluteFileAddress(path) {
40
+ const normalized = path.replace(/\\/g, "/");
41
+ const unc = normalized.startsWith("//");
42
+ const absolute = normalized.replace(/^\/+/, "");
43
+ return `${FILE_ADDRESS_PREFIX}absolute/${unc ? "/" : ""}${encodePath(absolute)}`;
44
+ }
45
+ //#endregion
12
46
  //#region ../../util/workspace-path/src/index.ts
13
47
  /**
14
48
  * Browser-safe Workspace path and display helpers.
@@ -19,16 +53,30 @@ window.__ModuleLoader__.load({
19
53
  return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
20
54
  }
21
55
  /**
22
- * Resolve a Workspace-relative path into the Host-facing spelling used by path operations.
23
- * @param cwd - Session Workspace root, when known.
24
- * @param path - Absolute or Workspace-relative path.
25
- * @returns an absolute path when a Workspace root is available, otherwise the original path.
56
+ * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
57
+ * @param path - the path to classify.
58
+ * @returns `true` for an absolute path; `false` for a Workspace-relative one.
59
+ */
60
+ function isAbsoluteWorkspacePath(path) {
61
+ return path.startsWith("/") || isWindowsStylePath(path);
62
+ }
63
+ /**
64
+ * The address for a path as a caller holds it: a relative path, or an absolute
65
+ * path inside the Session's workspace, becomes a `session`-scoped address; an
66
+ * absolute path outside it, or one whose workspace root is unknown, becomes an
67
+ * `absolute`-scoped address.
68
+ * @param sessionId - the Session the path is read in.
69
+ * @param cwd - that Session's workspace root, when known.
70
+ * @param path - absolute or workspace-relative path, in either separator spelling.
71
+ * @returns the `dsh-resource://file/…` address.
26
72
  */
27
- function resolveWorkspacePath(cwd, path) {
28
- if (path.startsWith("/") || isWindowsStylePath(path)) return path;
29
- if (cwd === void 0 || cwd === "") return path;
30
- const separator = isWindowsStylePath(cwd) && cwd.includes("\\") ? "\\" : "/";
31
- return `${cwd.replace(/[/\\]+$/, "")}${separator}${path.replace(/^[/\\]+/, "")}`;
73
+ function fileAddressFor(sessionId, cwd, path) {
74
+ const normalized = path.replace(/\\/g, "/");
75
+ if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
76
+ const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
77
+ if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
78
+ if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
79
+ return absoluteFileAddress(normalized);
32
80
  }
33
81
  //#endregion
34
82
  //#region lib/types/client/contract/snapshot.js
@@ -1407,7 +1455,7 @@ window.__ModuleLoader__.load({
1407
1455
  }
1408
1456
  //#endregion
1409
1457
  //#region lib/types/client/stores.js
1410
- /** Per-Session Chat selection store shared by the transcript and details panel. */
1458
+ /** Per-Session Chat view store. */
1411
1459
  /**
1412
1460
  * Resolve the manually expanded answer for one Turn.
1413
1461
  * @param state - Chat store snapshot.
@@ -1418,33 +1466,25 @@ window.__ModuleLoader__.load({
1418
1466
  return state.turnProcesses.find((entry) => entry.turn === turn);
1419
1467
  }
1420
1468
  /**
1421
- * Create the Chat selection store handle.
1469
+ * Create the Chat view store handle.
1422
1470
  * @returns a handle instantiated once per rendered Session scope.
1423
1471
  */
1424
1472
  function createChatStore() {
1425
1473
  return (0, _deepseek_ai_dsh_client_store.defineStore)({
1426
- init: () => ({
1427
- selection: null,
1428
- turnProcesses: []
1429
- }),
1430
- actions: {
1431
- select: (draft, target) => {
1432
- draft.selection = target;
1433
- },
1434
- setTurnProcessOpen: (draft, turn, answerStep, open) => {
1435
- const index = draft.turnProcesses.findIndex((entry) => entry.turn === turn);
1436
- if (!open) {
1437
- if (index >= 0) draft.turnProcesses.splice(index, 1);
1438
- return;
1439
- }
1440
- const next = {
1441
- turn,
1442
- answerStep
1443
- };
1444
- if (index < 0) draft.turnProcesses.push(next);
1445
- else draft.turnProcesses[index] = next;
1474
+ init: () => ({ turnProcesses: [] }),
1475
+ actions: { setTurnProcessOpen: (draft, turn, answerStep, open) => {
1476
+ const index = draft.turnProcesses.findIndex((entry) => entry.turn === turn);
1477
+ if (!open) {
1478
+ if (index >= 0) draft.turnProcesses.splice(index, 1);
1479
+ return;
1446
1480
  }
1447
- }
1481
+ const next = {
1482
+ turn,
1483
+ answerStep
1484
+ };
1485
+ if (index < 0) draft.turnProcesses.push(next);
1486
+ else draft.turnProcesses[index] = next;
1487
+ } }
1448
1488
  });
1449
1489
  }
1450
1490
  //#endregion
@@ -1515,7 +1555,7 @@ window.__ModuleLoader__.load({
1515
1555
  return location?.kind === "turn" || location?.kind === "step" ? location.turn.turn : void 0;
1516
1556
  }
1517
1557
  /** Subscribe, apply Turn-process visibility, and dispatch one stable Context key. */
1518
- const ChatNodeSeat = (0, react.memo)(function ChatNodeSeat({ nodeKey, useChatNode, useChatNodeProcess, historyIncomplete, compactTranscript, selectedCallId, cwd, openFile, inspectCall, forkAt, loadImage, renderMessageImages, fileMentions, useStore, actions, renderSlot, t }) {
1558
+ const ChatNodeSeat = (0, react.memo)(function ChatNodeSeat({ nodeKey, useChatNode, useChatNodeProcess, historyIncomplete, compactTranscript, cwd, openFile, inspectCall, forkAt, loadImage, renderMessageImages, fileMentions, useStore, actions, renderSlot, t }) {
1519
1559
  const node = useChatNode(nodeKey);
1520
1560
  const routedNode = node;
1521
1561
  const turn = turnOf(routedNode);
@@ -1549,7 +1589,6 @@ window.__ModuleLoader__.load({
1549
1589
  if (processMember) setOpen(true);
1550
1590
  }, [processMember, setOpen]));
1551
1591
  const owner = (0, react.useMemo)(() => node === void 0 ? null : {
1552
- selectedCallId,
1553
1592
  cwd,
1554
1593
  openFile,
1555
1594
  inspectCall,
@@ -1560,7 +1599,6 @@ window.__ModuleLoader__.load({
1560
1599
  turnProcess
1561
1600
  }, [
1562
1601
  node,
1563
- selectedCallId,
1564
1602
  cwd,
1565
1603
  openFile,
1566
1604
  inspectCall,
@@ -1985,10 +2023,6 @@ window.__ModuleLoader__.load({
1985
2023
  const message = error instanceof Error ? error.message : String(error);
1986
2024
  return message === "" ? fallback : message;
1987
2025
  }
1988
- /** ProducedFiles opens the session workspace as `.`. */
1989
- function isFolderOpenPath(path) {
1990
- return path === ".";
1991
- }
1992
2026
  /**
1993
2027
  * Prompt-RPC identities already rendered by durable material: user/steering
1994
2028
  * node sources plus queue occurrences. A submission echo whose identity
@@ -2063,7 +2097,6 @@ window.__ModuleLoader__.load({
2063
2097
  const openError = useSession((s) => s.openError);
2064
2098
  const hasMore = useSession((s) => s.hasMore);
2065
2099
  const loadingOlder = useSession((s) => s.loadingOlder);
2066
- const selectedCallId = useStore((s) => s.selection?.callId);
2067
2100
  const compactTranscript = useTranscriptView((mode) => mode === "compact");
2068
2101
  const inspectCall = (0, react.useCallback)((callId) => {
2069
2102
  openView("trajectory", callId);
@@ -2071,10 +2104,10 @@ window.__ModuleLoader__.load({
2071
2104
  const [fileOpenError, setFileOpenError] = (0, react.useState)(null);
2072
2105
  const [fileOpenBusy, setFileOpenBusy] = (0, react.useState)(false);
2073
2106
  const fileOpenRequest = (0, react.useRef)(0);
2074
- const requestOpenFile = (0, react.useCallback)((path) => {
2107
+ const requestOpenFile = (0, react.useCallback)((path, options) => {
2075
2108
  const id = ++fileOpenRequest.current;
2076
2109
  setFileOpenBusy(true);
2077
- openFile(path).then(() => {
2110
+ (options === void 0 ? openFile(path) : openFile(path, options)).then(() => {
2078
2111
  if (id !== fileOpenRequest.current) return;
2079
2112
  setFileOpenError(null);
2080
2113
  setFileOpenBusy(false);
@@ -2082,7 +2115,7 @@ window.__ModuleLoader__.load({
2082
2115
  if (id !== fileOpenRequest.current) return;
2083
2116
  setFileOpenError({
2084
2117
  path,
2085
- message: openFailureMessage(error, t(isFolderOpenPath(path) ? "fileOpen.folderUnknown" : "fileOpen.unknown"))
2118
+ message: openFailureMessage(error, t("fileOpen.unknown"))
2086
2119
  });
2087
2120
  setFileOpenBusy(false);
2088
2121
  });
@@ -2521,7 +2554,6 @@ window.__ModuleLoader__.load({
2521
2554
  compactTranscript,
2522
2555
  useStore,
2523
2556
  actions,
2524
- selectedCallId,
2525
2557
  cwd,
2526
2558
  openFile: requestOpenFile,
2527
2559
  inspectCall,
@@ -2564,7 +2596,6 @@ window.__ModuleLoader__.load({
2564
2596
  })
2565
2597
  ]
2566
2598
  }), fileOpenError !== null && (0, react_jsx_runtime.jsx)(FileOpenErrorDialog, {
2567
- path: fileOpenError.path,
2568
2599
  message: fileOpenError.message,
2569
2600
  busy: fileOpenBusy,
2570
2601
  onClose: closeFileOpenError,
@@ -2576,12 +2607,12 @@ window.__ModuleLoader__.load({
2576
2607
  });
2577
2608
  }
2578
2609
  /** In-page Host open-path refusal: the wire reason plus a retry of the same path. */
2579
- function FileOpenErrorDialog({ path, message, busy, onClose, onRetry, t }) {
2610
+ function FileOpenErrorDialog({ message, busy, onClose, onRetry, t }) {
2580
2611
  return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2581
2612
  open: true,
2582
2613
  onClose,
2583
2614
  closeLabel: t("close"),
2584
- title: t(isFolderOpenPath(path) ? "fileOpen.folderTitle" : "fileOpen.title"),
2615
+ title: t("fileOpen.title"),
2585
2616
  description: message,
2586
2617
  footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2587
2618
  variant: "outline",
@@ -2609,20 +2640,14 @@ window.__ModuleLoader__.load({
2609
2640
  "duration.compactSeconds": "{seconds}秒",
2610
2641
  "duration.compactMinutes": "{minutes}分{seconds}秒",
2611
2642
  "duration.milliseconds": "{milliseconds}毫秒",
2612
- "stats.counts": "{turns} 轮 · {steps} 步",
2613
- "stats.llm": "LLM {duration}",
2614
- "stats.toolCall": "工具调用 {duration}",
2615
- "stats.ttftAverage": "首 token 平均 {duration}",
2616
- "stats.tokensPerSecond": "{throughput} tok/s",
2643
+ "stats.counts": "{turns} 轮 {steps} 步",
2617
2644
  "stats.cacheHit": "缓存命中 {percent}%",
2618
- "stats.tokens": "输入 {input} tok · 输出 {output} tok",
2619
- "details.title": "详情",
2620
- "details.close": "关闭详情",
2621
- "details.empty": "点击消息流中的工具行查看详情",
2622
- "details.notInWindow": "该调用不在当前窗口内",
2623
- "details.input": "输入",
2624
- "details.output": "输出",
2625
- "details.running": "运行中…",
2645
+ "stats.dialog.title": "会话统计",
2646
+ "stats.dialog.usageTitle": "Token 用量",
2647
+ "stats.dialog.llmTime": "模型用时",
2648
+ "stats.dialog.toolTime": "工具调用用时",
2649
+ "stats.dialog.ttft": "首 token 平均(TTFT)",
2650
+ "stats.dialog.speed": "输出速度(TPS)",
2626
2651
  "chat.loadingHistory": "载入历史…",
2627
2652
  "chat.loadError": "历史加载失败:{message}({code})",
2628
2653
  "chat.loadOlder": "加载更早",
@@ -2638,10 +2663,9 @@ window.__ModuleLoader__.load({
2638
2663
  "settings.transcript.compact": "Compact",
2639
2664
  "fileOpen.title": "无法打开文件",
2640
2665
  "fileOpen.unknown": "无法打开此文件",
2641
- "fileOpen.folderTitle": "无法打开文件夹",
2642
- "fileOpen.folderUnknown": "无法打开此文件夹",
2643
2666
  "message.extraBlock": "附加内容块",
2644
2667
  "message.systemPrompt": "系统提示词",
2668
+ "message.systemPromptUpdate": "系统提示词更新",
2645
2669
  "message.contextInjection": "上下文注入",
2646
2670
  "message.contextRecall": "跨会话召回",
2647
2671
  "message.referenceSummary": "引用会话 · {labels}",
@@ -2722,20 +2746,14 @@ window.__ModuleLoader__.load({
2722
2746
  "duration.compactSeconds": "{seconds}s",
2723
2747
  "duration.compactMinutes": "{minutes}m{seconds}s",
2724
2748
  "duration.milliseconds": "{milliseconds}ms",
2725
- "stats.counts": "{turns} turns · {steps} steps",
2726
- "stats.llm": "LLM {duration}",
2727
- "stats.toolCall": "Tool call {duration}",
2728
- "stats.ttftAverage": "TTFT avg {duration}",
2729
- "stats.tokensPerSecond": "{throughput} tok/s",
2749
+ "stats.counts": "{turns} turns {steps} steps",
2730
2750
  "stats.cacheHit": "Cache hit {percent}%",
2731
- "stats.tokens": "Input {input} tok · Output {output} tok",
2732
- "details.title": "Details",
2733
- "details.close": "Close details",
2734
- "details.empty": "Click a tool row in the message flow to view its details",
2735
- "details.notInWindow": "This call is outside the current window",
2736
- "details.input": "Input",
2737
- "details.output": "Output",
2738
- "details.running": "Running…",
2751
+ "stats.dialog.title": "Session statistics",
2752
+ "stats.dialog.usageTitle": "Token usage",
2753
+ "stats.dialog.llmTime": "LLM time",
2754
+ "stats.dialog.toolTime": "Tool time",
2755
+ "stats.dialog.ttft": "Avg time to first token (TTFT)",
2756
+ "stats.dialog.speed": "Tokens per second (TPS)",
2739
2757
  "chat.loadingHistory": "Loading history…",
2740
2758
  "chat.loadError": "Failed to load history: {message} ({code})",
2741
2759
  "chat.loadOlder": "Load earlier",
@@ -2751,10 +2769,9 @@ window.__ModuleLoader__.load({
2751
2769
  "settings.transcript.compact": "Compact",
2752
2770
  "fileOpen.title": "Couldn’t open file",
2753
2771
  "fileOpen.unknown": "Couldn’t open this file",
2754
- "fileOpen.folderTitle": "Couldn’t open folder",
2755
- "fileOpen.folderUnknown": "Couldn’t open this folder",
2756
2772
  "message.extraBlock": "Extra content block",
2757
2773
  "message.systemPrompt": "System prompt",
2774
+ "message.systemPromptUpdate": "System prompt update",
2758
2775
  "message.contextInjection": "Context injection",
2759
2776
  "message.contextRecall": "Session recall",
2760
2777
  "message.referenceSummary": "Referenced session · {labels}",
@@ -2876,7 +2893,9 @@ window.__ModuleLoader__.load({
2876
2893
  return newline === -1 ? visible : visible.slice(newline + 1);
2877
2894
  }
2878
2895
  /**
2879
- * Render one assistant reasoning block as the Think disclosure row.
2896
+ * Render one assistant reasoning block as the Think disclosure row. The
2897
+ * collapsed summary omits double-asterisk markers; expanded content preserves
2898
+ * the complete text.
2880
2899
  * @param props.text - complete or streaming reasoning text.
2881
2900
  * @param props.running - whether this block is the streaming tail.
2882
2901
  * @param props.t - conversation locale seat for the running status.
@@ -2884,7 +2903,7 @@ window.__ModuleLoader__.load({
2884
2903
  */
2885
2904
  function ReasoningRow({ text, running, t }) {
2886
2905
  const [expanded, setExpanded] = (0, react.useState)(false);
2887
- const summary = running ? latestLine(text) : firstLine(text);
2906
+ const summary = (running ? latestLine(text) : firstLine(text)).replaceAll("**", "");
2888
2907
  return (0, react_jsx_runtime.jsxs)("div", {
2889
2908
  className: ReasoningRow_module_css_default.root,
2890
2909
  "data-variant": "think",
@@ -2943,9 +2962,28 @@ window.__ModuleLoader__.load({
2943
2962
  };
2944
2963
  //#endregion
2945
2964
  //#region lib/types/client/chat/AssistantMarkdown.js
2965
+ /**
2966
+ * Map one authored media destination to the same-origin workspace-file URL.
2967
+ * @param protocol - `window.location.protocol` at render time.
2968
+ * @param origin - `window.location.origin` at render time.
2969
+ * @param value - The authored markdown destination, exactly as written.
2970
+ * @returns The API URL for an absolute POSIX path on an HTTP(S) page, or
2971
+ * undefined when the destination cannot be a Host-served local file
2972
+ * (non-HTTP transport such as Electron `file://`, protocol-relative or
2973
+ * relative destinations).
2974
+ */
2975
+ function localPathMediaUrl(protocol, origin, value) {
2976
+ if (protocol !== "http:" && protocol !== "https:") return void 0;
2977
+ if (value.length === 0 || !value.startsWith("/") || value.startsWith("//")) return void 0;
2978
+ return `${origin}/api/file?path=${encodeURIComponent(value)}`;
2979
+ }
2946
2980
  /** Reasoning block as the Think variant summary row (figma 39:28304). */
2947
2981
  const AssistantMarkdown = (0, react.memo)(function AssistantMarkdown({ blocks, streaming, interrupted, renderMessageImages, reasoningHidden = false, revealProcess, mentions, t }) {
2948
2982
  const labels = (0, react.useMemo)(() => markdownLabels(t), [t]);
2983
+ const pathImages = (0, react.useMemo)(() => {
2984
+ const { protocol, origin } = window.location;
2985
+ return { resolve: (value) => localPathMediaUrl(protocol, origin, value) };
2986
+ }, []);
2949
2987
  const last = blocks.length - 1;
2950
2988
  if (!(streaming || interrupted === true || blocks.some((block) => block.kind !== "tool-call"))) return null;
2951
2989
  const rendered = [];
@@ -2958,7 +2996,8 @@ window.__ModuleLoader__.load({
2958
2996
  text: block.text,
2959
2997
  streaming,
2960
2998
  labels,
2961
- fileMentions: mentions
2999
+ fileMentions: mentions,
3000
+ pathImages
2962
3001
  }, i));
2963
3002
  break;
2964
3003
  case "reasoning":
@@ -3190,17 +3229,18 @@ window.__ModuleLoader__.load({
3190
3229
  /**
3191
3230
  * Render one complete system prompt as a collapsed disclosure whose expanded
3192
3231
  * body is the same opaque context chrome: 141px code-block scrollport and
3193
- * model-facing text with its real line breaks.
3194
- * @param props - Complete prompt text and the locale seat.
3232
+ * model-facing text with its real line breaks. An in-history update uses the
3233
+ * same row under its own title.
3234
+ * @param props - Complete prompt text, whether it is an update, and the locale seat.
3195
3235
  * @returns The system-prompt disclosure row.
3196
3236
  */
3197
- function SystemPromptRow({ text, t }) {
3237
+ function SystemPromptRow({ text, update = false, t }) {
3198
3238
  const [open, setOpen] = (0, react.useState)(false);
3199
3239
  return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
3200
3240
  className: ContextInjectionRow_module_css_default.root,
3201
3241
  icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
3202
3242
  chevronClassName: ContextInjectionRow_module_css_default.chevron,
3203
- title: t("message.systemPrompt"),
3243
+ title: t(update ? "message.systemPromptUpdate" : "message.systemPrompt"),
3204
3244
  open,
3205
3245
  expandable: true,
3206
3246
  expandOnRowClick: true,
@@ -3225,6 +3265,7 @@ window.__ModuleLoader__.load({
3225
3265
  const SystemPromptNodeView = (0, react.memo)(function SystemPromptNodeView({ node, t }) {
3226
3266
  return (0, react_jsx_runtime.jsx)(SystemPromptRow, {
3227
3267
  text: node.data.text,
3268
+ update: node.data.update === true,
3228
3269
  t
3229
3270
  });
3230
3271
  });
@@ -3357,37 +3398,7 @@ window.__ModuleLoader__.load({
3357
3398
  return `99.${"9".repeat(distinguishingPlaces - 1)}${10 - roundedLoss}`;
3358
3399
  }
3359
3400
  //#endregion
3360
- //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/chat/TurnUsagePanel.module.css.mjs
3361
- const css$4 = ".Q51KRG_root{min-width:0;display:inline-flex}.Q51KRG_root+.Q51KRG_root{margin-left:-6px}.Q51KRG_trigger{min-width:0;height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);font-variant-numeric:tabular-nums;line-height:calc(24px + var(--dsh-content-font-delta,0px));white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:28px;align-items:center;gap:4px;padding:6px 8px;display:inline-flex}.Q51KRG_label{text-overflow:ellipsis;min-width:0;overflow:hidden}.Q51KRG_trigger svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px));flex:none}.Q51KRG_trigger:hover,.Q51KRG_trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}@media (width<=480px){.Q51KRG_trigger{width:calc(28px + var(--dsh-content-font-delta,0px));justify-content:center;padding:6px}.Q51KRG_trigger .Q51KRG_label{display:none}.Q51KRG_root+.Q51KRG_root{margin-left:0}}.Q51KRG_panel{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-secondary);cursor:default;border:0;border-radius:12px;padding:16px;font-size:12px;line-height:18px;position:fixed}.Q51KRG_title{color:var(--dsw-alias-label-primary);justify-content:space-between;gap:16px;margin-bottom:8px;font-weight:500;display:flex}.Q51KRG_titleRule{border-top:.5px solid var(--dsw-alias-border-l2);margin-bottom:10px}.Q51KRG_titleValue{font-variant-numeric:tabular-nums}.Q51KRG_titleLabel{align-items:center;gap:6px;min-width:0;display:inline-flex}.Q51KRG_titleLabel svg{flex:none;width:14px;height:14px}.Q51KRG_details{color:var(--dsw-alias-label-tertiary);grid-template-columns:minmax(76px,auto) minmax(0,1fr);gap:6px 16px;margin:0;display:grid}.Q51KRG_details dt,.Q51KRG_details dd{min-width:0;margin:0}.Q51KRG_details dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}.Q51KRG_details .Q51KRG_route{overflow-wrap:anywhere}.Q51KRG_reasoning{color:var(--dsw-alias-label-tertiary);white-space:nowrap}";
3362
- const tagId$4 = "@deepseek-ai/dsh-client-ui-chat/TurnUsagePanel.module.css";
3363
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
3364
- const tag = document.createElement("style");
3365
- tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-chat";
3366
- tag.dataset.pluginCss = tagId$4;
3367
- tag.textContent = css$4;
3368
- document.head.appendChild(tag);
3369
- }
3370
- var TurnUsagePanel_module_css_default = {
3371
- "details": "Q51KRG_details",
3372
- "label": "Q51KRG_label",
3373
- "panel": "Q51KRG_panel",
3374
- "reasoning": "Q51KRG_reasoning",
3375
- "root": "Q51KRG_root",
3376
- "route": "Q51KRG_route",
3377
- "title": "Q51KRG_title",
3378
- "titleLabel": "Q51KRG_titleLabel",
3379
- "titleRule": "Q51KRG_titleRule",
3380
- "titleValue": "Q51KRG_titleValue",
3381
- "trigger": "Q51KRG_trigger"
3382
- };
3383
- //#endregion
3384
- //#region lib/types/client/chat/TurnUsagePanel.js
3385
- function formatCompactCount(value, t) {
3386
- return t("message.turnUsage.count", { count: formatTokens(value, t) });
3387
- }
3388
- function formatExactCount(value, t) {
3389
- return t("message.turnUsage.count", { count: formatExactTokens(value, t) });
3390
- }
3401
+ //#region lib/types/client/chat/stat-dialog.js
3391
3402
  /** Viewport margin the placement clamp keeps (the Menu portal margin). */
3392
3403
  const PANEL_MARGIN = 12;
3393
3404
  /** Distance between the trigger's top edge and the panel's bottom. */
@@ -3401,9 +3412,16 @@ window.__ModuleLoader__.load({
3401
3412
  left: 0,
3402
3413
  top: 0
3403
3414
  };
3404
- /** One trigger-anchored dialog seat: open state, viewport-clamped placement, outside-close. */
3405
- function useStatDialog() {
3406
- const [open, setOpen] = (0, react.useState)(false);
3415
+ /**
3416
+ * One trigger-anchored dialog seat: open state, viewport-clamped placement, outside-close.
3417
+ * @param controlled - external open state; when given the seat reads and writes
3418
+ * it instead of owning its own, letting sibling dialogs share one exclusive slot.
3419
+ * @returns the seat; spread `pos ?? MEASURE_STYLE` onto the portaled panel.
3420
+ */
3421
+ function useStatDialog(controlled) {
3422
+ const [ownOpen, setOwnOpen] = (0, react.useState)(false);
3423
+ const open = controlled?.open ?? ownOpen;
3424
+ const setOpen = controlled?.setOpen ?? setOwnOpen;
3407
3425
  const rootRef = (0, react.useRef)(null);
3408
3426
  const panelRef = (0, react.useRef)(null);
3409
3427
  const pos = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredPosition)({
@@ -3424,7 +3442,7 @@ window.__ModuleLoader__.load({
3424
3442
  return () => {
3425
3443
  document.removeEventListener("keydown", onKeyDown);
3426
3444
  };
3427
- }, [open]);
3445
+ }, [open, setOpen]);
3428
3446
  return {
3429
3447
  open,
3430
3448
  setOpen,
@@ -3433,6 +3451,51 @@ window.__ModuleLoader__.load({
3433
3451
  pos
3434
3452
  };
3435
3453
  }
3454
+ //#endregion
3455
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/chat/TurnUsagePanel.module.css.mjs
3456
+ const css$4 = ".Q51KRG_root{min-width:0;display:inline-flex}.Q51KRG_root+.Q51KRG_root{margin-left:-6px}.Q51KRG_trigger{min-width:0;height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);font-variant-numeric:tabular-nums;line-height:calc(24px + var(--dsh-content-font-delta,0px));white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:28px;align-items:center;gap:4px;padding:6px 8px;display:inline-flex}.Q51KRG_label{text-overflow:ellipsis;min-width:0;overflow:hidden}.Q51KRG_trigger svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px));flex:none}.Q51KRG_trigger:hover,.Q51KRG_trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}@media (width<=480px){.Q51KRG_trigger{width:calc(28px + var(--dsh-content-font-delta,0px));justify-content:center;padding:6px}.Q51KRG_trigger .Q51KRG_label{display:none}.Q51KRG_root+.Q51KRG_root{margin-left:0}}";
3457
+ const tagId$4 = "@deepseek-ai/dsh-client-ui-chat/TurnUsagePanel.module.css";
3458
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
3459
+ const tag = document.createElement("style");
3460
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-chat";
3461
+ tag.dataset.pluginCss = tagId$4;
3462
+ tag.textContent = css$4;
3463
+ document.head.appendChild(tag);
3464
+ }
3465
+ var TurnUsagePanel_module_css_default = {
3466
+ "label": "Q51KRG_label",
3467
+ "root": "Q51KRG_root",
3468
+ "trigger": "Q51KRG_trigger"
3469
+ };
3470
+ //#endregion
3471
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/chat/stat-dialog.module.css.mjs
3472
+ const css$3 = ".bRhRbq_panel{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-secondary);cursor:default;border:0;border-radius:12px;padding:16px;font-size:12px;line-height:18px;position:fixed}.bRhRbq_title{color:var(--dsw-alias-label-primary);justify-content:space-between;gap:16px;margin-bottom:8px;font-weight:500;display:flex}.bRhRbq_titleRule{border-top:.5px solid var(--dsw-alias-border-l2);margin-bottom:10px}.bRhRbq_titleValue{font-variant-numeric:tabular-nums}.bRhRbq_titleLabel{align-items:center;gap:6px;min-width:0;display:inline-flex}.bRhRbq_titleLabel svg{flex:none;width:14px;height:14px}.bRhRbq_details{color:var(--dsw-alias-label-tertiary);grid-template-columns:minmax(76px,auto) minmax(0,1fr);gap:6px 16px;margin:0;display:grid}.bRhRbq_details dt,.bRhRbq_details dd{min-width:0;margin:0}.bRhRbq_details dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}.bRhRbq_details .bRhRbq_route{overflow-wrap:anywhere}.bRhRbq_reasoning{color:var(--dsw-alias-label-tertiary);white-space:nowrap}";
3473
+ const tagId$3 = "@deepseek-ai/dsh-client-ui-chat/stat-dialog.module.css";
3474
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
3475
+ const tag = document.createElement("style");
3476
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-chat";
3477
+ tag.dataset.pluginCss = tagId$3;
3478
+ tag.textContent = css$3;
3479
+ document.head.appendChild(tag);
3480
+ }
3481
+ var stat_dialog_module_css_default = {
3482
+ "details": "bRhRbq_details",
3483
+ "panel": "bRhRbq_panel",
3484
+ "reasoning": "bRhRbq_reasoning",
3485
+ "route": "bRhRbq_route",
3486
+ "title": "bRhRbq_title",
3487
+ "titleLabel": "bRhRbq_titleLabel",
3488
+ "titleRule": "bRhRbq_titleRule",
3489
+ "titleValue": "bRhRbq_titleValue"
3490
+ };
3491
+ //#endregion
3492
+ //#region lib/types/client/chat/TurnUsagePanel.js
3493
+ function formatCompactCount(value, t) {
3494
+ return t("message.turnUsage.count", { count: formatTokens(value, t) });
3495
+ }
3496
+ function formatExactCount(value, t) {
3497
+ return t("message.turnUsage.count", { count: formatExactTokens(value, t) });
3498
+ }
3436
3499
  /**
3437
3500
  * Turn-usage IconActions pill with a click-open Turn-usage details dialog.
3438
3501
  * @param props - Turn usage buckets and locale seat.
@@ -3460,31 +3523,31 @@ window.__ModuleLoader__.load({
3460
3523
  })]
3461
3524
  }), open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
3462
3525
  ref: panelRef,
3463
- className: TurnUsagePanel_module_css_default.panel,
3526
+ className: stat_dialog_module_css_default.panel,
3464
3527
  role: "dialog",
3465
3528
  "aria-label": t("message.turnUsage.title"),
3466
3529
  style: pos ?? MEASURE_STYLE,
3467
3530
  children: [
3468
3531
  (0, react_jsx_runtime.jsxs)("div", {
3469
- className: TurnUsagePanel_module_css_default.title,
3532
+ className: stat_dialog_module_css_default.title,
3470
3533
  children: [(0, react_jsx_runtime.jsxs)("span", {
3471
- className: TurnUsagePanel_module_css_default.titleLabel,
3534
+ className: stat_dialog_module_css_default.titleLabel,
3472
3535
  children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDatabaseOutline16, {}), t("message.turnUsage.title")]
3473
3536
  }), (0, react_jsx_runtime.jsx)("span", {
3474
- className: TurnUsagePanel_module_css_default.titleValue,
3537
+ className: stat_dialog_module_css_default.titleValue,
3475
3538
  children: formatExactCount(usage.totalTokens, t)
3476
3539
  })]
3477
3540
  }),
3478
3541
  (0, react_jsx_runtime.jsx)("div", {
3479
- className: TurnUsagePanel_module_css_default.titleRule,
3542
+ className: stat_dialog_module_css_default.titleRule,
3480
3543
  "aria-hidden": true
3481
3544
  }),
3482
3545
  (0, react_jsx_runtime.jsxs)("dl", {
3483
- className: TurnUsagePanel_module_css_default.details,
3546
+ className: stat_dialog_module_css_default.details,
3484
3547
  "data-turn-usage-details": true,
3485
3548
  children: [
3486
3549
  routes !== "" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.model") }), (0, react_jsx_runtime.jsx)("dd", {
3487
- className: TurnUsagePanel_module_css_default.route,
3550
+ className: stat_dialog_module_css_default.route,
3488
3551
  children: routes
3489
3552
  })] }),
3490
3553
  cacheHit !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheHit") }), (0, react_jsx_runtime.jsx)("dd", { children: `${cacheHit}%` })] }),
@@ -3494,7 +3557,7 @@ window.__ModuleLoader__.load({
3494
3557
  usage.cacheWriteTokens !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheWrite") }), (0, react_jsx_runtime.jsx)("dd", { children: formatExactCount(usage.cacheWriteTokens, t) })] }),
3495
3558
  (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.output") }),
3496
3559
  (0, react_jsx_runtime.jsxs)("dd", { children: [formatExactCount(usage.outputTokens, t), usage.reasoningTokens !== void 0 && (0, react_jsx_runtime.jsx)("span", {
3497
- className: TurnUsagePanel_module_css_default.reasoning,
3560
+ className: stat_dialog_module_css_default.reasoning,
3498
3561
  children: t("message.turnUsage.reasoning", { tokens: formatExactCount(usage.reasoningTokens, t) })
3499
3562
  })] })
3500
3563
  ]
@@ -3527,24 +3590,24 @@ window.__ModuleLoader__.load({
3527
3590
  })]
3528
3591
  }), open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
3529
3592
  ref: panelRef,
3530
- className: TurnUsagePanel_module_css_default.panel,
3593
+ className: stat_dialog_module_css_default.panel,
3531
3594
  role: "dialog",
3532
3595
  "aria-label": t("message.turnTime.title"),
3533
3596
  style: pos ?? MEASURE_STYLE,
3534
3597
  children: [
3535
3598
  (0, react_jsx_runtime.jsx)("div", {
3536
- className: TurnUsagePanel_module_css_default.title,
3599
+ className: stat_dialog_module_css_default.title,
3537
3600
  children: (0, react_jsx_runtime.jsxs)("span", {
3538
- className: TurnUsagePanel_module_css_default.titleLabel,
3601
+ className: stat_dialog_module_css_default.titleLabel,
3539
3602
  children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutline16, {}), t("message.turnTime.title")]
3540
3603
  })
3541
3604
  }),
3542
3605
  (0, react_jsx_runtime.jsx)("div", {
3543
- className: TurnUsagePanel_module_css_default.titleRule,
3606
+ className: stat_dialog_module_css_default.titleRule,
3544
3607
  "aria-hidden": true
3545
3608
  }),
3546
3609
  (0, react_jsx_runtime.jsxs)("dl", {
3547
- className: TurnUsagePanel_module_css_default.details,
3610
+ className: stat_dialog_module_css_default.details,
3548
3611
  "data-turn-time-details": true,
3549
3612
  children: [
3550
3613
  (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnTime.duration") }),
@@ -3569,13 +3632,13 @@ window.__ModuleLoader__.load({
3569
3632
  }
3570
3633
  //#endregion
3571
3634
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/chat/TurnTailNodeView.module.css.mjs
3572
- const css$3 = ".TS9iAW_root{flex-direction:column;gap:16px;display:flex}.TS9iAW_actions{margin-left:-6px}";
3573
- const tagId$3 = "@deepseek-ai/dsh-client-ui-chat/TurnTailNodeView.module.css";
3574
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
3635
+ const css$2 = ".TS9iAW_root{flex-direction:column;gap:16px;display:flex}.TS9iAW_actions{margin-left:-6px}";
3636
+ const tagId$2 = "@deepseek-ai/dsh-client-ui-chat/TurnTailNodeView.module.css";
3637
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
3575
3638
  const tag = document.createElement("style");
3576
3639
  tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-chat";
3577
- tag.dataset.pluginCss = tagId$3;
3578
- tag.textContent = css$3;
3640
+ tag.dataset.pluginCss = tagId$2;
3641
+ tag.textContent = css$2;
3579
3642
  document.head.appendChild(tag);
3580
3643
  }
3581
3644
  var TurnTailNodeView_module_css_default = {
@@ -3789,22 +3852,25 @@ window.__ModuleLoader__.load({
3789
3852
  return metrics;
3790
3853
  }
3791
3854
  //#endregion
3792
- //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/chat/StatsLine.module.css.mjs
3793
- const css$2 = ".-NDN2W_root{text-align:center;max-width:var(--dsh-chat-content-width);box-sizing:border-box;width:100%;padding:4px calc(var(--dsh-composer-side-clearance) + 16px) 0px;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;margin:0 auto;display:block;overflow:hidden}.-NDN2W_sep{color:var(--dsw-alias-separator-primary);margin:0 10px}";
3794
- const tagId$2 = "@deepseek-ai/dsh-client-ui-chat/StatsLine.module.css";
3795
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
3855
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/chat/StatsPills.module.css.mjs
3856
+ const css$1 = ".bOPqQW_root{max-width:var(--dsh-chat-content-width);box-sizing:border-box;width:100%;padding:4px calc(var(--dsh-composer-side-clearance) + 16px) 0px;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));justify-content:center;gap:12px;margin:0 auto;display:flex}.bOPqQW_anchor{min-width:0;display:inline-flex}.bOPqQW_pill{box-sizing:border-box;max-width:100%;color:var(--dsw-alias-label-tertiary);font:inherit;font-variant-numeric:tabular-nums;line-height:inherit;white-space:nowrap;background:0 0;border:none;border-radius:24px;align-items:center;gap:6px;padding:1px 8px;display:inline-flex}.bOPqQW_pill svg{flex:none;width:14px;height:14px}button.bOPqQW_pill{cursor:pointer}button.bOPqQW_pill:hover,button.bOPqQW_pill[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.bOPqQW_label{text-overflow:ellipsis;min-width:0;overflow:hidden}.bOPqQW_sep{color:var(--dsw-alias-separator-primary);margin:0 6px}";
3857
+ const tagId$1 = "@deepseek-ai/dsh-client-ui-chat/StatsPills.module.css";
3858
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
3796
3859
  const tag = document.createElement("style");
3797
3860
  tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-chat";
3798
- tag.dataset.pluginCss = tagId$2;
3799
- tag.textContent = css$2;
3861
+ tag.dataset.pluginCss = tagId$1;
3862
+ tag.textContent = css$1;
3800
3863
  document.head.appendChild(tag);
3801
3864
  }
3802
- var StatsLine_module_css_default = {
3803
- "root": "-NDN2W_root",
3804
- "sep": "-NDN2W_sep"
3865
+ var StatsPills_module_css_default = {
3866
+ "anchor": "bOPqQW_anchor",
3867
+ "label": "bOPqQW_label",
3868
+ "pill": "bOPqQW_pill",
3869
+ "root": "bOPqQW_root",
3870
+ "sep": "bOPqQW_sep"
3805
3871
  };
3806
3872
  //#endregion
3807
- //#region lib/types/client/chat/StatsLine.js
3873
+ //#region lib/types/client/chat/StatsPills.js
3808
3874
  /**
3809
3875
  * Fold assistant and tool-result nodes into window-scoped display totals —
3810
3876
  * the FALLBACK for assemblies without the `sessionStats` projection.
@@ -3889,75 +3955,172 @@ window.__ModuleLoader__.load({
3889
3955
  function billedInputTokens(usage) {
3890
3956
  return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
3891
3957
  }
3892
- /** Render and measure one non-empty statistics line. */
3893
- const StatsLineContent = (0, react.memo)(function StatsLineContent({ groups, line }) {
3894
- const rootRef = (0, react.useRef)(null);
3895
- const [truncated, setTruncated] = (0, react.useState)(false);
3896
- const measure = (0, react.useCallback)(() => {
3897
- const el = rootRef.current;
3898
- if (el === null) return;
3899
- const next = el.scrollWidth > el.clientWidth;
3900
- setTruncated((current) => current === next ? current : next);
3901
- }, []);
3902
- (0, react.useLayoutEffect)(() => {
3903
- const el = rootRef.current;
3904
- if (el === null || typeof ResizeObserver === "undefined") return;
3905
- const observer = new ResizeObserver(measure);
3906
- observer.observe(el);
3907
- return () => {
3908
- observer.disconnect();
3909
- };
3910
- }, [measure]);
3911
- (0, react.useLayoutEffect)(measure, [line, measure]);
3912
- return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
3913
- label: line,
3914
- side: "top",
3915
- delayMs: 500,
3916
- disabled: !truncated,
3917
- children: (0, react_jsx_runtime.jsx)("div", {
3918
- ref: rootRef,
3919
- className: StatsLine_module_css_default.root,
3920
- children: groups.map((group, i) => (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [i > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
3921
- className: StatsLine_module_css_default.sep,
3922
- "aria-hidden": true,
3923
- children: "|"
3924
- }), " "] }), (0, react_jsx_runtime.jsx)("span", { children: group })] }, group))
3958
+ function exactCount(value, t) {
3959
+ return t("message.turnUsage.count", { count: formatExactTokens(value, t) });
3960
+ }
3961
+ function TimePill({ stats, t, dialog }) {
3962
+ const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
3963
+ const counts = t("stats.counts", {
3964
+ turns: stats.turns,
3965
+ steps: stats.steps
3966
+ });
3967
+ const tps = stats.decodeMs > 0 ? t("message.tokensPerSecond", { tps: formatTokensPerSecond(stats.decodeTokens / (stats.decodeMs / 1e3)) }) : null;
3968
+ const label = (0, react_jsx_runtime.jsxs)("span", {
3969
+ className: StatsPills_module_css_default.label,
3970
+ children: [counts, tps !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
3971
+ className: StatsPills_module_css_default.sep,
3972
+ "aria-hidden": true,
3973
+ children: "·"
3974
+ }), tps] })]
3975
+ });
3976
+ if (stats.llmMs <= 0 && stats.toolMs <= 0 && stats.ttftSteps <= 0 && stats.decodeMs <= 0) return (0, react_jsx_runtime.jsx)("span", {
3977
+ className: StatsPills_module_css_default.anchor,
3978
+ children: (0, react_jsx_runtime.jsxs)("span", {
3979
+ className: StatsPills_module_css_default.pill,
3980
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGaugeOutline16, {}), label]
3925
3981
  })
3926
3982
  });
3927
- });
3928
- const StatsLine = (0, react.memo)(function StatsLine({ useChat, useProjection, t }) {
3983
+ return (0, react_jsx_runtime.jsxs)("span", {
3984
+ ref: rootRef,
3985
+ className: StatsPills_module_css_default.anchor,
3986
+ children: [(0, react_jsx_runtime.jsxs)("button", {
3987
+ type: "button",
3988
+ className: StatsPills_module_css_default.pill,
3989
+ "aria-haspopup": "dialog",
3990
+ "aria-expanded": open,
3991
+ "aria-label": tps === null ? counts : `${counts} · ${tps}`,
3992
+ onClick: () => {
3993
+ setOpen(!open);
3994
+ },
3995
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGaugeOutline16, {}), label]
3996
+ }), open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
3997
+ ref: panelRef,
3998
+ className: stat_dialog_module_css_default.panel,
3999
+ role: "dialog",
4000
+ "aria-label": t("stats.dialog.title"),
4001
+ style: pos ?? MEASURE_STYLE,
4002
+ children: [
4003
+ (0, react_jsx_runtime.jsx)("div", {
4004
+ className: stat_dialog_module_css_default.title,
4005
+ children: (0, react_jsx_runtime.jsxs)("span", {
4006
+ className: stat_dialog_module_css_default.titleLabel,
4007
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGaugeOutline16, {}), t("stats.dialog.title")]
4008
+ })
4009
+ }),
4010
+ (0, react_jsx_runtime.jsx)("div", {
4011
+ className: stat_dialog_module_css_default.titleRule,
4012
+ "aria-hidden": true
4013
+ }),
4014
+ (0, react_jsx_runtime.jsxs)("dl", {
4015
+ className: stat_dialog_module_css_default.details,
4016
+ "data-session-stats-details": true,
4017
+ children: [
4018
+ stats.llmMs > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.llmTime") }), (0, react_jsx_runtime.jsx)("dd", { children: formatDuration(stats.llmMs, t) })] }),
4019
+ stats.toolMs > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.toolTime") }), (0, react_jsx_runtime.jsx)("dd", { children: formatDuration(stats.toolMs, t) })] }),
4020
+ stats.ttftSteps > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.ttft") }), (0, react_jsx_runtime.jsx)("dd", { children: formatDuration(stats.ttftMs / stats.ttftSteps, t) })] }),
4021
+ stats.decodeMs > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.speed") }), (0, react_jsx_runtime.jsx)("dd", { children: t("message.tokensPerSecond", { tps: formatTokensPerSecond(stats.decodeTokens / (stats.decodeMs / 1e3)) }) })] })
4022
+ ]
4023
+ })
4024
+ ]
4025
+ }), document.body)]
4026
+ });
4027
+ }
4028
+ function UsagePill({ usage, t, dialog }) {
4029
+ const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
4030
+ const total = billedInputTokens(usage) + usage.outputTokens;
4031
+ const totalText = t("message.turnUsage.count", { count: formatTokens(total, t) });
4032
+ const cacheHit = cacheHitPercent(usage);
4033
+ const cacheHitText = cacheHit !== null ? t("stats.cacheHit", { percent: cacheHit }) : null;
4034
+ return (0, react_jsx_runtime.jsxs)("span", {
4035
+ ref: rootRef,
4036
+ className: StatsPills_module_css_default.anchor,
4037
+ children: [(0, react_jsx_runtime.jsxs)("button", {
4038
+ type: "button",
4039
+ className: StatsPills_module_css_default.pill,
4040
+ "aria-haspopup": "dialog",
4041
+ "aria-expanded": open,
4042
+ "aria-label": cacheHitText === null ? totalText : `${totalText} · ${cacheHitText}`,
4043
+ onClick: () => {
4044
+ setOpen(!open);
4045
+ },
4046
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDatabaseOutline16, {}), (0, react_jsx_runtime.jsxs)("span", {
4047
+ className: StatsPills_module_css_default.label,
4048
+ children: [totalText, cacheHitText !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
4049
+ className: StatsPills_module_css_default.sep,
4050
+ "aria-hidden": true,
4051
+ children: "·"
4052
+ }), cacheHitText] })]
4053
+ })]
4054
+ }), open && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
4055
+ ref: panelRef,
4056
+ className: stat_dialog_module_css_default.panel,
4057
+ role: "dialog",
4058
+ "aria-label": t("stats.dialog.usageTitle"),
4059
+ style: pos ?? MEASURE_STYLE,
4060
+ children: [
4061
+ (0, react_jsx_runtime.jsxs)("div", {
4062
+ className: stat_dialog_module_css_default.title,
4063
+ children: [(0, react_jsx_runtime.jsxs)("span", {
4064
+ className: stat_dialog_module_css_default.titleLabel,
4065
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDatabaseOutline16, {}), t("stats.dialog.usageTitle")]
4066
+ }), (0, react_jsx_runtime.jsx)("span", {
4067
+ className: stat_dialog_module_css_default.titleValue,
4068
+ children: exactCount(total, t)
4069
+ })]
4070
+ }),
4071
+ (0, react_jsx_runtime.jsx)("div", {
4072
+ className: stat_dialog_module_css_default.titleRule,
4073
+ "aria-hidden": true
4074
+ }),
4075
+ (0, react_jsx_runtime.jsxs)("dl", {
4076
+ className: stat_dialog_module_css_default.details,
4077
+ "data-session-stats-usage": true,
4078
+ children: [
4079
+ cacheHit !== null && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheHit") }), (0, react_jsx_runtime.jsx)("dd", { children: `${cacheHit}%` })] }),
4080
+ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.input") }),
4081
+ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(usage.uncachedInputTokens, t) }),
4082
+ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheRead") }),
4083
+ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(usage.cacheReadTokens, t) }),
4084
+ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheWrite") }),
4085
+ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(usage.cacheWriteTokens, t) }),
4086
+ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.output") }),
4087
+ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(usage.outputTokens, t) })
4088
+ ]
4089
+ })
4090
+ ]
4091
+ }), document.body)]
4092
+ });
4093
+ }
4094
+ const StatsPills = (0, react.memo)(function StatsPills({ useChat, useProjection, t }) {
3929
4095
  const settledNodes = useChat((s) => s.legacy.nodes);
3930
4096
  const usage = useProjection("tokenUsage");
4097
+ const [openPill, setOpenPill] = (0, react.useState)(null);
3931
4098
  const projected = useProjection("sessionStats");
3932
4099
  const stats = (0, react.useMemo)(() => projected ?? deriveStats(settledNodes), [projected, settledNodes]);
3933
- const groups = [];
3934
- if (stats.steps > 0) {
3935
- groups.push(t("stats.counts", {
3936
- turns: stats.turns,
3937
- steps: stats.steps
3938
- }));
3939
- const durations = [];
3940
- if (stats.llmMs > 0) durations.push(t("stats.llm", { duration: formatDuration(stats.llmMs, t) }));
3941
- if (stats.toolMs > 0) durations.push(t("stats.toolCall", { duration: formatDuration(stats.toolMs, t) }));
3942
- if (durations.length > 0) groups.push(durations.join(" · "));
3943
- const speeds = [];
3944
- if (stats.ttftSteps > 0) speeds.push(t("stats.ttftAverage", { duration: formatDuration(stats.ttftMs / stats.ttftSteps, t) }));
3945
- if (stats.decodeMs > 0) speeds.push(t("stats.tokensPerSecond", { throughput: formatTokensPerSecond(stats.decodeTokens / (stats.decodeMs / 1e3)) }));
3946
- if (speeds.length > 0) groups.push(speeds.join(" · "));
3947
- }
3948
- if (usage !== void 0 && (billedInputTokens(usage) > 0 || usage.outputTokens > 0)) {
3949
- const cacheHit = cacheHitPercent(usage);
3950
- if (cacheHit !== null) groups.push(t("stats.cacheHit", { percent: cacheHit }));
3951
- groups.push(t("stats.tokens", {
3952
- input: formatTokens(billedInputTokens(usage), t),
3953
- output: formatTokens(usage.outputTokens, t)
3954
- }));
3955
- }
3956
- const line = groups.join(" | ");
3957
- if (groups.length === 0) return null;
3958
- return (0, react_jsx_runtime.jsx)(StatsLineContent, {
3959
- groups,
3960
- line
4100
+ const hasTokens = usage !== void 0 && (billedInputTokens(usage) > 0 || usage.outputTokens > 0);
4101
+ if (stats.steps === 0 && !hasTokens) return null;
4102
+ return (0, react_jsx_runtime.jsxs)("div", {
4103
+ className: StatsPills_module_css_default.root,
4104
+ "data-composer-stats": true,
4105
+ children: [stats.steps > 0 && (0, react_jsx_runtime.jsx)(TimePill, {
4106
+ stats,
4107
+ t,
4108
+ dialog: {
4109
+ open: openPill === "time",
4110
+ setOpen: (open) => {
4111
+ setOpenPill(open ? "time" : null);
4112
+ }
4113
+ }
4114
+ }), hasTokens && (0, react_jsx_runtime.jsx)(UsagePill, {
4115
+ usage,
4116
+ t,
4117
+ dialog: {
4118
+ open: openPill === "usage",
4119
+ setOpen: (open) => {
4120
+ setOpenPill(open ? "usage" : null);
4121
+ }
4122
+ }
4123
+ })]
3961
4124
  });
3962
4125
  });
3963
4126
  //#endregion
@@ -5314,7 +5477,7 @@ window.__ModuleLoader__.load({
5314
5477
  function sameContribution(left, right) {
5315
5478
  return left !== void 0 && left.anchorSeq === right.anchorSeq && left.partial?.blocks === right.partial?.blocks && left.partial?.turn === right.partial?.turn && left.partial?.step === right.partial?.step && left.running === right.running && sameReferences$1(left.nodes, right.nodes);
5316
5479
  }
5317
- /** Incremental compatibility projection for StatsLine and legacy top-level snapshot fields. */
5480
+ /** Incremental compatibility projection for StatsPills and legacy top-level snapshot fields. */
5318
5481
  var LegacySliceBuilder = class {
5319
5482
  contributions = /* @__PURE__ */ new Map();
5320
5483
  finalizedContributions = /* @__PURE__ */ new Map();
@@ -5521,6 +5684,7 @@ window.__ModuleLoader__.load({
5521
5684
  //#region ../../core/session/src/surface.ts
5522
5685
  /** Runtime counterpart of the message-producing event union. */
5523
5686
  const SURFACE_EVENT_TYPES = new Set([
5687
+ "system/message",
5524
5688
  "user/message",
5525
5689
  "assistant/message",
5526
5690
  "tool/result"
@@ -5945,7 +6109,7 @@ window.__ModuleLoader__.load({
5945
6109
  }
5946
6110
  //#endregion
5947
6111
  //#region lib/types/client/conversation-nodes/request-prompt.js
5948
- /** Place a request's system field at the start of its visible message series. */
6112
+ /** Place a request's system prompt at the start of its visible message series. */
5949
6113
  function requestPromptAnchor(match, previous, isInitial) {
5950
6114
  if (match.location.kind !== "step") return match.event.seq;
5951
6115
  if (previous === void 0 && !isInitial) return match.event.seq;
@@ -5958,7 +6122,41 @@ window.__ModuleLoader__.load({
5958
6122
  return current?.kind === "system-prompt" ? current.anchorSeq : requestPromptAnchor(match, previous, isInitial);
5959
6123
  }
5960
6124
  /**
5961
- * Request-header prompt Definition for the Chat target.
6125
+ * System-prompt surface node Definition for the Chat target. It owns every
6126
+ * `system/message` event on the Chat target so the unknown-surface fallback
6127
+ * never renders the prompt as a transcript row. Each nonempty append owns a
6128
+ * prompt card, even without a loaded request header. Initial cards precede
6129
+ * their step's input; in-history updates stay at their own positions. The
6130
+ * request-prompt Definition owns replacement and later-series cards. Positional
6131
+ * replacements advance the effective prompt without changing historical cards.
6132
+ * @param inspect - Pure surface interpretation supplied by uiConversation.
6133
+ * @returns The Chat system-prompt Definition.
6134
+ */
6135
+ function systemMessageDefinition(inspect) {
6136
+ return {
6137
+ kind: "system-message",
6138
+ target: "chat",
6139
+ match: (event) => event.type === "system/message" || "surfaceOp" in event && event.surfaceOp !== "append" ? {
6140
+ id: String(event.seq),
6141
+ role: "start"
6142
+ } : null,
6143
+ start: (_context, match, reader) => {
6144
+ return inspect(reader.previous("system-message")?.state, match.event);
6145
+ },
6146
+ update: (context) => context.state,
6147
+ buildViewNode: (context) => {
6148
+ const state = context.state?.introduced;
6149
+ if (state === void 0 || state.text === "" || context.start?.event.type !== "system/message" || context.start.event.surfaceOp !== "append") return null;
6150
+ return chatNode(context, "system-prompt", state.update ? state.seq : requestPromptAnchor(context.start, void 0, true), {
6151
+ text: state.text,
6152
+ ...state.update ? { update: true } : {}
6153
+ });
6154
+ }
6155
+ };
6156
+ }
6157
+ /**
6158
+ * Request-header prompt Definition for the Chat target. Resume and explicit
6159
+ * series starts retain a prompt card even when the system text is unchanged.
5962
6160
  * @param inspect - the shared prompt interpretation, supplied by the
5963
6161
  * uiConversation service (a client bundle cannot value-import it).
5964
6162
  * @returns the Chat request-prompt Definition.
@@ -5974,15 +6172,19 @@ window.__ModuleLoader__.load({
5974
6172
  start: (context, match, reader) => {
5975
6173
  if (match.event.type !== "request/header") throw new Error("request-prompt start requires request/header");
5976
6174
  const previous = reader.previous("request-prompt")?.state;
6175
+ const systemContext = reader.previous("system-message");
6176
+ const system = systemContext?.state.effective;
5977
6177
  const location = match.location.kind === "step" ? {
5978
6178
  turn: match.location.turn.turn,
5979
6179
  step: match.location.step.step
5980
6180
  } : {};
5981
- const inspection = inspect(previous?.prompt, match.event);
6181
+ const inspection = inspect(previous?.prompt, match.event, system);
5982
6182
  const change = inspection.change?.kind;
6183
+ const systemEvent = systemContext?.matches[0]?.event;
6184
+ const shownByUpdate = system !== void 0 && systemEvent?.type === "system/message" && systemEvent.surfaceOp === "append" && (system.update || previous === void 0) && system.turn === location.turn && system.step === location.step;
5983
6185
  return {
5984
6186
  anchorSeq: stableRequestPromptAnchor(context, match, previous, match.event.data.reason === "initial"),
5985
- showsPrompt: previous === void 0 || match.event.data.reason === "series" || match.event.data.startsSeries === true || change === "system" || change === "system-and-tools",
6187
+ showsPrompt: !shownByUpdate && (previous === void 0 || match.event.data.reason !== "change" || match.event.data.startsSeries === true || change === "system" || change === "system-and-tools"),
5986
6188
  ...location,
5987
6189
  ...inspection
5988
6190
  };
@@ -5999,11 +6201,12 @@ window.__ModuleLoader__.load({
5999
6201
  };
6000
6202
  }
6001
6203
  /**
6002
- * Register model-request system prompts in the Chat flow.
6204
+ * Register the system-prompt surface node and the model-request prompt card in the Chat flow.
6003
6205
  * @param ctx - Owning UI Conversation context.
6004
6206
  */
6005
6207
  function registerRequestPromptConversationNode(ctx) {
6006
- ctx.uiConversation.events.register(requestPromptDefinition((previous, event) => ctx.uiConversation.inspectRequestPrompt(previous, event)));
6208
+ ctx.uiConversation.events.register(systemMessageDefinition((previous, event) => ctx.uiConversation.inspectSystemPrompt(previous, event)));
6209
+ ctx.uiConversation.events.register(requestPromptDefinition((previous, event, system) => ctx.uiConversation.inspectRequestPrompt(previous, event, system)));
6007
6210
  }
6008
6211
  //#endregion
6009
6212
  //#region lib/types/client/conversation-nodes/retry.js
@@ -6198,13 +6401,13 @@ window.__ModuleLoader__.load({
6198
6401
  }
6199
6402
  function updateDispatch(state, match) {
6200
6403
  const event = match.event;
6201
- if (event.type !== "tool/code-dispatch-start" && event.type !== "tool/code-dispatch") return state;
6404
+ if (event.type !== "tool/ptc-dispatch-start" && event.type !== "tool/ptc-dispatch") return state;
6202
6405
  const data = event.data;
6203
6406
  const parentCallId = String(data.parentCallId);
6204
6407
  const subCallId = String(data.subCallId);
6205
6408
  const siblings = state.children.get(parentCallId) ?? [];
6206
6409
  const index = siblings.findIndex((candidate) => candidate.callId === subCallId);
6207
- if (event.type === "tool/code-dispatch-start") {
6410
+ if (event.type === "tool/ptc-dispatch-start") {
6208
6411
  if (index >= 0 || !acceptsEdge(state, parentCallId, subCallId)) return state;
6209
6412
  const children = new Map(state.children);
6210
6413
  children.set(parentCallId, [...siblings, childCall(match, data)]);
@@ -6290,7 +6493,7 @@ window.__ModuleLoader__.load({
6290
6493
  for (const candidate of context.matches) state = updateDispatch(state, candidate);
6291
6494
  return state;
6292
6495
  }
6293
- /** Root Tool lifecycle and nested Code Dispatch Definition. */
6496
+ /** Root Tool lifecycle and nested PTC dispatch Definition. */
6294
6497
  const toolDefinition = {
6295
6498
  kind: "tool-call",
6296
6499
  target: "chat",
@@ -6303,7 +6506,7 @@ window.__ModuleLoader__.load({
6303
6506
  id: String(event.data.message.source.callId),
6304
6507
  role: "update"
6305
6508
  };
6306
- if (event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") {
6509
+ if (event.type === "tool/ptc-dispatch-start" || event.type === "tool/ptc-dispatch") {
6307
6510
  const rootCallId = event.data.rootCallId;
6308
6511
  return typeof rootCallId === "string" && rootCallId !== "" ? {
6309
6512
  id: rootCallId,
@@ -7102,160 +7305,6 @@ window.__ModuleLoader__.load({
7102
7305
  registerChatConversationView(ctx);
7103
7306
  }
7104
7307
  //#endregion
7105
- //#region lib/types/client/details/tool-node-reader.js
7106
- function toolNode(node) {
7107
- return node?.kind === "tool-call" ? node : void 0;
7108
- }
7109
- /**
7110
- * Find any root or nested Tool lifecycle through the internal Node store.
7111
- * @param snapshot - current Conversation snapshot.
7112
- * @param callId - root or nested call identity.
7113
- * @returns current Tool lifecycle when materialized in the loaded window.
7114
- */
7115
- function findToolCall(snapshot, callId) {
7116
- const visit = (block) => {
7117
- if (block.callId === callId) return block;
7118
- for (const child of block.subCalls) {
7119
- const found = visit(child);
7120
- if (found !== void 0) return found;
7121
- }
7122
- };
7123
- for (const node of snapshot.nodes.values()) {
7124
- const root = toolNode(node)?.data.root;
7125
- if (root === void 0) continue;
7126
- const found = visit(root);
7127
- if (found !== void 0) return found;
7128
- }
7129
- }
7130
- //#endregion
7131
- //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/details/DetailsPanel.module.css.mjs
7132
- const css$1 = "._2ctAZa_root{border-left:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex-direction:column;min-width:0;height:100%;display:flex}._2ctAZa_header{border-bottom:.5px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:8px;padding:14px 12px 12px;display:flex}._2ctAZa_title{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}._2ctAZa_close{corner-shape:round;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;display:grid}._2ctAZa_close:hover{background:var(--dsw-alias-interactive-bg-hover)}._2ctAZa_body{flex:1;min-height:0;padding:12px 16px;overflow-y:auto}._2ctAZa_empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}._2ctAZa_section{margin-bottom:16px}._2ctAZa_sectionLabel{color:var(--dsw-alias-label-secondary);margin-bottom:6px;font-size:12px;font-weight:500;line-height:18px}._2ctAZa_code{background:var(--dsw-alias-markdown-code-block);font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:12px;margin:0;padding:16px;font-size:13px;line-height:22px}._2ctAZa_code[data-error]{color:var(--dsw-alias-state-error-primary)}";
7133
- const tagId$1 = "@deepseek-ai/dsh-client-ui-chat/DetailsPanel.module.css";
7134
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
7135
- const tag = document.createElement("style");
7136
- tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-chat";
7137
- tag.dataset.pluginCss = tagId$1;
7138
- tag.textContent = css$1;
7139
- document.head.appendChild(tag);
7140
- }
7141
- var DetailsPanel_module_css_default = {
7142
- "body": "_2ctAZa_body",
7143
- "close": "_2ctAZa_close",
7144
- "code": "_2ctAZa_code",
7145
- "empty": "_2ctAZa_empty",
7146
- "header": "_2ctAZa_header",
7147
- "root": "_2ctAZa_root",
7148
- "section": "_2ctAZa_section",
7149
- "sectionLabel": "_2ctAZa_sectionLabel",
7150
- "title": "_2ctAZa_title"
7151
- };
7152
- //#endregion
7153
- //#region lib/types/client/details/DetailsPanel.js
7154
- function settledMaterial(node, callId) {
7155
- return {
7156
- name: node.call?.name ?? callId,
7157
- argsRaw: node.call?.argsRaw ?? null,
7158
- block: node
7159
- };
7160
- }
7161
- function runningMaterial(call) {
7162
- return {
7163
- name: call.name,
7164
- argsRaw: call.argsRaw,
7165
- block: call
7166
- };
7167
- }
7168
- function materialFor(s, callId) {
7169
- const found = findToolCall(s, callId);
7170
- if (found === void 0) return null;
7171
- return "kind" in found ? settledMaterial(found, callId) : runningMaterial(found);
7172
- }
7173
- function pretty(raw) {
7174
- try {
7175
- return JSON.stringify(JSON.parse(raw), null, 2);
7176
- } catch {
7177
- return raw;
7178
- }
7179
- }
7180
- /** Flatten a settled result for the no-ui-tool fallback. */
7181
- function rawResultText(block) {
7182
- if (!("kind" in block)) return "";
7183
- const parts = block.content.map((item) => item.type === "text" ? item.text : JSON.stringify(item, null, 2));
7184
- if (parts.length === 0 && block.error !== void 0) parts.push(`${block.error.name}: ${block.error.code}`);
7185
- return parts.join("\n");
7186
- }
7187
- function DetailsPanel({ useChat, useSessions, sessionId, useStore, renderSlot, closeDetails, t }) {
7188
- const selection = useStore((s) => s.selection);
7189
- const sessionCwd = useSessions((list) => list.byId[sessionId]?.cwd);
7190
- const callId = selection?.callId;
7191
- const material = useChat((s) => callId === void 0 ? null : materialFor(s, callId), (a, b) => (0, _deepseek_ai_dsh_client_store.shallowEqual)(a, b));
7192
- return (0, react_jsx_runtime.jsxs)("div", {
7193
- className: DetailsPanel_module_css_default.root,
7194
- children: [(0, react_jsx_runtime.jsxs)("div", {
7195
- className: DetailsPanel_module_css_default.header,
7196
- children: [(0, react_jsx_runtime.jsx)("div", {
7197
- className: DetailsPanel_module_css_default.title,
7198
- children: selection === null ? t("details.title") : material?.name ?? selection.toolName ?? t("details.title")
7199
- }), (0, react_jsx_runtime.jsx)("button", {
7200
- type: "button",
7201
- className: DetailsPanel_module_css_default.close,
7202
- "aria-label": t("details.close"),
7203
- onClick: () => {
7204
- closeDetails();
7205
- },
7206
- children: (0, react_jsx_runtime.jsx)("svg", {
7207
- viewBox: "0 0 16 16",
7208
- width: "14",
7209
- height: "14",
7210
- "aria-hidden": true,
7211
- children: (0, react_jsx_runtime.jsx)("path", {
7212
- d: "M4 4l8 8M12 4l-8 8",
7213
- stroke: "currentColor",
7214
- strokeWidth: "1.5",
7215
- strokeLinecap: "round"
7216
- })
7217
- })
7218
- })]
7219
- }), (0, react_jsx_runtime.jsx)("div", {
7220
- className: DetailsPanel_module_css_default.body,
7221
- children: selection === null || callId === void 0 ? (0, react_jsx_runtime.jsx)("div", {
7222
- className: DetailsPanel_module_css_default.empty,
7223
- children: t("details.empty")
7224
- }) : material === null ? (0, react_jsx_runtime.jsx)("div", {
7225
- className: DetailsPanel_module_css_default.empty,
7226
- children: t("details.notInWindow")
7227
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [material.argsRaw !== null && (0, react_jsx_runtime.jsxs)("section", {
7228
- className: DetailsPanel_module_css_default.section,
7229
- children: [(0, react_jsx_runtime.jsx)("div", {
7230
- className: DetailsPanel_module_css_default.sectionLabel,
7231
- children: t("details.input")
7232
- }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.CodeBlock, {
7233
- code: pretty(material.argsRaw),
7234
- lang: "json",
7235
- copyLabel: t("copy"),
7236
- copiedLabel: t("copied")
7237
- })]
7238
- }), (0, react_jsx_runtime.jsxs)("section", {
7239
- className: DetailsPanel_module_css_default.section,
7240
- children: [(0, react_jsx_runtime.jsx)("div", {
7241
- className: DetailsPanel_module_css_default.sectionLabel,
7242
- children: t("details.output")
7243
- }), (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderSlot("conversation.details.tool", {
7244
- block: material.block,
7245
- cwd: sessionCwd
7246
- }, { fallback: "kind" in material.block ? (0, react_jsx_runtime.jsx)("pre", {
7247
- className: DetailsPanel_module_css_default.code,
7248
- "data-error": material.block.isError || void 0,
7249
- children: rawResultText(material.block)
7250
- }) : (0, react_jsx_runtime.jsx)("div", {
7251
- className: DetailsPanel_module_css_default.empty,
7252
- children: t("details.running")
7253
- }) }) }, callId)]
7254
- })] })
7255
- })]
7256
- });
7257
- }
7258
- //#endregion
7259
7308
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/settings/TranscriptViewRow.module.css.mjs
7260
7309
  const css = ".lats3W_row{border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.lats3W_rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.lats3W_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.lats3W_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.lats3W_selector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lats3W_selector:hover{background:var(--dsw-alias-interactive-bg-hover)}.lats3W_chevron{flex:none}";
7261
7310
  const tagId = "@deepseek-ai/dsh-client-ui-chat/TranscriptViewRow.module.css";
@@ -8201,11 +8250,11 @@ window.__ModuleLoader__.load({
8201
8250
  "sessions",
8202
8251
  "uiSession",
8203
8252
  "uiConversation",
8204
- "layout",
8205
8253
  "locale",
8206
8254
  "settingsScope",
8207
8255
  "remote",
8208
- "remote.session"
8256
+ "remote.session",
8257
+ "sidebarRight"
8209
8258
  ];
8210
8259
  /**
8211
8260
  * Mount all Chat-owned contributions.
@@ -8270,7 +8319,7 @@ window.__ModuleLoader__.load({
8270
8319
  }
8271
8320
  },
8272
8321
  store: chatStore,
8273
- inject: (sessionId, actions) => {
8322
+ inject: (sessionId) => {
8274
8323
  const binding = ctx.sessions.binding(sessionId);
8275
8324
  if (binding === void 0) throw new Error(`ui-chat: unknown session "${sessionId}"`);
8276
8325
  const session = binding.session;
@@ -8281,15 +8330,13 @@ window.__ModuleLoader__.load({
8281
8330
  chatNode: (key) => chat.getSnapshot().nodes.source(key),
8282
8331
  chatNodeProcess: (key) => chat.getSnapshot().nodes.processSource(key)
8283
8332
  },
8284
- openDetails: (target) => {
8285
- actions.select(target);
8286
- ctx.layout.openDetails();
8287
- },
8288
8333
  fileMentions: (owner) => ctx.get("chatFileMentions")?.forClosing(owner),
8289
- openFile: async (path) => {
8334
+ openFile: async (path, options) => {
8290
8335
  const cwd = ctx.sessions.list.getSnapshot().byId[sessionId]?.cwd;
8291
- const result = await ctx.remote.session.openWorkspacePath({ path: resolveWorkspacePath(cwd, path) });
8292
- if (!result.ok) throw new Error(`path open failed: ${result.error.message}`);
8336
+ const url = fileAddressFor(sessionId, cwd, path);
8337
+ if (options?.line === void 0) ctx.sidebarRight.openResource(url);
8338
+ else ctx.sidebarRight.openResource(url, { params: { line: options.line } });
8339
+ await Promise.resolve();
8293
8340
  },
8294
8341
  loadOlder: () => {
8295
8342
  session.loadOlder();
@@ -8321,20 +8368,8 @@ window.__ModuleLoader__.load({
8321
8368
  id: "stats",
8322
8369
  order: 0,
8323
8370
  locale: NS
8324
- }, StatsLine));
8371
+ }, StatsPills));
8325
8372
  ctx.slots.inject("conversation.approval.detail", () => ctx.slots.register({ name: "conversation.approval.detail" }, ApprovalCommand));
8326
- ctx.slots.inject("details", () => ctx.slots.register({
8327
- name: "details",
8328
- locale: NS,
8329
- children: { "conversation.details.tool": {
8330
- kind: "single",
8331
- scope: "session"
8332
- } },
8333
- store: chatStore,
8334
- inject: () => ({ closeDetails: () => {
8335
- ctx.layout.closeDetails();
8336
- } })
8337
- }, DetailsPanel));
8338
8373
  }
8339
8374
  //#endregion
8340
8375
  exports.EMPTY_CHAT_SNAPSHOT = EMPTY_CHAT_SNAPSHOT;