@deepseek-ai/dsh-client-ui-chat 0.1.2-alpha.5 → 0.1.3-alpha.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.
package/lib/client.js CHANGED
@@ -27,7 +27,8 @@ window.__ModuleLoader__.load({
27
27
  function resolveWorkspacePath(cwd, path) {
28
28
  if (path.startsWith("/") || isWindowsStylePath(path)) return path;
29
29
  if (cwd === void 0 || cwd === "") return path;
30
- return `${cwd.replace(/[/\\]+$/, "")}/${path.replace(/^[/\\]+/, "")}`;
30
+ const separator = isWindowsStylePath(cwd) && cwd.includes("\\") ? "\\" : "/";
31
+ return `${cwd.replace(/[/\\]+$/, "")}${separator}${path.replace(/^[/\\]+/, "")}`;
31
32
  }
32
33
  //#endregion
33
34
  //#region lib/types/client/contract/snapshot.js
@@ -115,7 +116,7 @@ window.__ModuleLoader__.load({
115
116
  }
116
117
  //#endregion
117
118
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-chat/src/client/chat/MessageItem.module.css.mjs
118
- const css$15 = ".Sixlwa_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.Sixlwa_userStack{min-width:0;max-width:min(calc(var(--dsh-chat-content-width,748px) * .702), 82%);flex-direction:column;align-items:flex-end;gap:8px;display:flex}.Sixlwa_bubble{background:var(--dsw-specific-bubble);max-width:100%;font-size:var(--dsh-content-font-size,14px);line-height:calc(22px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:22px;padding:10px 16px}.Sixlwa_referenceSummary{color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px))}.Sixlwa_contextRow,.Sixlwa_compactionRow{padding:2px 0}.Sixlwa_compactionButton{width:100%;height:calc(24px + var(--dsh-content-font-delta,0px));min-width:0;color:inherit;font:inherit;text-align:left;background:0 0;border:none;border-radius:6px;align-items:center;padding:0;display:flex}.Sixlwa_compactionButton:not(:disabled){cursor:pointer}.Sixlwa_compactionButton:not(:disabled):hover{background:var(--dsw-alias-interactive-bg-hover)}.Sixlwa_compactionLeading{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);flex:none;place-items:center;margin-right:6px;display:inline-grid}.Sixlwa_compactionLeading svg{width:calc(14px + var(--dsh-content-font-delta,0px));height:calc(14px + var(--dsh-content-font-delta,0px))}.Sixlwa_compactionContextIcon,.Sixlwa_compactionDisclosureIcon{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.Sixlwa_compactionDisclosureIcon,.Sixlwa_compactionButton:not(:disabled):hover .Sixlwa_compactionContextIcon,.Sixlwa_compactionButton:not(:disabled):focus-visible .Sixlwa_compactionContextIcon{opacity:0}.Sixlwa_compactionButton:not(:disabled):hover .Sixlwa_compactionDisclosureIcon,.Sixlwa_compactionButton:not(:disabled):focus-visible .Sixlwa_compactionDisclosureIcon{opacity:1}.Sixlwa_compactionTitle{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-primary-dimmed);flex:none}.Sixlwa_compactionSep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.Sixlwa_compactionSummary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.Sixlwa_compactionBody{padding:4px 0 4px calc(22px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px))}.Sixlwa_retryRow{color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px))}.Sixlwa_retrySummary{width:fit-content;color:inherit;cursor:pointer;user-select:none;border-radius:3px;align-items:center;gap:7px;padding:2px 0;list-style:none;display:inline-flex}.Sixlwa_retrySummary::-webkit-details-marker{display:none}.Sixlwa_retrySummary:after{content:\"\";opacity:.8;border-bottom:1.5px solid;border-right:1.5px solid;width:6px;height:6px;transition:transform .12s;transform:rotate(-45deg)}.Sixlwa_retrySummary:hover{color:var(--dsw-alias-label-secondary)}.Sixlwa_retrySummary:focus-visible{outline:1.5px solid var(--dsw-alias-button-info-fill);outline-offset:2px}.Sixlwa_retryText{color:inherit}.Sixlwa_retryRow[data-active] .Sixlwa_retryText{background:linear-gradient(90deg, var(--dsw-alias-label-tertiary) 0%, var(--dsw-alias-label-tertiary) 40%, var(--dsw-alias-label-secondary) 50%, var(--dsw-alias-label-tertiary) 60%, var(--dsw-alias-label-tertiary) 100%);color:#0000;background-position:100%;background-size:200% 100%;background-clip:text;animation:1.6s ease-in-out infinite Sixlwa_retry-shimmer}.Sixlwa_retryRow[open] .Sixlwa_retrySummary:after{transform:rotate(45deg)}.Sixlwa_retryDetails{overflow-wrap:anywhere;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px));gap:2px;margin-top:3px;padding-left:14px;display:grid}.Sixlwa_retryDetailLabel{color:var(--dsw-alias-label-secondary)}.Sixlwa_turnErrorRow{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));grid-template-columns:10px minmax(0,1fr) auto;align-items:start;gap:8px;padding:2px 0;display:grid}.Sixlwa_turnErrorDot{margin-top:5px}.Sixlwa_turnErrorCopy{overflow-wrap:anywhere;min-width:0}.Sixlwa_turnErrorTitle{color:var(--dsw-alias-state-error-primary);margin-right:6px;font-weight:600}.Sixlwa_turnErrorMessage{color:var(--dsw-alias-label-secondary)}.Sixlwa_turnErrorCode{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-markdown-code-block-small)}.Sixlwa_maxTokensTitle{color:var(--dsw-alias-state-warn-primary);margin-right:6px;font-weight:600}@keyframes Sixlwa_retry-shimmer{0%{background-position:100%}to{background-position:0}}@media (prefers-reduced-motion:reduce){.Sixlwa_retryRow[data-active] .Sixlwa_retryText{color:inherit;background:0 0;animation:none}}";
119
+ const css$15 = ".Sixlwa_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.Sixlwa_userStack{min-width:0;max-width:min(calc(var(--dsh-chat-content-width,748px) * .702), 82%);flex-direction:column;align-items:flex-end;gap:8px;display:flex}.Sixlwa_bubble{background:var(--dsw-specific-bubble);max-width:100%;font-size:var(--dsh-content-font-size,14px);line-height:calc(22px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:22px;padding:10px 16px}.Sixlwa_referenceSummary{color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px))}.Sixlwa_contextRow,.Sixlwa_compactionRow{padding:2px 0}.Sixlwa_compactionButton{width:100%;height:calc(24px + var(--dsh-content-font-delta,0px));min-width:0;color:inherit;font:inherit;text-align:left;background:0 0;border:none;border-radius:6px;align-items:center;padding:0;display:flex}.Sixlwa_compactionButton:not(:disabled){cursor:pointer}.Sixlwa_compactionButton:not(:disabled):hover{background:var(--dsw-alias-interactive-bg-hover)}.Sixlwa_compactionLeading{width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);flex:none;place-items:center;margin-right:6px;display:inline-grid}.Sixlwa_compactionLeading svg{width:calc(14px + var(--dsh-content-font-delta,0px));height:calc(14px + var(--dsh-content-font-delta,0px))}.Sixlwa_compactionContextIcon,.Sixlwa_compactionDisclosureIcon{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.Sixlwa_compactionDisclosureIcon,.Sixlwa_compactionButton:not(:disabled):hover .Sixlwa_compactionContextIcon,.Sixlwa_compactionButton:not(:disabled):focus-visible .Sixlwa_compactionContextIcon{opacity:0}.Sixlwa_compactionButton:not(:disabled):hover .Sixlwa_compactionDisclosureIcon,.Sixlwa_compactionButton:not(:disabled):focus-visible .Sixlwa_compactionDisclosureIcon{opacity:1}.Sixlwa_compactionTitle{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-primary-dimmed);flex:none}.Sixlwa_compactionSep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.Sixlwa_compactionSummary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.Sixlwa_compactionBody{padding:4px 0 4px calc(22px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px))}.Sixlwa_retryRow{color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px))}.Sixlwa_retrySummary{width:fit-content;color:inherit;cursor:pointer;user-select:none;border-radius:3px;align-items:center;gap:7px;padding:2px 0;list-style:none;display:inline-flex}.Sixlwa_retrySummary::-webkit-details-marker{display:none}.Sixlwa_retrySummary:after{content:\"\";opacity:.8;border-bottom:1.5px solid;border-right:1.5px solid;width:6px;height:6px;transition:transform .12s;transform:rotate(-45deg)}.Sixlwa_retrySummary:hover{color:var(--dsw-alias-label-secondary)}.Sixlwa_retrySummary:focus-visible{outline:1.5px solid var(--dsw-alias-button-info-fill);outline-offset:2px}.Sixlwa_retryText{color:inherit}.Sixlwa_retryRow[data-active] .Sixlwa_retryText{background:linear-gradient(90deg, var(--dsw-alias-label-tertiary) 0%, var(--dsw-alias-label-tertiary) 40%, var(--dsw-alias-label-secondary) 50%, var(--dsw-alias-label-tertiary) 60%, var(--dsw-alias-label-tertiary) 100%);color:#0000;background-position:100%;background-size:200% 100%;background-clip:text;animation:1.6s ease-in-out infinite Sixlwa_retry-shimmer}.Sixlwa_retryRow[open] .Sixlwa_retrySummary:after{transform:rotate(45deg)}.Sixlwa_retryDetails{overflow-wrap:anywhere;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px));gap:2px;margin-top:3px;padding-left:14px;display:grid}.Sixlwa_retryDetailLabel{color:var(--dsw-alias-label-secondary)}.Sixlwa_turnErrorRow{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));grid-template-columns:10px minmax(0,1fr) auto;align-items:start;gap:8px;padding:2px 0;display:grid}.Sixlwa_turnErrorDot{margin-top:5px}.Sixlwa_turnErrorCopy{overflow-wrap:anywhere;min-width:0}.Sixlwa_turnErrorTitle{color:var(--dsw-alias-state-error-primary);margin-right:6px;font-weight:600}.Sixlwa_turnErrorMessage{color:var(--dsw-alias-label-secondary)}.Sixlwa_turnErrorCode{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-markdown-code-block-small)}.Sixlwa_maxTokensTitle{color:var(--dsw-alias-state-warn-primary);margin-right:6px;font-weight:600}@keyframes Sixlwa_retry-shimmer{0%{background-position:100%}to{background-position:0}}@media (prefers-reduced-motion:reduce){.Sixlwa_retryRow[data-active] .Sixlwa_retryText{color:inherit;background:0 0;animation:none}}.Sixlwa_attachmentRow{flex-wrap:wrap;justify-content:flex-end;gap:8px;max-width:100%;display:flex}.Sixlwa_fileCard{border:.5px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-specific-input-major,transparent);box-sizing:border-box;border-radius:16px;flex:0 0 240px;align-items:center;gap:10px;width:240px;min-height:64px;padding:8px 12px;display:inline-flex}.Sixlwa_fileIcon{flex:none;width:24px;height:28px}.Sixlwa_fileContent{flex-direction:column;flex:1;min-width:0;display:flex}.Sixlwa_fileName{white-space:nowrap;text-overflow:ellipsis;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px;overflow:hidden}.Sixlwa_fileMeta{white-space:nowrap;text-overflow:ellipsis;color:var(--dsw-alias-label-tertiary,#00000073);font-size:12px;line-height:15px;overflow:hidden}";
119
120
  const tagId$15 = "@deepseek-ai/dsh-client-ui-chat/MessageItem.module.css";
120
121
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$15) + "]") === null) {
121
122
  const tag = document.createElement("style");
@@ -125,6 +126,7 @@ window.__ModuleLoader__.load({
125
126
  document.head.appendChild(tag);
126
127
  }
127
128
  var MessageItem_module_css_default = {
129
+ "attachmentRow": "Sixlwa_attachmentRow",
128
130
  "bubble": "Sixlwa_bubble",
129
131
  "compactionBody": "Sixlwa_compactionBody",
130
132
  "compactionButton": "Sixlwa_compactionButton",
@@ -136,6 +138,11 @@ window.__ModuleLoader__.load({
136
138
  "compactionSummary": "Sixlwa_compactionSummary",
137
139
  "compactionTitle": "Sixlwa_compactionTitle",
138
140
  "contextRow": "Sixlwa_contextRow",
141
+ "fileCard": "Sixlwa_fileCard",
142
+ "fileContent": "Sixlwa_fileContent",
143
+ "fileIcon": "Sixlwa_fileIcon",
144
+ "fileMeta": "Sixlwa_fileMeta",
145
+ "fileName": "Sixlwa_fileName",
139
146
  "maxTokensTitle": "Sixlwa_maxTokensTitle",
140
147
  "referenceSummary": "Sixlwa_referenceSummary",
141
148
  "retry-shimmer": "Sixlwa_retry-shimmer",
@@ -1054,19 +1061,31 @@ window.__ModuleLoader__.load({
1054
1061
  }
1055
1062
  //#endregion
1056
1063
  //#region lib/types/client/chat/MessageItem.js
1064
+ function extensionOf(name) {
1065
+ const dot = name.lastIndexOf(".");
1066
+ if (dot <= 0 || dot === name.length - 1) return "";
1067
+ return name.slice(dot + 1).toUpperCase().slice(0, 8);
1068
+ }
1057
1069
  function contentParts(content) {
1058
1070
  const texts = [];
1059
- const images = [];
1071
+ const attachments = [];
1060
1072
  const rest = [];
1061
1073
  for (const block of content) {
1062
1074
  const b = block;
1063
1075
  if (b.type === "text" && typeof b.text === "string") texts.push(b.text);
1064
- else if (b.type === "image" && b.attachment !== void 0) images.push({ attachment: b.attachment });
1076
+ else if (b.type === "image" && b.attachment !== void 0) attachments.push({
1077
+ type: "image",
1078
+ image: { attachment: b.attachment }
1079
+ });
1080
+ else if (b.type === "file" && b.attachment !== void 0) attachments.push({
1081
+ type: "file",
1082
+ file: b.attachment
1083
+ });
1065
1084
  else rest.push(block);
1066
1085
  }
1067
1086
  return {
1068
1087
  text: texts.join(""),
1069
- images,
1088
+ attachments,
1070
1089
  rest
1071
1090
  };
1072
1091
  }
@@ -1180,9 +1199,10 @@ window.__ModuleLoader__.load({
1180
1199
  });
1181
1200
  }
1182
1201
  /** Right-aligned bubble shared by user and steering rows. */
1183
- function UserStyleBubble({ content, renderMessageImages, actions, pending = false, echo = false, referenceLabels = [], previewImages, t }) {
1184
- const { text, images: contentImages, rest } = contentParts(content);
1185
- const images = previewImages ?? contentImages;
1202
+ function UserStyleBubble({ content, renderMessageImages, actions, pending = false, echo = false, referenceLabels = [], skillNames = [], previewAttachments, t }) {
1203
+ const { text, attachments: contentAttachments, rest } = contentParts(content);
1204
+ const attachments = previewAttachments ?? contentAttachments;
1205
+ const compactImages = attachments.length > 1;
1186
1206
  const truncated = (total) => t("json.truncated", { total });
1187
1207
  const showBubble = text !== "" || rest.length > 0;
1188
1208
  return (0, react_jsx_runtime.jsxs)("div", {
@@ -1192,13 +1212,31 @@ window.__ModuleLoader__.load({
1192
1212
  children: [(0, react_jsx_runtime.jsxs)("div", {
1193
1213
  className: MessageItem_module_css_default.userStack,
1194
1214
  children: [
1195
- renderMessageImages({
1196
- images,
1197
- align: "end"
1215
+ attachments.length > 0 && (0, react_jsx_runtime.jsx)("div", {
1216
+ className: MessageItem_module_css_default.attachmentRow,
1217
+ "data-message-attachments": true,
1218
+ children: attachments.map((attachment, index) => attachment.type === "image" ? (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderMessageImages({
1219
+ images: [attachment.image],
1220
+ align: "end",
1221
+ compact: compactImages
1222
+ }) }, `image:${index}`) : (0, react_jsx_runtime.jsxs)("span", {
1223
+ className: MessageItem_module_css_default.fileCard,
1224
+ title: attachment.file.name,
1225
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DocumentFileIcon, { className: MessageItem_module_css_default.fileIcon }), (0, react_jsx_runtime.jsxs)("span", {
1226
+ className: MessageItem_module_css_default.fileContent,
1227
+ children: [(0, react_jsx_runtime.jsx)("span", {
1228
+ className: MessageItem_module_css_default.fileName,
1229
+ children: attachment.file.name
1230
+ }), (0, react_jsx_runtime.jsx)("span", {
1231
+ className: MessageItem_module_css_default.fileMeta,
1232
+ children: [extensionOf(attachment.file.name), (0, _deepseek_ai_dsh_client_ui_primitives.fileSizeText)(attachment.file.bytes)].filter(Boolean).join(" ")
1233
+ })]
1234
+ })]
1235
+ }, `file:${index}`))
1198
1236
  }),
1199
1237
  showBubble && (0, react_jsx_runtime.jsxs)("div", {
1200
1238
  className: MessageItem_module_css_default.bubble,
1201
- children: [(0, _deepseek_ai_dsh_client_ui_primitives.projectUserText)(text, referenceLabels), rest.map((block, i) => (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
1239
+ children: [(0, _deepseek_ai_dsh_client_ui_primitives.projectUserText)(text, referenceLabels, skillNames), rest.map((block, i) => (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
1202
1240
  label: t("message.extraBlock"),
1203
1241
  payload: block,
1204
1242
  truncatedLabel: truncated
@@ -1246,12 +1284,18 @@ window.__ModuleLoader__.load({
1246
1284
  type: "text",
1247
1285
  text: submission.text
1248
1286
  }], [submission.text]),
1249
- previewImages: (0, react.useMemo)(() => submission.images.map((image) => ({ preview: {
1250
- url: image.previewUrl,
1251
- ...image.name === void 0 ? {} : { name: image.name },
1252
- ...image.width === void 0 ? {} : { width: image.width },
1253
- ...image.height === void 0 ? {} : { height: image.height }
1254
- } })), [submission.images]),
1287
+ previewAttachments: (0, react.useMemo)(() => submission.attachments.map((attachment) => attachment.type === "image" ? {
1288
+ type: "image",
1289
+ image: { preview: {
1290
+ url: attachment.value.previewUrl,
1291
+ ...attachment.value.name === void 0 ? {} : { name: attachment.value.name },
1292
+ ...attachment.value.width === void 0 ? {} : { width: attachment.value.width },
1293
+ ...attachment.value.height === void 0 ? {} : { height: attachment.value.height }
1294
+ } }
1295
+ } : {
1296
+ type: "file",
1297
+ file: attachment.value
1298
+ }), [submission.attachments]),
1255
1299
  renderMessageImages,
1256
1300
  pending: submission.placement === "steering",
1257
1301
  echo: true,
@@ -1272,6 +1316,7 @@ window.__ModuleLoader__.load({
1272
1316
  content: data.content,
1273
1317
  renderMessageImages,
1274
1318
  ...data.referenceLabels === void 0 ? {} : { referenceLabels: data.referenceLabels },
1319
+ ...data.skillNames === void 0 ? {} : { skillNames: data.skillNames },
1275
1320
  t,
1276
1321
  actions: (text) => (0, react_jsx_runtime.jsx)(MessageIconActions, {
1277
1322
  text,
@@ -1470,7 +1515,7 @@ window.__ModuleLoader__.load({
1470
1515
  return location?.kind === "turn" || location?.kind === "step" ? location.turn.turn : void 0;
1471
1516
  }
1472
1517
  /** Subscribe, apply Turn-process visibility, and dispatch one stable Context key. */
1473
- const ChatNodeSeat = (0, react.memo)(function ChatNodeSeat({ nodeKey, useChatNode, useChatNodeProcess, historyIncomplete, compactTranscript, selectedCallId, cwd, openFile, inspectCall, forkAt, renderMessageImages, fileMentions, useStore, actions, renderSlot, t }) {
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 }) {
1474
1519
  const node = useChatNode(nodeKey);
1475
1520
  const routedNode = node;
1476
1521
  const turn = turnOf(routedNode);
@@ -1509,6 +1554,7 @@ window.__ModuleLoader__.load({
1509
1554
  openFile,
1510
1555
  inspectCall,
1511
1556
  forkAt,
1557
+ loadImage,
1512
1558
  renderMessageImages,
1513
1559
  fileMentions,
1514
1560
  turnProcess
@@ -1519,6 +1565,7 @@ window.__ModuleLoader__.load({
1519
1565
  openFile,
1520
1566
  inspectCall,
1521
1567
  forkAt,
1568
+ loadImage,
1522
1569
  renderMessageImages,
1523
1570
  fileMentions,
1524
1571
  turnProcess
@@ -1860,6 +1907,10 @@ window.__ModuleLoader__.load({
1860
1907
  function scrollerOf(from) {
1861
1908
  return from.closest("[data-conversation-scroll]") ?? from;
1862
1909
  }
1910
+ /** Browser shrink clamps and recorded writes do not transfer scroll ownership. */
1911
+ function readerMovedScroll(top, floor, observedTop) {
1912
+ return Math.abs(top - Math.min(observedTop, floor)) > .5;
1913
+ }
1863
1914
  /** Find an already-rendered row without interpolating a selector. */
1864
1915
  function anchorElement(list, key) {
1865
1916
  for (const row of list.querySelectorAll("[data-chat-anchor-key]:not([hidden])")) if (row.dataset.chatAnchorKey === key) return row;
@@ -2261,7 +2312,7 @@ window.__ModuleLoader__.load({
2261
2312
  if (local === null) return;
2262
2313
  const el = scrollerOf(local);
2263
2314
  const floor = Math.max(0, el.scrollHeight - el.clientHeight);
2264
- const movedByReader = Math.abs(el.scrollTop - Math.min(observedTopRef.current, floor)) > .5;
2315
+ const movedByReader = readerMovedScroll(el.scrollTop, floor, observedTopRef.current);
2265
2316
  const isAtBottom = movedByReader ? floor - el.scrollTop <= 25 : atBottomRef.current;
2266
2317
  if (!movedByReader && isAtBottom) {
2267
2318
  toBottom(el);
@@ -2296,6 +2347,13 @@ window.__ModuleLoader__.load({
2296
2347
  };
2297
2348
  const onScroll = () => {
2298
2349
  scrollSamplePendingRef.current = true;
2350
+ if (atBottomRef.current) {
2351
+ const floor = Math.max(0, el.scrollHeight - el.clientHeight);
2352
+ if (!readerMovedScroll(el.scrollTop, floor, observedTopRef.current)) {
2353
+ sample();
2354
+ return;
2355
+ }
2356
+ }
2299
2357
  sampleTimer ??= window.setTimeout(sample, SCROLL_SAMPLE_INTERVAL_MS);
2300
2358
  };
2301
2359
  el.addEventListener("scroll", onScroll, { passive: true });
@@ -2468,6 +2526,7 @@ window.__ModuleLoader__.load({
2468
2526
  openFile: requestOpenFile,
2469
2527
  inspectCall,
2470
2528
  forkAt,
2529
+ loadImage,
2471
2530
  renderMessageImages,
2472
2531
  fileMentions,
2473
2532
  renderSlot,
@@ -3902,47 +3961,6 @@ window.__ModuleLoader__.load({
3902
3961
  });
3903
3962
  });
3904
3963
  //#endregion
3905
- //#region ../../core/session/src/surface.ts
3906
- /** Runtime counterpart of the message-producing event union. */
3907
- const SURFACE_EVENT_TYPES = new Set([
3908
- "user/message",
3909
- "assistant/message",
3910
- "tool/result"
3911
- ]);
3912
- /**
3913
- * Narrow an event to a surface-eligible event carrying its required marker.
3914
- * @param event - event to test.
3915
- * @returns true when both the type and marker identify a surface event.
3916
- */
3917
- function isSurfaceEvent(event) {
3918
- if (!SURFACE_EVENT_TYPES.has(event.type)) return false;
3919
- return event.surfaceOp !== void 0;
3920
- }
3921
- /**
3922
- * Narrow an event to an append-origin surface event: one that entered the
3923
- * surface at its own log position and was never itself a replacement copy.
3924
- *
3925
- * The model-visible surface deliberately shadows replaced ranges, so it is the
3926
- * wrong source for a human transcript — a landed replacement would erase
3927
- * conversation the user already saw. Append-origin events are that transcript's
3928
- * durable source material; replacement copies stay model-only.
3929
- * @param event - event to test.
3930
- * @returns true when the event appended to the surface tail.
3931
- */
3932
- function isAppendSurfaceEvent(event) {
3933
- return isSurfaceEvent(event) && event.surfaceOp === "append";
3934
- }
3935
- /**
3936
- * Narrow an event to a surface replacement: a node that shadowed an existing
3937
- * surface range instead of appending to the tail. The counterpart of
3938
- * {@link isAppendSurfaceEvent} over the two {@link SurfaceOp} variants.
3939
- * @param event - event to test.
3940
- * @returns true when the event replaced a surface range.
3941
- */
3942
- function isReplacementSurfaceEvent(event) {
3943
- return isSurfaceEvent(event) && event.surfaceOp !== "append";
3944
- }
3945
- //#endregion
3946
3964
  //#region lib/types/client/conversation-nodes/common.js
3947
3965
  /**
3948
3966
  * Relative positions in one durable event's seq neighborhood: interrupted
@@ -4075,6 +4093,16 @@ window.__ModuleLoader__.load({
4075
4093
  return collect(record, "references", "label");
4076
4094
  }
4077
4095
  /**
4096
+ * Read the skill name a durable skill-invocation injection loaded.
4097
+ * @param source - Logged `user/message` source.
4098
+ * @returns The skill name, or null for every other source.
4099
+ */
4100
+ function skillInvocationName(source) {
4101
+ const record = asRecord(source);
4102
+ if (record === null || readString(record, "kind") !== "skill-invocation") return null;
4103
+ return readString(record, "name");
4104
+ }
4105
+ /**
4078
4106
  * Classify finalized Assistant content for Chat rendering.
4079
4107
  * @param content - Core content blocks.
4080
4108
  * @returns Chat blocks in source order.
@@ -4173,9 +4201,6 @@ window.__ModuleLoader__.load({
4173
4201
  }
4174
4202
  //#endregion
4175
4203
  //#region lib/types/client/conversation-nodes/assistant.js
4176
- function isChunkRunEvent$1(event) {
4177
- return event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks";
4178
- }
4179
4204
  function initialState(turn, step) {
4180
4205
  return {
4181
4206
  turn,
@@ -4219,9 +4244,7 @@ window.__ModuleLoader__.load({
4219
4244
  hidden: true
4220
4245
  };
4221
4246
  }
4222
- function updateChunk(state, match) {
4223
- if (match.event.type !== "assistant/chunk") return state;
4224
- const chunk = match.event.data.chunk;
4247
+ function updateChunk(state, chunk, seq, time) {
4225
4248
  const blocks = [...state.blocks];
4226
4249
  let changedIndex = -1;
4227
4250
  let previousVisible = false;
@@ -4288,78 +4311,21 @@ window.__ModuleLoader__.load({
4288
4311
  visibleBlocks,
4289
4312
  hidden: visibleBlocks > 0 ? false : state.hidden,
4290
4313
  ...visibleBlocks > 0 && state.firstVisibleSeq === void 0 ? {
4291
- firstVisibleSeq: match.event.seq,
4292
- firstVisibleTime: match.event.time
4314
+ firstVisibleSeq: seq,
4315
+ firstVisibleTime: time
4293
4316
  } : {},
4294
- ...firstToken && state.firstTokenTime === void 0 ? { firstTokenTime: match.event.time } : {}
4317
+ ...firstToken && state.firstTokenTime === void 0 ? { firstTokenTime: time } : {}
4295
4318
  };
4296
4319
  }
4297
- function chunkRunBoundaries(event, needsToken, needsVisible, visibleFromStart) {
4298
- const fragments = event.type === "chunkrow/tool-call-chunks" ? event.data.args : event.data.texts;
4299
- const nameStartsToken = event.type === "chunkrow/tool-call-chunks" && Object.hasOwn(event.data, "name");
4300
- let firstTokenTime;
4301
- let firstVisible;
4302
- let time = event.time;
4303
- for (let index = 0; index < fragments.length; index++) {
4304
- const fragment = fragments[index];
4305
- if (needsToken && firstTokenTime === void 0 && (nameStartsToken || fragment !== "")) firstTokenTime = time;
4306
- if (needsVisible && firstVisible === void 0 && (visibleFromStart || event.type !== "chunkrow/tool-call-chunks" && fragment.trim() !== "")) firstVisible = {
4307
- seq: event.seq + index,
4308
- time
4309
- };
4310
- if ((!needsToken || firstTokenTime !== void 0) && (!needsVisible || firstVisible !== void 0)) break;
4311
- time += event.data.dt[index] ?? 0;
4312
- }
4313
- return {
4314
- firstTokenTime,
4315
- firstVisible
4316
- };
4317
- }
4318
- function updateChunkRun(state, event) {
4319
- const blocks = [...state.blocks];
4320
- const previous = blocks[event.data.index];
4321
- const previousVisible = blockIsVisible(previous);
4322
- let visibleFromStart = state.visibleBlocks - Number(previousVisible) > 0;
4323
- if (event.type === "chunkrow/text-chunks") {
4324
- const text = previous?.kind === "text" ? previous.text : "";
4325
- visibleFromStart ||= text.trim() !== "";
4326
- blocks[event.data.index] = {
4327
- kind: "text",
4328
- text: text + event.data.texts.join("")
4329
- };
4330
- } else if (event.type === "chunkrow/reasoning-chunks") {
4331
- const text = previous?.kind === "reasoning" ? previous.text : "";
4332
- visibleFromStart ||= text.trim() !== "";
4333
- blocks[event.data.index] = {
4334
- kind: "reasoning",
4335
- text: text + event.data.texts.join("")
4336
- };
4337
- } else {
4338
- const base = previous?.kind === "tool-call" ? previous : {
4339
- kind: "tool-call",
4340
- callId: "",
4341
- name: "",
4342
- argsRaw: ""
4343
- };
4344
- blocks[event.data.index] = {
4345
- kind: "tool-call",
4346
- callId: base.callId || String(event.data.id),
4347
- name: Object.hasOwn(event.data, "name") ? event.data.name : base.name,
4348
- argsRaw: base.argsRaw + event.data.args.join("")
4349
- };
4350
- }
4351
- const boundaries = chunkRunBoundaries(event, state.firstTokenTime === void 0, state.firstVisibleSeq === void 0, visibleFromStart);
4352
- const visibleBlocks = state.visibleBlocks - Number(previousVisible) + Number(blockIsVisible(blocks[event.data.index]));
4320
+ function settleMessage(state, match, event) {
4321
+ const blocks = toAssistantBlocks(event.data.message.content);
4353
4322
  return {
4354
4323
  ...state,
4355
4324
  blocks,
4356
- visibleBlocks,
4357
- hidden: visibleBlocks > 0 ? false : state.hidden,
4358
- ...boundaries.firstVisible === void 0 ? {} : {
4359
- firstVisibleSeq: boundaries.firstVisible.seq,
4360
- firstVisibleTime: boundaries.firstVisible.time
4361
- },
4362
- ...boundaries.firstTokenTime === void 0 ? {} : { firstTokenTime: boundaries.firstTokenTime }
4325
+ visibleBlocks: countVisibleBlocks(blocks),
4326
+ hidden: false,
4327
+ final: match,
4328
+ usage: event.data.usage
4363
4329
  };
4364
4330
  }
4365
4331
  function closedBoundary(location) {
@@ -4405,27 +4371,14 @@ window.__ModuleLoader__.load({
4405
4371
  function fallbackState$5(context) {
4406
4372
  let state;
4407
4373
  for (const match of context.matches) {
4408
- if (isChunkRunEvent$1(match.event)) {
4374
+ if (match.event.type === "assistant/live-chunk") {
4409
4375
  state ??= initialState(match.event.data.turn, match.event.data.step);
4410
- state = updateChunkRun(state, match.event);
4411
- continue;
4412
- }
4413
- if (match.event.type === "assistant/chunk") {
4414
- state ??= initialState(match.event.data.turn, match.event.data.step);
4415
- state = updateChunk(state, match);
4376
+ state = updateChunk(state, match.event.data.chunk, match.event.seq, match.event.time);
4416
4377
  continue;
4417
4378
  }
4418
4379
  if (match.event.type === "assistant/message") {
4419
4380
  state ??= initialState(match.event.data.turn, match.event.data.step);
4420
- const blocks = toAssistantBlocks(match.event.data.message.content);
4421
- state = {
4422
- ...state,
4423
- blocks,
4424
- visibleBlocks: countVisibleBlocks(blocks),
4425
- hidden: false,
4426
- final: match,
4427
- usage: match.event.data.usage
4428
- };
4381
+ state = settleMessage(state, match, match.event);
4429
4382
  continue;
4430
4383
  }
4431
4384
  if (match.event.type === "llm/retry" && state !== void 0) state = resetForRetry(state);
@@ -4469,11 +4422,7 @@ window.__ModuleLoader__.load({
4469
4422
  id: `${event.data.turn}:${event.data.step}`,
4470
4423
  role: "start"
4471
4424
  };
4472
- if (event.type === "assistant/chunk" || event.type === "assistant/message" && isAppendSurfaceEvent(event)) return {
4473
- id: `${event.data.turn}:${event.data.step}`,
4474
- role: "update"
4475
- };
4476
- if (isChunkRunEvent$1(event)) return {
4425
+ if (event.type === "assistant/live-chunk" || event.type === "assistant/message" && event.surfaceOp === "append") return {
4477
4426
  id: `${event.data.turn}:${event.data.step}`,
4478
4427
  role: "update"
4479
4428
  };
@@ -4488,26 +4437,14 @@ window.__ModuleLoader__.load({
4488
4437
  return initialState(match.event.data.turn, match.event.data.step);
4489
4438
  },
4490
4439
  update: (context, match) => {
4491
- if (isChunkRunEvent$1(match.event)) return updateChunkRun(context.state, match.event);
4492
- if (match.event.type === "assistant/chunk") return updateChunk(context.state, match);
4493
- if (match.event.type === "assistant/message") {
4494
- const blocks = toAssistantBlocks(match.event.data.message.content);
4495
- return {
4496
- ...context.state,
4497
- blocks,
4498
- visibleBlocks: countVisibleBlocks(blocks),
4499
- hidden: false,
4500
- final: match,
4501
- usage: match.event.data.usage
4502
- };
4503
- }
4440
+ if (match.event.type === "assistant/live-chunk") return updateChunk(context.state, match.event.data.chunk, match.event.seq, match.event.time);
4441
+ if (match.event.type === "assistant/message") return settleMessage(context.state, match, match.event);
4504
4442
  if (match.event.type === "llm/retry") return resetForRetry(context.state);
4505
4443
  return context.state;
4506
4444
  },
4507
4445
  publication: (match) => {
4508
4446
  if (match.event.type === "step/start") return "none";
4509
- if (isChunkRunEvent$1(match.event)) return "animation-frame";
4510
- if (match.event.type !== "assistant/chunk") return "immediate";
4447
+ if (match.event.type !== "assistant/live-chunk") return "immediate";
4511
4448
  const type = match.event.data.chunk.type;
4512
4449
  return type === "usage" || type === "finish" ? "none" : "animation-frame";
4513
4450
  },
@@ -5107,6 +5044,194 @@ window.__ModuleLoader__.load({
5107
5044
  return [...byKey.values()];
5108
5045
  }
5109
5046
  };
5047
+ function withSkillNames(node, names) {
5048
+ const candidate = node;
5049
+ if (candidate.kind !== "user" && candidate.kind !== "steering") return node;
5050
+ const current = candidate.data.skillNames ?? EMPTY_KEYS;
5051
+ const hasNames = Object.hasOwn(candidate.data, "skillNames");
5052
+ if (sameReferences$1(current, names) && hasNames === names.length > 0) return node;
5053
+ const data = { ...candidate.data };
5054
+ if (names.length === 0) delete data.skillNames;
5055
+ else data.skillNames = names;
5056
+ return {
5057
+ ...candidate,
5058
+ data
5059
+ };
5060
+ }
5061
+ /**
5062
+ * Classify one Node for batching: a direct message, a `skill-invocation`
5063
+ * context, or a boundary of any other kind. A context that injects no skill
5064
+ * (workspace rules, the catalog, a recall) is transparent and yields null.
5065
+ */
5066
+ function slashEntryOf(node) {
5067
+ const candidate = node;
5068
+ if (candidate.kind === "user" || candidate.kind === "steering") return {
5069
+ key: node.key,
5070
+ seq: node.anchorSeq,
5071
+ kind: "message",
5072
+ name: null
5073
+ };
5074
+ if (candidate.kind === "context") {
5075
+ const name = skillInvocationName(candidate.data.source);
5076
+ return name === null ? null : {
5077
+ key: node.key,
5078
+ seq: node.anchorSeq,
5079
+ kind: "skill",
5080
+ name
5081
+ };
5082
+ }
5083
+ return {
5084
+ key: node.key,
5085
+ seq: node.anchorSeq,
5086
+ kind: "boundary",
5087
+ name: null
5088
+ };
5089
+ }
5090
+ function sameSlashEntry(left, right) {
5091
+ return left.seq === right.seq && left.kind === right.kind && left.name === right.name;
5092
+ }
5093
+ /**
5094
+ * Attaches each direct message's step-loaded skill names to its Node.
5095
+ *
5096
+ * A step's `skill-invocation` injections follow the direct messages the host
5097
+ * scanned for `/name` gestures and precede the step's first Node of any other
5098
+ * kind, so every non-message, non-context Node closes a batch. Every ended
5099
+ * Turn publishes its `turn-tail` Node on `turn/end` whatever the reason, so a
5100
+ * batch never spans Turns, and `step/start` precedes the direct message in
5101
+ * the log, so no boundary separates a message from its injections. Names
5102
+ * attach to every direct message of the batch: the bubble decorates only the
5103
+ * tokens its own text carries.
5104
+ *
5105
+ * The index holds only messages, skill injections, and boundaries, ordered by
5106
+ * `anchorSeq`. An apply re-reads just the batches around the Nodes whose
5107
+ * classification changed and never scans the store, so an assistant
5108
+ * streaming frame costs nothing here (the append hot path never scans the
5109
+ * Chat Nodes).
5110
+ */
5111
+ var SkillNameProjector = class {
5112
+ entries = /* @__PURE__ */ new Map();
5113
+ /** Every indexed entry in `anchorSeq` order. */
5114
+ sorted = [];
5115
+ /**
5116
+ * Rebuild the index from a whole Node set and attach names to its messages.
5117
+ * @param nodes - every materialized Chat Node, in any order.
5118
+ * @returns the same Nodes, direct messages carrying their batch's names.
5119
+ */
5120
+ replace(nodes) {
5121
+ this.entries.clear();
5122
+ this.sorted = [];
5123
+ for (const node of nodes) {
5124
+ const entry = slashEntryOf(node);
5125
+ if (entry === null) continue;
5126
+ this.entries.set(entry.key, entry);
5127
+ this.sorted.push(entry);
5128
+ }
5129
+ this.sorted.sort((left, right) => left.seq - right.seq);
5130
+ const names = /* @__PURE__ */ new Map();
5131
+ for (let index = 0; index < this.sorted.length; index++) {
5132
+ if (this.sorted[index]?.kind === "boundary") continue;
5133
+ const end = this.runEnd(index);
5134
+ this.assignRun(index, end, names);
5135
+ index = end;
5136
+ }
5137
+ return nodes.map((node) => withSkillNames(node, names.get(node.key) ?? EMPTY_KEYS));
5138
+ }
5139
+ /**
5140
+ * Fold one incremental upsert set: re-read only the batches around the
5141
+ * Nodes whose classification changed.
5142
+ * @param upserts - the changed Nodes.
5143
+ * @param store - the resident Nodes, read by key for the messages of an affected batch.
5144
+ * @returns the upserts plus any resident message whose names changed.
5145
+ */
5146
+ apply(upserts, store) {
5147
+ const dirty = [];
5148
+ for (const node of upserts) {
5149
+ const next = slashEntryOf(node);
5150
+ const previous = this.entries.get(node.key);
5151
+ if (previous !== void 0) {
5152
+ if (next !== null && sameSlashEntry(previous, next)) {
5153
+ if (next.kind === "message") dirty.push(next.seq);
5154
+ continue;
5155
+ }
5156
+ this.remove(previous);
5157
+ dirty.push(previous.seq);
5158
+ }
5159
+ if (next === null) continue;
5160
+ this.insert(next);
5161
+ dirty.push(next.seq);
5162
+ }
5163
+ if (dirty.length === 0) return upserts;
5164
+ const names = /* @__PURE__ */ new Map();
5165
+ for (const seq of dirty) this.collectAround(seq, names);
5166
+ const byKey = new Map(upserts.map((node) => [node.key, node]));
5167
+ for (const [key, list] of names) {
5168
+ const node = byKey.get(key) ?? store.get(key);
5169
+ if (node === void 0) continue;
5170
+ const next = withSkillNames(node, list);
5171
+ if (next !== node || byKey.has(key)) byKey.set(key, next);
5172
+ }
5173
+ return [...byKey.values()];
5174
+ }
5175
+ insert(entry) {
5176
+ this.sorted.splice(this.lowerBound(entry.seq), 0, entry);
5177
+ this.entries.set(entry.key, entry);
5178
+ }
5179
+ remove(entry) {
5180
+ this.sorted.splice(this.sorted.indexOf(entry), 1);
5181
+ this.entries.delete(entry.key);
5182
+ }
5183
+ /** First index whose seq is at least `seq`. */
5184
+ lowerBound(seq) {
5185
+ let low = 0;
5186
+ let high = this.sorted.length;
5187
+ while (low < high) {
5188
+ const middle = low + high >>> 1;
5189
+ if ((this.sorted[middle]?.seq ?? Number.POSITIVE_INFINITY) < seq) low = middle + 1;
5190
+ else high = middle;
5191
+ }
5192
+ return low;
5193
+ }
5194
+ /** Last index of the boundary-free run containing `index`. */
5195
+ runEnd(index) {
5196
+ let end = index;
5197
+ while (end + 1 < this.sorted.length && this.sorted[end + 1]?.kind !== "boundary") end++;
5198
+ return end;
5199
+ }
5200
+ /** First index of the boundary-free run containing `index`. */
5201
+ runStart(index) {
5202
+ let start = index;
5203
+ while (start - 1 >= 0 && this.sorted[start - 1]?.kind !== "boundary") start--;
5204
+ return start;
5205
+ }
5206
+ /** Record the names every message of the run `[start, end]` carries. */
5207
+ assignRun(start, end, names) {
5208
+ const list = [];
5209
+ for (let index = start; index <= end; index++) {
5210
+ const entry = this.sorted[index];
5211
+ if (entry?.kind === "skill" && entry.name !== null && !list.includes(entry.name)) list.push(entry.name);
5212
+ }
5213
+ for (let index = start; index <= end; index++) {
5214
+ const entry = this.sorted[index];
5215
+ if (entry?.kind === "message") names.set(entry.key, list);
5216
+ }
5217
+ }
5218
+ /**
5219
+ * Re-read the run(s) around one changed seq: the run holding a message or
5220
+ * skill entry, or — for a boundary, or a seq that left the index — the runs
5221
+ * on both sides of that position.
5222
+ */
5223
+ collectAround(seq, names) {
5224
+ const at = this.lowerBound(seq);
5225
+ const here = this.sorted[at];
5226
+ if (here !== void 0 && here.seq === seq && here.kind !== "boundary") {
5227
+ this.assignRun(this.runStart(at), this.runEnd(at), names);
5228
+ return;
5229
+ }
5230
+ if (at - 1 >= 0 && this.sorted[at - 1]?.kind !== "boundary") this.assignRun(this.runStart(at - 1), at - 1, names);
5231
+ const right = here !== void 0 && here.seq === seq ? at + 1 : at;
5232
+ if (right < this.sorted.length && this.sorted[right]?.kind !== "boundary") this.assignRun(right, this.runEnd(right), names);
5233
+ }
5234
+ };
5110
5235
  const EMPTY_CONTRIBUTION = {
5111
5236
  anchorSeq: 0,
5112
5237
  nodes: EMPTY_LIST,
@@ -5300,6 +5425,7 @@ window.__ModuleLoader__.load({
5300
5425
  navigation = new MutableTurnNavigationIndex();
5301
5426
  legacy = new LegacySliceBuilder();
5302
5427
  referenceLabels = new ReferenceLabelProjector();
5428
+ skillNames = new SkillNameProjector();
5303
5429
  order = EMPTY_KEYS;
5304
5430
  /** Last published timeline: a Turn boundary can land without a new node. */
5305
5431
  timeline = null;
@@ -5311,7 +5437,7 @@ window.__ModuleLoader__.load({
5311
5437
  });
5312
5438
  }
5313
5439
  replace(input) {
5314
- const nodes = this.referenceLabels.replace(input.nodes);
5440
+ const nodes = this.skillNames.replace(this.referenceLabels.replace(input.nodes));
5315
5441
  this.store.replace(nodes);
5316
5442
  this.order = orderedVisibleChatNodes(nodes).map((node) => node.key);
5317
5443
  this.locations.rebuild(this.order, this.store);
@@ -5323,7 +5449,7 @@ window.__ModuleLoader__.load({
5323
5449
  return snapshot;
5324
5450
  }
5325
5451
  apply(input) {
5326
- const upserts = this.referenceLabels.apply(input.upserts, this.store);
5452
+ const upserts = this.skillNames.apply(this.referenceLabels.apply(input.upserts, this.store), this.store);
5327
5453
  const processTurns = /* @__PURE__ */ new Set();
5328
5454
  let structural = false;
5329
5455
  const contentOnly = [];
@@ -5392,6 +5518,47 @@ window.__ModuleLoader__.load({
5392
5518
  ctx.uiConversation.views.register(chatViewDefinition);
5393
5519
  }
5394
5520
  //#endregion
5521
+ //#region ../../core/session/src/surface.ts
5522
+ /** Runtime counterpart of the message-producing event union. */
5523
+ const SURFACE_EVENT_TYPES = new Set([
5524
+ "user/message",
5525
+ "assistant/message",
5526
+ "tool/result"
5527
+ ]);
5528
+ /**
5529
+ * Narrow an event to a surface-eligible event carrying its required marker.
5530
+ * @param event - event to test.
5531
+ * @returns true when both the type and marker identify a surface event.
5532
+ */
5533
+ function isSurfaceEvent(event) {
5534
+ if (!SURFACE_EVENT_TYPES.has(event.type)) return false;
5535
+ return event.surfaceOp !== void 0;
5536
+ }
5537
+ /**
5538
+ * Narrow an event to an append-origin surface event: one that entered the
5539
+ * surface at its own log position and was never itself a replacement copy.
5540
+ *
5541
+ * The model-visible surface deliberately shadows replaced ranges, so it is the
5542
+ * wrong source for a human transcript — a landed replacement would erase
5543
+ * conversation the user already saw. Append-origin events are that transcript's
5544
+ * durable source material; replacement copies stay model-only.
5545
+ * @param event - event to test.
5546
+ * @returns true when the event appended to the surface tail.
5547
+ */
5548
+ function isAppendSurfaceEvent(event) {
5549
+ return isSurfaceEvent(event) && event.surfaceOp === "append";
5550
+ }
5551
+ /**
5552
+ * Narrow an event to a surface replacement: a node that shadowed an existing
5553
+ * surface range instead of appending to the tail. The counterpart of
5554
+ * {@link isAppendSurfaceEvent} over the two {@link SurfaceOp} variants.
5555
+ * @param event - event to test.
5556
+ * @returns true when the event replaced a surface range.
5557
+ */
5558
+ function isReplacementSurfaceEvent(event) {
5559
+ return isSurfaceEvent(event) && event.surfaceOp !== "append";
5560
+ }
5561
+ //#endregion
5395
5562
  //#region lib/types/client/conversation-nodes/command.js
5396
5563
  const COMPACT_PLUGIN = "compact";
5397
5564
  function commandFromRun(match) {
@@ -5615,13 +5782,10 @@ window.__ModuleLoader__.load({
5615
5782
  const unknownFallbackDefinition = {
5616
5783
  kind: "unknown-surface",
5617
5784
  target: "chat",
5618
- match: (event) => {
5619
- if (event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks") return null;
5620
- return isAppendSurfaceEvent(event) ? {
5621
- id: String(event.seq),
5622
- role: "start"
5623
- } : null;
5624
- },
5785
+ match: (event) => event.type !== "assistant/live-chunk" && isAppendSurfaceEvent(event) ? {
5786
+ id: String(event.seq),
5787
+ role: "start"
5788
+ } : null,
5625
5789
  start: (_context, match) => ({
5626
5790
  kind: "unknown",
5627
5791
  seq: match.event.seq,
@@ -5818,7 +5982,7 @@ window.__ModuleLoader__.load({
5818
5982
  const change = inspection.change?.kind;
5819
5983
  return {
5820
5984
  anchorSeq: stableRequestPromptAnchor(context, match, previous, match.event.data.reason === "initial"),
5821
- showsPrompt: previous === void 0 || match.event.data.reason !== "change" || match.event.data.startsSeries === true || change === "system" || change === "system-and-tools",
5985
+ showsPrompt: previous === void 0 || match.event.data.reason === "series" || match.event.data.startsSeries === true || change === "system" || change === "system-and-tools",
5822
5986
  ...location,
5823
5987
  ...inspection
5824
5988
  };
@@ -5826,8 +5990,11 @@ window.__ModuleLoader__.load({
5826
5990
  update: (context) => context.state,
5827
5991
  buildViewNode: (context) => {
5828
5992
  const state = context.state;
5829
- if (state === void 0 || !state.showsPrompt || state.prompt.system === "") return null;
5830
- return chatNode(context, "system-prompt", state.anchorSeq, { text: state.prompt.system });
5993
+ if (state === void 0) return null;
5994
+ const current = context.current.get("chat");
5995
+ const visible = state.showsPrompt && state.prompt.system !== "";
5996
+ if (!visible && current?.kind !== "system-prompt") return null;
5997
+ return chatNode(context, "system-prompt", state.anchorSeq, { text: state.prompt.system }, { visibility: visible ? "visible" : "hidden" });
5831
5998
  }
5832
5999
  };
5833
6000
  }
@@ -6336,49 +6503,38 @@ window.__ModuleLoader__.load({
6336
6503
  }
6337
6504
  //#endregion
6338
6505
  //#region lib/types/client/conversation-nodes/turn-process.js
6339
- function isChunkRunEvent(event) {
6340
- return event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks";
6341
- }
6342
6506
  function eventTurn(event) {
6343
6507
  const data = event.data;
6344
6508
  return typeof data.turn === "number" ? data.turn : void 0;
6345
6509
  }
6510
+ function visibleChunk(chunk) {
6511
+ if (chunk.type === "text-delta" || chunk.type === "reasoning-delta") return chunk.text.trim() !== "";
6512
+ if (chunk.type === "block-start") return chunk.blockType !== "text" && chunk.blockType !== "reasoning" && chunk.blockType !== "tool-call";
6513
+ if (chunk.type !== "block-end") return false;
6514
+ const block = chunk.block;
6515
+ if (block.type === "tool-call") return false;
6516
+ if (block.type === "text" || block.type === "reasoning") return block.text.trim() !== "";
6517
+ return true;
6518
+ }
6346
6519
  function visibleAssistantEvent(event) {
6347
- if (event.type === "assistant/chunk") {
6348
- const chunk = event.data.chunk;
6349
- if (chunk.type === "text-delta" || chunk.type === "reasoning-delta") return chunk.text.trim() !== "";
6350
- if (chunk.type === "block-start") return chunk.blockType !== "text" && chunk.blockType !== "reasoning" && chunk.blockType !== "tool-call";
6351
- if (chunk.type !== "block-end") return false;
6352
- const block = chunk.block;
6353
- if (block.type === "tool-call") return false;
6354
- if (block.type === "text" || block.type === "reasoning") return block.text.trim() !== "";
6355
- return true;
6356
- }
6357
- return event.type === "assistant/message" && isAppendSurfaceEvent(event) && toAssistantBlocks(event.data.message.content).some((block) => {
6520
+ if (event.type === "assistant/live-chunk") return visibleChunk(event.data.chunk);
6521
+ if (event.type === "assistant/attempt") return false;
6522
+ return event.type === "assistant/message" && event.surfaceOp === "append" && toAssistantBlocks(event.data.message.content).some((block) => {
6358
6523
  if (block.kind === "tool-call") return false;
6359
6524
  if (block.kind === "text" || block.kind === "reasoning") return block.text.trim() !== "";
6360
6525
  return true;
6361
6526
  });
6362
6527
  }
6363
6528
  function processEvidence(event) {
6364
- if (isChunkRunEvent(event)) {
6365
- if (event.type === "chunkrow/tool-call-chunks") return void 0;
6366
- const firstVisible = event.data.texts.findIndex((text) => text.trim() !== "");
6367
- return firstVisible < 0 ? void 0 : {
6368
- kind: "assistant",
6369
- seq: event.seq + firstVisible,
6370
- step: event.data.step
6371
- };
6372
- }
6373
6529
  if (visibleAssistantEvent(event)) {
6374
- if (event.type !== "assistant/chunk" && event.type !== "assistant/message") return void 0;
6530
+ if (event.type !== "assistant/live-chunk" && event.type !== "assistant/message" && event.type !== "assistant/attempt") return void 0;
6375
6531
  return {
6376
6532
  kind: "assistant",
6377
6533
  seq: event.seq,
6378
6534
  step: event.data.step
6379
6535
  };
6380
6536
  }
6381
- if (event.type === "tool/call" || event.type === "tool/result" && isAppendSurfaceEvent(event) || event.type === "llm/retry") return {
6537
+ if (event.type === "tool/call" || event.type === "tool/result" && event.surfaceOp === "append" || event.type === "llm/retry") return {
6382
6538
  kind: "other",
6383
6539
  seq: event.seq
6384
6540
  };
@@ -6442,7 +6598,7 @@ window.__ModuleLoader__.load({
6442
6598
  }
6443
6599
  function updateProcessState(state, event) {
6444
6600
  let current = state;
6445
- if (event.type === "assistant/message" && isAppendSurfaceEvent(event) && hasAssistantReplyContent(toAssistantBlocks(event.data.message.content))) {
6601
+ if (event.type === "assistant/message" && event.surfaceOp === "append" && hasAssistantReplyContent(toAssistantBlocks(event.data.message.content))) {
6446
6602
  const messageCountByStep = new Map(current.messageCountByStep);
6447
6603
  messageCountByStep.set(event.data.step, (messageCountByStep.get(event.data.step) ?? 0) + 1);
6448
6604
  current = {
@@ -6486,7 +6642,7 @@ window.__ModuleLoader__.load({
6486
6642
  };
6487
6643
  const turn = eventTurn(event);
6488
6644
  if (turn === void 0) return null;
6489
- if (event.type === "assistant/chunk" || event.type === "assistant/message" || isChunkRunEvent(event) || event.type === "tool/call" || event.type === "tool/result" || event.type === "llm/retry" || event.type === "step/start" || event.type === "step/end" || event.type === "turn/end") return {
6645
+ if (event.type === "assistant/live-chunk" || event.type === "assistant/message" || event.type === "tool/call" || event.type === "tool/result" || event.type === "llm/retry" || event.type === "step/start" || event.type === "step/end" || event.type === "turn/end") return {
6490
6646
  id: String(turn),
6491
6647
  role: "update"
6492
6648
  };
@@ -6505,8 +6661,7 @@ window.__ModuleLoader__.load({
6505
6661
  },
6506
6662
  update: (context, match) => updateProcessState(context.state, match.event),
6507
6663
  publication: (match) => {
6508
- if (isChunkRunEvent(match.event)) return "animation-frame";
6509
- if (match.event.type === "assistant/chunk") {
6664
+ if (match.event.type === "assistant/live-chunk") {
6510
6665
  const type = match.event.data.chunk.type;
6511
6666
  return type === "usage" || type === "finish" ? "none" : "animation-frame";
6512
6667
  }
@@ -6549,6 +6704,20 @@ window.__ModuleLoader__.load({
6549
6704
  ctx.uiConversation.events.register(turnProcessDefinition);
6550
6705
  }
6551
6706
  //#endregion
6707
+ //#region ../../llm/llm/src/assistant-stream.ts
6708
+ /**
6709
+ * The last raw chunk of one never-packed type, scanning backwards and stopping at the first hit.
6710
+ * @param stream - compact records from one durable Assistant settlement.
6711
+ * @param type - chunk type that only appears as a raw record.
6712
+ * @returns the stream's final chunk of that type, or undefined when it has none.
6713
+ */
6714
+ function lastAssistantStreamChunk(stream, type) {
6715
+ for (let index = stream.length - 1; index >= 0; index -= 1) {
6716
+ const record = stream[index];
6717
+ if (record.type === "chunk" && record.chunk.type === type) return record.chunk;
6718
+ }
6719
+ }
6720
+ //#endregion
6552
6721
  //#region ../../llm/token-meter/src/turn-usage.ts
6553
6722
  function isCount(value) {
6554
6723
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
@@ -6568,6 +6737,9 @@ window.__ModuleLoader__.load({
6568
6737
  model
6569
6738
  } : void 0;
6570
6739
  }
6740
+ function streamUsage(stream) {
6741
+ return lastAssistantStreamChunk(stream, "usage")?.usage;
6742
+ }
6571
6743
  function normalizeUsage(usage, route) {
6572
6744
  const { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens, totalTokens } = usage;
6573
6745
  if (!isCount(inputTokens) || !isCount(outputTokens)) return void 0;
@@ -6695,16 +6867,19 @@ window.__ModuleLoader__.load({
6695
6867
  };
6696
6868
  continue;
6697
6869
  }
6698
- if (event.type === "assistant/chunk") {
6870
+ if (event.type === "assistant/attempt") {
6699
6871
  if (event.data.turn !== turn || state.kind !== "open" || !sameAttempt(state, event.data.turn, event.data.step)) {
6700
6872
  invalid = true;
6701
6873
  continue;
6702
6874
  }
6703
- if (event.data.chunk.type === "usage") state = {
6704
- ...state,
6705
- sample: event.data.chunk.usage
6875
+ const sample = streamUsage(event.data.stream) ?? state.sample;
6876
+ state = {
6877
+ kind: "open",
6878
+ turn,
6879
+ step: event.data.step,
6880
+ ...sample === void 0 ? {} : { sample }
6706
6881
  };
6707
- else if (event.data.chunk.type === "finish" && (event.data.chunk.reason.kind === "error" || event.data.chunk.reason.kind === "aborted")) if (!closeOpen()) invalid = true;
6882
+ if (!closeOpen()) invalid = true;
6708
6883
  else state = {
6709
6884
  kind: "finishClosed",
6710
6885
  turn,
@@ -6717,9 +6892,10 @@ window.__ModuleLoader__.load({
6717
6892
  invalid = true;
6718
6893
  continue;
6719
6894
  }
6720
- if (event.data.usage !== void 0) state = {
6895
+ const sample = event.data.usage ?? streamUsage(event.data.stream);
6896
+ if (sample !== void 0) state = {
6721
6897
  ...state,
6722
- sample: event.data.usage
6898
+ sample
6723
6899
  };
6724
6900
  if (!closeOpen(messageRoute(event.data.message))) invalid = true;
6725
6901
  else state = {
@@ -6758,21 +6934,17 @@ window.__ModuleLoader__.load({
6758
6934
  //#endregion
6759
6935
  //#region lib/types/client/conversation-nodes/turn-tail.js
6760
6936
  function isSessionEvent(event) {
6761
- return event.type !== "chunkrow/text-chunks" && event.type !== "chunkrow/reasoning-chunks" && event.type !== "chunkrow/tool-call-chunks";
6937
+ return event.type !== "assistant/live-chunk";
6762
6938
  }
6763
6939
  function hasTextAssistant(event) {
6764
- return event.type === "assistant/message" && isAppendSurfaceEvent(event) && toAssistantBlocks(event.data.message.content).some((block) => block.kind === "text" && block.text.trim() !== "");
6940
+ return event.type === "assistant/message" && event.surfaceOp === "append" && toAssistantBlocks(event.data.message.content).some((block) => block.kind === "text" && block.text.trim() !== "");
6765
6941
  }
6766
- function chunkHasText(event) {
6767
- if (event.type === "chunkrow/text-chunks") return event.data.texts.some((text) => text.trim() !== "");
6768
- if (event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks") return false;
6769
- if (event.type !== "assistant/chunk") return false;
6770
- const chunk = event.data.chunk;
6942
+ function chunkHasText(chunk) {
6771
6943
  if (chunk.type === "text-delta") return chunk.text.trim() !== "";
6772
6944
  return chunk.type === "block-end" && chunk.block.type === "text" && chunk.block.text.trim() !== "";
6773
6945
  }
6774
6946
  function turnCoordinates(event) {
6775
- if (event.type === "assistant/message" || event.type === "assistant/chunk" || event.type === "step/start" || event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks" || event.type === "step/end") return {
6947
+ if (event.type === "assistant/message" || event.type === "assistant/attempt" || event.type === "assistant/live-chunk" || event.type === "step/start" || event.type === "step/end") return {
6776
6948
  turn: event.data.turn,
6777
6949
  step: event.data.step
6778
6950
  };
@@ -6793,10 +6965,10 @@ window.__ModuleLoader__.load({
6793
6965
  streamedText: false,
6794
6966
  finalized: false
6795
6967
  };
6796
- if (event.type === "assistant/chunk" || event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks") {
6968
+ if (event.type === "assistant/live-chunk") {
6797
6969
  steps.set(coordinates.step, {
6798
6970
  ...previous,
6799
- streamedText: previous.streamedText || chunkHasText(event)
6971
+ streamedText: previous.streamedText || chunkHasText(event.data.chunk)
6800
6972
  });
6801
6973
  continue;
6802
6974
  }
@@ -6836,7 +7008,7 @@ window.__ModuleLoader__.load({
6836
7008
  let latestTranscriptSeq = finalized.at(-1)?.finalNode.seq;
6837
7009
  for (const match of context.matches) {
6838
7010
  const event = match.event;
6839
- const candidate = event.type === "tool/call" || event.type === "tool/result" && isAppendSurfaceEvent(event) || event.type === "turn/end" && event.data.reason.kind === "error" || event.type === "llm/retry" ? event.seq : void 0;
7011
+ const candidate = event.type === "tool/call" || event.type === "tool/result" && event.surfaceOp === "append" || event.type === "turn/end" && event.data.reason.kind === "error" || event.type === "llm/retry" ? event.seq : void 0;
6840
7012
  if (candidate !== void 0 && (latestTranscriptSeq === void 0 || candidate > latestTranscriptSeq)) latestTranscriptSeq = candidate;
6841
7013
  }
6842
7014
  const metrics = deriveTurnMetrics(finalized.map((candidate) => candidate.finalNode)).get(end.event.data.turn);