@prettier-ai/dsh-client-ui-chat 0.1.2-rc.1 → 0.1.3-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
@@ -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/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/MessageItem.module.css.mjs
118
- const css$15 = ".-EE-uG_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.-EE-uG_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}.-EE-uG_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}.-EE-uG_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))}.-EE-uG_contextRow,.-EE-uG_compactionRow{padding:2px 0}.-EE-uG_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}.-EE-uG_compactionButton:not(:disabled){cursor:pointer}.-EE-uG_compactionButton:not(:disabled):hover{background:var(--dsw-alias-interactive-bg-hover)}.-EE-uG_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}.-EE-uG_compactionLeading svg{width:calc(14px + var(--dsh-content-font-delta,0px));height:calc(14px + var(--dsh-content-font-delta,0px))}.-EE-uG_compactionContextIcon,.-EE-uG_compactionDisclosureIcon{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.-EE-uG_compactionDisclosureIcon,.-EE-uG_compactionButton:not(:disabled):hover .-EE-uG_compactionContextIcon,.-EE-uG_compactionButton:not(:disabled):focus-visible .-EE-uG_compactionContextIcon{opacity:0}.-EE-uG_compactionButton:not(:disabled):hover .-EE-uG_compactionDisclosureIcon,.-EE-uG_compactionButton:not(:disabled):focus-visible .-EE-uG_compactionDisclosureIcon{opacity:1}.-EE-uG_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}.-EE-uG_compactionSep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.-EE-uG_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}.-EE-uG_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))}.-EE-uG_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))}.-EE-uG_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}.-EE-uG_retrySummary::-webkit-details-marker{display:none}.-EE-uG_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)}.-EE-uG_retrySummary:hover{color:var(--dsw-alias-label-secondary)}.-EE-uG_retrySummary:focus-visible{outline:1.5px solid var(--dsw-alias-button-info-fill);outline-offset:2px}.-EE-uG_retryText{color:inherit}.-EE-uG_retryRow[data-active] .-EE-uG_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 -EE-uG_retry-shimmer}.-EE-uG_retryRow[open] .-EE-uG_retrySummary:after{transform:rotate(45deg)}.-EE-uG_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}.-EE-uG_retryDetailLabel{color:var(--dsw-alias-label-secondary)}.-EE-uG_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}.-EE-uG_turnErrorDot{margin-top:5px}.-EE-uG_turnErrorCopy{overflow-wrap:anywhere;min-width:0}.-EE-uG_turnErrorTitle{color:var(--dsw-alias-state-error-primary);margin-right:6px;font-weight:600}.-EE-uG_turnErrorMessage{color:var(--dsw-alias-label-secondary)}.-EE-uG_turnErrorCode{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-markdown-code-block-small)}.-EE-uG_maxTokensTitle{color:var(--dsw-alias-state-warn-primary);margin-right:6px;font-weight:600}@keyframes -EE-uG_retry-shimmer{0%{background-position:100%}to{background-position:0}}@media (prefers-reduced-motion:reduce){.-EE-uG_retryRow[data-active] .-EE-uG_retryText{color:inherit;background:0 0;animation:none}}";
119
+ const css$15 = ".-EE-uG_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.-EE-uG_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}.-EE-uG_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}.-EE-uG_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))}.-EE-uG_contextRow,.-EE-uG_compactionRow{padding:2px 0}.-EE-uG_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}.-EE-uG_compactionButton:not(:disabled){cursor:pointer}.-EE-uG_compactionButton:not(:disabled):hover{background:var(--dsw-alias-interactive-bg-hover)}.-EE-uG_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}.-EE-uG_compactionLeading svg{width:calc(14px + var(--dsh-content-font-delta,0px));height:calc(14px + var(--dsh-content-font-delta,0px))}.-EE-uG_compactionContextIcon,.-EE-uG_compactionDisclosureIcon{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.-EE-uG_compactionDisclosureIcon,.-EE-uG_compactionButton:not(:disabled):hover .-EE-uG_compactionContextIcon,.-EE-uG_compactionButton:not(:disabled):focus-visible .-EE-uG_compactionContextIcon{opacity:0}.-EE-uG_compactionButton:not(:disabled):hover .-EE-uG_compactionDisclosureIcon,.-EE-uG_compactionButton:not(:disabled):focus-visible .-EE-uG_compactionDisclosureIcon{opacity:1}.-EE-uG_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}.-EE-uG_compactionSep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.-EE-uG_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}.-EE-uG_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))}.-EE-uG_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))}.-EE-uG_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}.-EE-uG_retrySummary::-webkit-details-marker{display:none}.-EE-uG_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)}.-EE-uG_retrySummary:hover{color:var(--dsw-alias-label-secondary)}.-EE-uG_retrySummary:focus-visible{outline:1.5px solid var(--dsw-alias-button-info-fill);outline-offset:2px}.-EE-uG_retryText{color:inherit}.-EE-uG_retryRow[data-active] .-EE-uG_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 -EE-uG_retry-shimmer}.-EE-uG_retryRow[open] .-EE-uG_retrySummary:after{transform:rotate(45deg)}.-EE-uG_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}.-EE-uG_retryDetailLabel{color:var(--dsw-alias-label-secondary)}.-EE-uG_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}.-EE-uG_turnErrorDot{margin-top:5px}.-EE-uG_turnErrorCopy{overflow-wrap:anywhere;min-width:0}.-EE-uG_turnErrorTitle{color:var(--dsw-alias-state-error-primary);margin-right:6px;font-weight:600}.-EE-uG_turnErrorMessage{color:var(--dsw-alias-label-secondary)}.-EE-uG_turnErrorCode{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-markdown-code-block-small)}.-EE-uG_maxTokensTitle{color:var(--dsw-alias-state-warn-primary);margin-right:6px;font-weight:600}@keyframes -EE-uG_retry-shimmer{0%{background-position:100%}to{background-position:0}}@media (prefers-reduced-motion:reduce){.-EE-uG_retryRow[data-active] .-EE-uG_retryText{color:inherit;background:0 0;animation:none}}.-EE-uG_attachmentRow{flex-wrap:wrap;justify-content:flex-end;gap:8px;max-width:100%;display:flex}.-EE-uG_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}.-EE-uG_fileIcon{flex:none;width:24px;height:28px}.-EE-uG_fileContent{flex-direction:column;flex:1;min-width:0;display:flex}.-EE-uG_fileName{white-space:nowrap;text-overflow:ellipsis;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px;overflow:hidden}.-EE-uG_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 = "@prettier-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": "-EE-uG_attachmentRow",
128
130
  "bubble": "-EE-uG_bubble",
129
131
  "compactionBody": "-EE-uG_compactionBody",
130
132
  "compactionButton": "-EE-uG_compactionButton",
@@ -136,6 +138,11 @@ window.__ModuleLoader__.load({
136
138
  "compactionSummary": "-EE-uG_compactionSummary",
137
139
  "compactionTitle": "-EE-uG_compactionTitle",
138
140
  "contextRow": "-EE-uG_contextRow",
141
+ "fileCard": "-EE-uG_fileCard",
142
+ "fileContent": "-EE-uG_fileContent",
143
+ "fileIcon": "-EE-uG_fileIcon",
144
+ "fileMeta": "-EE-uG_fileMeta",
145
+ "fileName": "-EE-uG_fileName",
139
146
  "maxTokensTitle": "-EE-uG_maxTokensTitle",
140
147
  "referenceSummary": "-EE-uG_referenceSummary",
141
148
  "retry-shimmer": "-EE-uG_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)(_prettier_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, _prettier_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, _prettier_ai_dsh_client_ui_primitives.projectUserText)(text, referenceLabels), rest.map((block, i) => (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.JsonBlock, {
1239
+ children: [(0, _prettier_ai_dsh_client_ui_primitives.projectUserText)(text, referenceLabels, skillNames), rest.map((block, i) => (0, react_jsx_runtime.jsx)(_prettier_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
@@ -2468,6 +2515,7 @@ window.__ModuleLoader__.load({
2468
2515
  openFile: requestOpenFile,
2469
2516
  inspectCall,
2470
2517
  forkAt,
2518
+ loadImage,
2471
2519
  renderMessageImages,
2472
2520
  fileMentions,
2473
2521
  renderSlot,
@@ -3902,6 +3950,345 @@ window.__ModuleLoader__.load({
3902
3950
  });
3903
3951
  });
3904
3952
  //#endregion
3953
+ //#region ../../util/values/src/index.ts
3954
+ /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
3955
+ function hasIntrinsicConstructor(prototype, name) {
3956
+ const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
3957
+ if (typeof constructor !== "function") return false;
3958
+ try {
3959
+ return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
3960
+ } catch {
3961
+ return false;
3962
+ }
3963
+ }
3964
+ /** Whether a candidate is one realm's intrinsic `Object.prototype`. */
3965
+ function isIntrinsicObjectPrototype(value) {
3966
+ return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
3967
+ }
3968
+ /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
3969
+ function hasPlainArrayPrototype(value) {
3970
+ const prototype = Object.getPrototypeOf(value);
3971
+ if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
3972
+ const objectPrototype = Object.getPrototypeOf(prototype);
3973
+ return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
3974
+ }
3975
+ /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
3976
+ function hasPlainObjectPrototype(value) {
3977
+ const prototype = Object.getPrototypeOf(value);
3978
+ return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
3979
+ }
3980
+ /** Return every JSON-visible object key, or reject own data JSON would discard. */
3981
+ function enumerableStringKeys(value) {
3982
+ const keys = Reflect.ownKeys(value);
3983
+ if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
3984
+ return keys;
3985
+ }
3986
+ /** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
3987
+ function walkJsonValue(value, detach) {
3988
+ const ancestors = /* @__PURE__ */ new Set();
3989
+ let root;
3990
+ const assign = (destination, item) => {
3991
+ if (destination === void 0) return;
3992
+ if (destination.kind === "root") root = item;
3993
+ else if (destination.kind === "array") destination.target[destination.index] = item;
3994
+ else Object.defineProperty(destination.target, destination.key, {
3995
+ value: item,
3996
+ enumerable: true,
3997
+ configurable: true,
3998
+ writable: true
3999
+ });
4000
+ };
4001
+ const tasks = [{
4002
+ kind: "visit",
4003
+ value,
4004
+ ...detach ? { destination: { kind: "root" } } : {}
4005
+ }];
4006
+ for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
4007
+ if (task.kind === "leave") {
4008
+ ancestors.delete(task.source);
4009
+ continue;
4010
+ }
4011
+ if (task.kind === "array-item") {
4012
+ if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
4013
+ tasks.push({
4014
+ kind: "visit",
4015
+ value: task.source[task.index],
4016
+ ...task.target === void 0 ? {} : { destination: {
4017
+ kind: "array",
4018
+ target: task.target,
4019
+ index: task.index
4020
+ } }
4021
+ });
4022
+ continue;
4023
+ }
4024
+ if (task.kind === "object-property") {
4025
+ tasks.push({
4026
+ kind: "visit",
4027
+ value: task.source[task.key],
4028
+ ...task.target === void 0 ? {} : { destination: {
4029
+ kind: "object",
4030
+ target: task.target,
4031
+ key: task.key
4032
+ } }
4033
+ });
4034
+ continue;
4035
+ }
4036
+ const current = task.value;
4037
+ if (current === null) {
4038
+ assign(task.destination, null);
4039
+ continue;
4040
+ }
4041
+ if (typeof current === "boolean" || typeof current === "string") {
4042
+ assign(task.destination, current);
4043
+ continue;
4044
+ }
4045
+ if (typeof current === "number") {
4046
+ if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
4047
+ assign(task.destination, current);
4048
+ continue;
4049
+ }
4050
+ if (typeof current !== "object") return void 0;
4051
+ if (ancestors.has(current)) return void 0;
4052
+ if (Array.isArray(current)) {
4053
+ if (!hasPlainArrayPrototype(current)) return void 0;
4054
+ const length = current.length;
4055
+ if (Reflect.ownKeys(current).length !== length + 1) return void 0;
4056
+ const target = detach ? [] : void 0;
4057
+ if (target !== void 0) assign(task.destination, target);
4058
+ ancestors.add(current);
4059
+ tasks.push({
4060
+ kind: "leave",
4061
+ source: current
4062
+ });
4063
+ for (let index = length - 1; index >= 0; index--) tasks.push({
4064
+ kind: "array-item",
4065
+ source: current,
4066
+ index,
4067
+ ...target === void 0 ? {} : { target }
4068
+ });
4069
+ continue;
4070
+ }
4071
+ if (!hasPlainObjectPrototype(current)) return void 0;
4072
+ const keys = enumerableStringKeys(current);
4073
+ if (keys === void 0) return void 0;
4074
+ const target = detach ? {} : void 0;
4075
+ if (target !== void 0) assign(task.destination, target);
4076
+ ancestors.add(current);
4077
+ tasks.push({
4078
+ kind: "leave",
4079
+ source: current
4080
+ });
4081
+ for (let index = keys.length - 1; index >= 0; index--) {
4082
+ const key = keys[index];
4083
+ /* v8 ignore next -- the loop is bounded by the captured key count. */
4084
+ if (key === void 0) return void 0;
4085
+ tasks.push({
4086
+ kind: "object-property",
4087
+ source: current,
4088
+ key,
4089
+ ...target === void 0 ? {} : { target }
4090
+ });
4091
+ }
4092
+ }
4093
+ return detach ? root : true;
4094
+ }
4095
+ /**
4096
+ * Validate and detach lossless JSON in one read per property.
4097
+ * @param value - candidate value to validate and detach.
4098
+ * @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
4099
+ */
4100
+ function snapshotJsonValue(value) {
4101
+ return walkJsonValue(value, true);
4102
+ }
4103
+ /**
4104
+ * Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
4105
+ * @param value - value to freeze.
4106
+ * @returns the same value after every reachable enumerable child is frozen.
4107
+ */
4108
+ function deepFreeze(value) {
4109
+ const seen = /* @__PURE__ */ new WeakSet();
4110
+ const pending = [{
4111
+ kind: "visit",
4112
+ node: value
4113
+ }];
4114
+ while (pending.length > 0) {
4115
+ const task = pending.pop();
4116
+ /* v8 ignore next -- the loop condition guarantees one pending task. */
4117
+ if (task === void 0) continue;
4118
+ if (task.kind === "property") {
4119
+ pending.push({
4120
+ kind: "visit",
4121
+ node: task.source[task.key]
4122
+ });
4123
+ continue;
4124
+ }
4125
+ const node = task.node;
4126
+ if (node === null || typeof node !== "object") continue;
4127
+ if (node instanceof AbortSignal) continue;
4128
+ if (seen.has(node)) continue;
4129
+ seen.add(node);
4130
+ Object.freeze(node);
4131
+ const keys = Object.keys(node);
4132
+ for (let index = keys.length - 1; index >= 0; index--) {
4133
+ const key = keys[index];
4134
+ /* v8 ignore next -- the loop is bounded by the captured key count. */
4135
+ if (key === void 0) continue;
4136
+ pending.push({
4137
+ kind: "property",
4138
+ source: node,
4139
+ key
4140
+ });
4141
+ }
4142
+ }
4143
+ return value;
4144
+ }
4145
+ //#endregion
4146
+ //#region ../../llm/llm/src/assistant-stream.ts
4147
+ /** Lossless compact representation of one model-stream attempt. */
4148
+ function safeTime(value) {
4149
+ if (!Number.isSafeInteger(value)) throw new TypeError(`Assistant stream time must be a safe integer, got ${String(value)}`);
4150
+ return value;
4151
+ }
4152
+ function safeIndex(value, label) {
4153
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`${label} index must be a non-negative safe integer`);
4154
+ return value;
4155
+ }
4156
+ function snapshotChunk(chunk) {
4157
+ const snapshot = snapshotJsonValue(chunk);
4158
+ if (snapshot === void 0) throw new TypeError("Assistant stream chunk must be losslessly JSON-serializable");
4159
+ return snapshot;
4160
+ }
4161
+ /**
4162
+ * Expand compact records into the exact timed chunk sequence.
4163
+ * @param stream - compact records from one durable Assistant settlement.
4164
+ * @returns detached timed chunks with every original delta boundary preserved.
4165
+ * @throws {TypeError} when a record or reconstructed timestamp is invalid.
4166
+ */
4167
+ function expandAssistantStream(stream) {
4168
+ const chunks = [];
4169
+ for (const candidate of stream) {
4170
+ const record = validateRecord(candidate);
4171
+ if (record.type === "chunk") {
4172
+ chunks.push({
4173
+ time: record.time,
4174
+ chunk: record.chunk
4175
+ });
4176
+ continue;
4177
+ }
4178
+ const members = record.type === "tool-call-chunks" ? record.args : record.texts;
4179
+ let time = record.time0;
4180
+ for (let index = 0; index < members.length; index += 1) {
4181
+ if (index > 0) time += record.dt[index - 1];
4182
+ let chunk;
4183
+ if (record.type === "text-chunks") chunk = {
4184
+ type: "text-delta",
4185
+ index: record.index,
4186
+ text: members[index]
4187
+ };
4188
+ else if (record.type === "reasoning-chunks") chunk = {
4189
+ type: "reasoning-delta",
4190
+ index: record.index,
4191
+ text: members[index]
4192
+ };
4193
+ else chunk = {
4194
+ type: "tool-call-delta",
4195
+ index: record.index,
4196
+ id: record.id,
4197
+ ...Object.hasOwn(record, "name") ? { name: record.name } : {},
4198
+ argumentsDelta: members[index]
4199
+ };
4200
+ chunks.push({
4201
+ time,
4202
+ chunk
4203
+ });
4204
+ }
4205
+ }
4206
+ return chunks;
4207
+ }
4208
+ function validateRecord(value) {
4209
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Assistant stream record must be an object");
4210
+ const record = value;
4211
+ switch (record.type) {
4212
+ case "text-chunks":
4213
+ case "reasoning-chunks": {
4214
+ exactKeys(record, [
4215
+ "type",
4216
+ "time0",
4217
+ "index",
4218
+ "dt",
4219
+ "texts"
4220
+ ], record.type);
4221
+ const texts = stringArray(record.texts, `${record.type} texts`);
4222
+ if (texts.length === 0) throw new TypeError(`${record.type} texts must be non-empty`);
4223
+ validateRun(record, texts.length, record.type);
4224
+ return record;
4225
+ }
4226
+ case "tool-call-chunks": {
4227
+ exactKeys(record, Object.hasOwn(record, "name") ? [
4228
+ "type",
4229
+ "time0",
4230
+ "index",
4231
+ "dt",
4232
+ "id",
4233
+ "name",
4234
+ "args"
4235
+ ] : [
4236
+ "type",
4237
+ "time0",
4238
+ "index",
4239
+ "dt",
4240
+ "id",
4241
+ "args"
4242
+ ], record.type);
4243
+ const args = stringArray(record.args, "tool-call-chunks args");
4244
+ if (args.length === 0) throw new TypeError("tool-call-chunks args must be non-empty");
4245
+ if (typeof record.id !== "string" || record.id.length === 0) throw new TypeError("tool-call-chunks id must be a non-empty string");
4246
+ if (record.name !== void 0 && (typeof record.name !== "string" || record.name.length === 0)) throw new TypeError("tool-call-chunks name must be a non-empty string");
4247
+ validateRun(record, args.length, record.type);
4248
+ return record;
4249
+ }
4250
+ case "chunk": {
4251
+ exactKeys(record, [
4252
+ "type",
4253
+ "time",
4254
+ "chunk"
4255
+ ], "chunk");
4256
+ const time = safeTime(record.time);
4257
+ if (typeof record.chunk !== "object" || record.chunk === null || Array.isArray(record.chunk)) throw new TypeError("Assistant stream raw chunk must be a lossless JSON object");
4258
+ let chunk;
4259
+ try {
4260
+ chunk = snapshotChunk(record.chunk);
4261
+ } catch (error) {
4262
+ throw new TypeError("Assistant stream raw chunk must be a lossless JSON object", { cause: error });
4263
+ }
4264
+ return deepFreeze({
4265
+ type: "chunk",
4266
+ time,
4267
+ chunk
4268
+ });
4269
+ }
4270
+ default: throw new TypeError(`Unsupported Assistant stream record ${JSON.stringify(record.type)}`);
4271
+ }
4272
+ }
4273
+ function validateRun(record, members, label) {
4274
+ safeTime(record.time0);
4275
+ safeIndex(record.index, label);
4276
+ if (!Array.isArray(record.dt) || record.dt.some((value) => !Number.isSafeInteger(value))) throw new TypeError(`${label} dt must contain safe integers`);
4277
+ if (record.dt.length !== members - 1) throw new TypeError(`${label} dt length must be one less than its members`);
4278
+ let time = record.time0;
4279
+ for (const gap of record.dt) {
4280
+ time += gap;
4281
+ if (!Number.isSafeInteger(time)) throw new TypeError(`${label} member times must stay safe integers`);
4282
+ }
4283
+ }
4284
+ function stringArray(value, label) {
4285
+ if (!Array.isArray(value) || value.some((member) => typeof member !== "string")) throw new TypeError(`${label} must be a string array`);
4286
+ return value;
4287
+ }
4288
+ function exactKeys(record, keys, label) {
4289
+ if (Object.keys(record).length !== keys.length || !keys.every((key) => Object.hasOwn(record, key))) throw new TypeError(`${label} Assistant stream record must contain exactly ${keys.join(", ")}`);
4290
+ }
4291
+ //#endregion
3905
4292
  //#region ../../core/session/src/surface.ts
3906
4293
  /** Runtime counterpart of the message-producing event union. */
3907
4294
  const SURFACE_EVENT_TYPES = new Set([
@@ -4075,6 +4462,16 @@ window.__ModuleLoader__.load({
4075
4462
  return collect(record, "references", "label");
4076
4463
  }
4077
4464
  /**
4465
+ * Read the skill name a durable skill-invocation injection loaded.
4466
+ * @param source - Logged `user/message` source.
4467
+ * @returns The skill name, or null for every other source.
4468
+ */
4469
+ function skillInvocationName(source) {
4470
+ const record = asRecord(source);
4471
+ if (record === null || readString(record, "kind") !== "skill-invocation") return null;
4472
+ return readString(record, "name");
4473
+ }
4474
+ /**
4078
4475
  * Classify finalized Assistant content for Chat rendering.
4079
4476
  * @param content - Core content blocks.
4080
4477
  * @returns Chat blocks in source order.
@@ -4173,9 +4570,6 @@ window.__ModuleLoader__.load({
4173
4570
  }
4174
4571
  //#endregion
4175
4572
  //#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
4573
  function initialState(turn, step) {
4180
4574
  return {
4181
4575
  turn,
@@ -4219,9 +4613,7 @@ window.__ModuleLoader__.load({
4219
4613
  hidden: true
4220
4614
  };
4221
4615
  }
4222
- function updateChunk(state, match) {
4223
- if (match.event.type !== "assistant/chunk") return state;
4224
- const chunk = match.event.data.chunk;
4616
+ function updateChunk(state, chunk, seq, time) {
4225
4617
  const blocks = [...state.blocks];
4226
4618
  let changedIndex = -1;
4227
4619
  let previousVisible = false;
@@ -4288,79 +4680,16 @@ window.__ModuleLoader__.load({
4288
4680
  visibleBlocks,
4289
4681
  hidden: visibleBlocks > 0 ? false : state.hidden,
4290
4682
  ...visibleBlocks > 0 && state.firstVisibleSeq === void 0 ? {
4291
- firstVisibleSeq: match.event.seq,
4292
- firstVisibleTime: match.event.time
4683
+ firstVisibleSeq: seq,
4684
+ firstVisibleTime: time
4293
4685
  } : {},
4294
- ...firstToken && state.firstTokenTime === void 0 ? { firstTokenTime: match.event.time } : {}
4295
- };
4296
- }
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
4686
+ ...firstToken && state.firstTokenTime === void 0 ? { firstTokenTime: time } : {}
4316
4687
  };
4317
4688
  }
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]));
4353
- return {
4354
- ...state,
4355
- 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 }
4363
- };
4689
+ function updateEmbedded(state, event) {
4690
+ let next = state;
4691
+ for (const member of expandAssistantStream(event.data.stream)) next = updateChunk(next, member.chunk, event.seq, member.time);
4692
+ return next;
4364
4693
  }
4365
4694
  function closedBoundary(location) {
4366
4695
  if (location.kind === "step" && location.step.status === "closed" && location.step.end !== void 0) return location.step.end;
@@ -4405,15 +4734,14 @@ window.__ModuleLoader__.load({
4405
4734
  function fallbackState$5(context) {
4406
4735
  let state;
4407
4736
  for (const match of context.matches) {
4408
- if (isChunkRunEvent$1(match.event)) {
4737
+ if (match.event.type === "assistant/live-chunk") {
4409
4738
  state ??= initialState(match.event.data.turn, match.event.data.step);
4410
- state = updateChunkRun(state, match.event);
4739
+ state = updateChunk(state, match.event.data.chunk, match.event.seq, match.event.time);
4411
4740
  continue;
4412
4741
  }
4413
- if (match.event.type === "assistant/chunk") {
4742
+ if (match.event.type === "assistant/message" || match.event.type === "assistant/attempt") {
4414
4743
  state ??= initialState(match.event.data.turn, match.event.data.step);
4415
- state = updateChunk(state, match);
4416
- continue;
4744
+ state = updateEmbedded(state, match.event);
4417
4745
  }
4418
4746
  if (match.event.type === "assistant/message") {
4419
4747
  state ??= initialState(match.event.data.turn, match.event.data.step);
@@ -4469,11 +4797,7 @@ window.__ModuleLoader__.load({
4469
4797
  id: `${event.data.turn}:${event.data.step}`,
4470
4798
  role: "start"
4471
4799
  };
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 {
4800
+ if (event.type === "assistant/live-chunk" || event.type === "assistant/attempt" || event.type === "assistant/message" && isAppendSurfaceEvent(event)) return {
4477
4801
  id: `${event.data.turn}:${event.data.step}`,
4478
4802
  role: "update"
4479
4803
  };
@@ -4488,12 +4812,13 @@ window.__ModuleLoader__.load({
4488
4812
  return initialState(match.event.data.turn, match.event.data.step);
4489
4813
  },
4490
4814
  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);
4815
+ if (match.event.type === "assistant/live-chunk") return updateChunk(context.state, match.event.data.chunk, match.event.seq, match.event.time);
4816
+ if (match.event.type === "assistant/attempt") return updateEmbedded(context.state, match.event);
4493
4817
  if (match.event.type === "assistant/message") {
4818
+ const streamed = updateEmbedded(context.state, match.event);
4494
4819
  const blocks = toAssistantBlocks(match.event.data.message.content);
4495
4820
  return {
4496
- ...context.state,
4821
+ ...streamed,
4497
4822
  blocks,
4498
4823
  visibleBlocks: countVisibleBlocks(blocks),
4499
4824
  hidden: false,
@@ -4506,8 +4831,7 @@ window.__ModuleLoader__.load({
4506
4831
  },
4507
4832
  publication: (match) => {
4508
4833
  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";
4834
+ if (match.event.type !== "assistant/live-chunk") return "immediate";
4511
4835
  const type = match.event.data.chunk.type;
4512
4836
  return type === "usage" || type === "finish" ? "none" : "animation-frame";
4513
4837
  },
@@ -5107,6 +5431,194 @@ window.__ModuleLoader__.load({
5107
5431
  return [...byKey.values()];
5108
5432
  }
5109
5433
  };
5434
+ function withSkillNames(node, names) {
5435
+ const candidate = node;
5436
+ if (candidate.kind !== "user" && candidate.kind !== "steering") return node;
5437
+ const current = candidate.data.skillNames ?? EMPTY_KEYS;
5438
+ const hasNames = Object.hasOwn(candidate.data, "skillNames");
5439
+ if (sameReferences$1(current, names) && hasNames === names.length > 0) return node;
5440
+ const data = { ...candidate.data };
5441
+ if (names.length === 0) delete data.skillNames;
5442
+ else data.skillNames = names;
5443
+ return {
5444
+ ...candidate,
5445
+ data
5446
+ };
5447
+ }
5448
+ /**
5449
+ * Classify one Node for batching: a direct message, a `skill-invocation`
5450
+ * context, or a boundary of any other kind. A context that injects no skill
5451
+ * (workspace rules, the catalog, a recall) is transparent and yields null.
5452
+ */
5453
+ function slashEntryOf(node) {
5454
+ const candidate = node;
5455
+ if (candidate.kind === "user" || candidate.kind === "steering") return {
5456
+ key: node.key,
5457
+ seq: node.anchorSeq,
5458
+ kind: "message",
5459
+ name: null
5460
+ };
5461
+ if (candidate.kind === "context") {
5462
+ const name = skillInvocationName(candidate.data.source);
5463
+ return name === null ? null : {
5464
+ key: node.key,
5465
+ seq: node.anchorSeq,
5466
+ kind: "skill",
5467
+ name
5468
+ };
5469
+ }
5470
+ return {
5471
+ key: node.key,
5472
+ seq: node.anchorSeq,
5473
+ kind: "boundary",
5474
+ name: null
5475
+ };
5476
+ }
5477
+ function sameSlashEntry(left, right) {
5478
+ return left.seq === right.seq && left.kind === right.kind && left.name === right.name;
5479
+ }
5480
+ /**
5481
+ * Attaches each direct message's step-loaded skill names to its Node.
5482
+ *
5483
+ * A step's `skill-invocation` injections follow the direct messages the host
5484
+ * scanned for `/name` gestures and precede the step's first Node of any other
5485
+ * kind, so every non-message, non-context Node closes a batch. Every ended
5486
+ * Turn publishes its `turn-tail` Node on `turn/end` whatever the reason, so a
5487
+ * batch never spans Turns, and `step/start` precedes the direct message in
5488
+ * the log, so no boundary separates a message from its injections. Names
5489
+ * attach to every direct message of the batch: the bubble decorates only the
5490
+ * tokens its own text carries.
5491
+ *
5492
+ * The index holds only messages, skill injections, and boundaries, ordered by
5493
+ * `anchorSeq`. An apply re-reads just the batches around the Nodes whose
5494
+ * classification changed and never scans the store, so an assistant
5495
+ * streaming frame costs nothing here (the append hot path never scans the
5496
+ * Chat Nodes).
5497
+ */
5498
+ var SkillNameProjector = class {
5499
+ entries = /* @__PURE__ */ new Map();
5500
+ /** Every indexed entry in `anchorSeq` order. */
5501
+ sorted = [];
5502
+ /**
5503
+ * Rebuild the index from a whole Node set and attach names to its messages.
5504
+ * @param nodes - every materialized Chat Node, in any order.
5505
+ * @returns the same Nodes, direct messages carrying their batch's names.
5506
+ */
5507
+ replace(nodes) {
5508
+ this.entries.clear();
5509
+ this.sorted = [];
5510
+ for (const node of nodes) {
5511
+ const entry = slashEntryOf(node);
5512
+ if (entry === null) continue;
5513
+ this.entries.set(entry.key, entry);
5514
+ this.sorted.push(entry);
5515
+ }
5516
+ this.sorted.sort((left, right) => left.seq - right.seq);
5517
+ const names = /* @__PURE__ */ new Map();
5518
+ for (let index = 0; index < this.sorted.length; index++) {
5519
+ if (this.sorted[index]?.kind === "boundary") continue;
5520
+ const end = this.runEnd(index);
5521
+ this.assignRun(index, end, names);
5522
+ index = end;
5523
+ }
5524
+ return nodes.map((node) => withSkillNames(node, names.get(node.key) ?? EMPTY_KEYS));
5525
+ }
5526
+ /**
5527
+ * Fold one incremental upsert set: re-read only the batches around the
5528
+ * Nodes whose classification changed.
5529
+ * @param upserts - the changed Nodes.
5530
+ * @param store - the resident Nodes, read by key for the messages of an affected batch.
5531
+ * @returns the upserts plus any resident message whose names changed.
5532
+ */
5533
+ apply(upserts, store) {
5534
+ const dirty = [];
5535
+ for (const node of upserts) {
5536
+ const next = slashEntryOf(node);
5537
+ const previous = this.entries.get(node.key);
5538
+ if (previous !== void 0) {
5539
+ if (next !== null && sameSlashEntry(previous, next)) {
5540
+ if (next.kind === "message") dirty.push(next.seq);
5541
+ continue;
5542
+ }
5543
+ this.remove(previous);
5544
+ dirty.push(previous.seq);
5545
+ }
5546
+ if (next === null) continue;
5547
+ this.insert(next);
5548
+ dirty.push(next.seq);
5549
+ }
5550
+ if (dirty.length === 0) return upserts;
5551
+ const names = /* @__PURE__ */ new Map();
5552
+ for (const seq of dirty) this.collectAround(seq, names);
5553
+ const byKey = new Map(upserts.map((node) => [node.key, node]));
5554
+ for (const [key, list] of names) {
5555
+ const node = byKey.get(key) ?? store.get(key);
5556
+ if (node === void 0) continue;
5557
+ const next = withSkillNames(node, list);
5558
+ if (next !== node || byKey.has(key)) byKey.set(key, next);
5559
+ }
5560
+ return [...byKey.values()];
5561
+ }
5562
+ insert(entry) {
5563
+ this.sorted.splice(this.lowerBound(entry.seq), 0, entry);
5564
+ this.entries.set(entry.key, entry);
5565
+ }
5566
+ remove(entry) {
5567
+ this.sorted.splice(this.sorted.indexOf(entry), 1);
5568
+ this.entries.delete(entry.key);
5569
+ }
5570
+ /** First index whose seq is at least `seq`. */
5571
+ lowerBound(seq) {
5572
+ let low = 0;
5573
+ let high = this.sorted.length;
5574
+ while (low < high) {
5575
+ const middle = low + high >>> 1;
5576
+ if ((this.sorted[middle]?.seq ?? Number.POSITIVE_INFINITY) < seq) low = middle + 1;
5577
+ else high = middle;
5578
+ }
5579
+ return low;
5580
+ }
5581
+ /** Last index of the boundary-free run containing `index`. */
5582
+ runEnd(index) {
5583
+ let end = index;
5584
+ while (end + 1 < this.sorted.length && this.sorted[end + 1]?.kind !== "boundary") end++;
5585
+ return end;
5586
+ }
5587
+ /** First index of the boundary-free run containing `index`. */
5588
+ runStart(index) {
5589
+ let start = index;
5590
+ while (start - 1 >= 0 && this.sorted[start - 1]?.kind !== "boundary") start--;
5591
+ return start;
5592
+ }
5593
+ /** Record the names every message of the run `[start, end]` carries. */
5594
+ assignRun(start, end, names) {
5595
+ const list = [];
5596
+ for (let index = start; index <= end; index++) {
5597
+ const entry = this.sorted[index];
5598
+ if (entry?.kind === "skill" && entry.name !== null && !list.includes(entry.name)) list.push(entry.name);
5599
+ }
5600
+ for (let index = start; index <= end; index++) {
5601
+ const entry = this.sorted[index];
5602
+ if (entry?.kind === "message") names.set(entry.key, list);
5603
+ }
5604
+ }
5605
+ /**
5606
+ * Re-read the run(s) around one changed seq: the run holding a message or
5607
+ * skill entry, or — for a boundary, or a seq that left the index — the runs
5608
+ * on both sides of that position.
5609
+ */
5610
+ collectAround(seq, names) {
5611
+ const at = this.lowerBound(seq);
5612
+ const here = this.sorted[at];
5613
+ if (here !== void 0 && here.seq === seq && here.kind !== "boundary") {
5614
+ this.assignRun(this.runStart(at), this.runEnd(at), names);
5615
+ return;
5616
+ }
5617
+ if (at - 1 >= 0 && this.sorted[at - 1]?.kind !== "boundary") this.assignRun(this.runStart(at - 1), at - 1, names);
5618
+ const right = here !== void 0 && here.seq === seq ? at + 1 : at;
5619
+ if (right < this.sorted.length && this.sorted[right]?.kind !== "boundary") this.assignRun(right, this.runEnd(right), names);
5620
+ }
5621
+ };
5110
5622
  const EMPTY_CONTRIBUTION = {
5111
5623
  anchorSeq: 0,
5112
5624
  nodes: EMPTY_LIST,
@@ -5300,6 +5812,7 @@ window.__ModuleLoader__.load({
5300
5812
  navigation = new MutableTurnNavigationIndex();
5301
5813
  legacy = new LegacySliceBuilder();
5302
5814
  referenceLabels = new ReferenceLabelProjector();
5815
+ skillNames = new SkillNameProjector();
5303
5816
  order = EMPTY_KEYS;
5304
5817
  /** Last published timeline: a Turn boundary can land without a new node. */
5305
5818
  timeline = null;
@@ -5311,7 +5824,7 @@ window.__ModuleLoader__.load({
5311
5824
  });
5312
5825
  }
5313
5826
  replace(input) {
5314
- const nodes = this.referenceLabels.replace(input.nodes);
5827
+ const nodes = this.skillNames.replace(this.referenceLabels.replace(input.nodes));
5315
5828
  this.store.replace(nodes);
5316
5829
  this.order = orderedVisibleChatNodes(nodes).map((node) => node.key);
5317
5830
  this.locations.rebuild(this.order, this.store);
@@ -5323,7 +5836,7 @@ window.__ModuleLoader__.load({
5323
5836
  return snapshot;
5324
5837
  }
5325
5838
  apply(input) {
5326
- const upserts = this.referenceLabels.apply(input.upserts, this.store);
5839
+ const upserts = this.skillNames.apply(this.referenceLabels.apply(input.upserts, this.store), this.store);
5327
5840
  const processTurns = /* @__PURE__ */ new Set();
5328
5841
  let structural = false;
5329
5842
  const contentOnly = [];
@@ -5615,13 +6128,10 @@ window.__ModuleLoader__.load({
5615
6128
  const unknownFallbackDefinition = {
5616
6129
  kind: "unknown-surface",
5617
6130
  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
- },
6131
+ match: (event) => event.type !== "assistant/live-chunk" && isAppendSurfaceEvent(event) ? {
6132
+ id: String(event.seq),
6133
+ role: "start"
6134
+ } : null,
5625
6135
  start: (_context, match) => ({
5626
6136
  kind: "unknown",
5627
6137
  seq: match.event.seq,
@@ -6336,24 +6846,22 @@ window.__ModuleLoader__.load({
6336
6846
  }
6337
6847
  //#endregion
6338
6848
  //#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
6849
  function eventTurn(event) {
6343
6850
  const data = event.data;
6344
6851
  return typeof data.turn === "number" ? data.turn : void 0;
6345
6852
  }
6853
+ function visibleChunk(chunk) {
6854
+ if (chunk.type === "text-delta" || chunk.type === "reasoning-delta") return chunk.text.trim() !== "";
6855
+ if (chunk.type === "block-start") return chunk.blockType !== "text" && chunk.blockType !== "reasoning" && chunk.blockType !== "tool-call";
6856
+ if (chunk.type !== "block-end") return false;
6857
+ const block = chunk.block;
6858
+ if (block.type === "tool-call") return false;
6859
+ if (block.type === "text" || block.type === "reasoning") return block.text.trim() !== "";
6860
+ return true;
6861
+ }
6346
6862
  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
- }
6863
+ if (event.type === "assistant/live-chunk") return visibleChunk(event.data.chunk);
6864
+ if (event.type === "assistant/attempt") return expandAssistantStream(event.data.stream).some((member) => visibleChunk(member.chunk));
6357
6865
  return event.type === "assistant/message" && isAppendSurfaceEvent(event) && toAssistantBlocks(event.data.message.content).some((block) => {
6358
6866
  if (block.kind === "tool-call") return false;
6359
6867
  if (block.kind === "text" || block.kind === "reasoning") return block.text.trim() !== "";
@@ -6361,17 +6869,8 @@ window.__ModuleLoader__.load({
6361
6869
  });
6362
6870
  }
6363
6871
  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
6872
  if (visibleAssistantEvent(event)) {
6374
- if (event.type !== "assistant/chunk" && event.type !== "assistant/message") return void 0;
6873
+ if (event.type !== "assistant/live-chunk" && event.type !== "assistant/message" && event.type !== "assistant/attempt") return void 0;
6375
6874
  return {
6376
6875
  kind: "assistant",
6377
6876
  seq: event.seq,
@@ -6486,7 +6985,7 @@ window.__ModuleLoader__.load({
6486
6985
  };
6487
6986
  const turn = eventTurn(event);
6488
6987
  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 {
6988
+ if (event.type === "assistant/live-chunk" || event.type === "assistant/message" || event.type === "assistant/attempt" || 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
6989
  id: String(turn),
6491
6990
  role: "update"
6492
6991
  };
@@ -6505,8 +7004,7 @@ window.__ModuleLoader__.load({
6505
7004
  },
6506
7005
  update: (context, match) => updateProcessState(context.state, match.event),
6507
7006
  publication: (match) => {
6508
- if (isChunkRunEvent(match.event)) return "animation-frame";
6509
- if (match.event.type === "assistant/chunk") {
7007
+ if (match.event.type === "assistant/live-chunk") {
6510
7008
  const type = match.event.data.chunk.type;
6511
7009
  return type === "usage" || type === "finish" ? "none" : "animation-frame";
6512
7010
  }
@@ -6568,6 +7066,11 @@ window.__ModuleLoader__.load({
6568
7066
  model
6569
7067
  } : void 0;
6570
7068
  }
7069
+ function streamUsage(stream) {
7070
+ let sample;
7071
+ for (const member of expandAssistantStream(stream)) if (member.chunk.type === "usage") sample = member.chunk.usage;
7072
+ return sample;
7073
+ }
6571
7074
  function normalizeUsage(usage, route) {
6572
7075
  const { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens, totalTokens } = usage;
6573
7076
  if (!isCount(inputTokens) || !isCount(outputTokens)) return void 0;
@@ -6695,16 +7198,20 @@ window.__ModuleLoader__.load({
6695
7198
  };
6696
7199
  continue;
6697
7200
  }
6698
- if (event.type === "assistant/chunk") {
7201
+ if (event.type === "assistant/attempt") {
6699
7202
  if (event.data.turn !== turn || state.kind !== "open" || !sameAttempt(state, event.data.turn, event.data.step)) {
6700
7203
  invalid = true;
6701
7204
  continue;
6702
7205
  }
6703
- if (event.data.chunk.type === "usage") state = {
6704
- ...state,
6705
- sample: event.data.chunk.usage
7206
+ let sample = state.sample;
7207
+ for (const member of expandAssistantStream(event.data.stream)) if (member.chunk.type === "usage") sample = member.chunk.usage;
7208
+ state = {
7209
+ kind: "open",
7210
+ turn,
7211
+ step: event.data.step,
7212
+ ...sample === void 0 ? {} : { sample }
6706
7213
  };
6707
- else if (event.data.chunk.type === "finish" && (event.data.chunk.reason.kind === "error" || event.data.chunk.reason.kind === "aborted")) if (!closeOpen()) invalid = true;
7214
+ if (!closeOpen()) invalid = true;
6708
7215
  else state = {
6709
7216
  kind: "finishClosed",
6710
7217
  turn,
@@ -6717,9 +7224,10 @@ window.__ModuleLoader__.load({
6717
7224
  invalid = true;
6718
7225
  continue;
6719
7226
  }
6720
- if (event.data.usage !== void 0) state = {
7227
+ const sample = event.data.usage ?? streamUsage(event.data.stream);
7228
+ if (sample !== void 0) state = {
6721
7229
  ...state,
6722
- sample: event.data.usage
7230
+ sample
6723
7231
  };
6724
7232
  if (!closeOpen(messageRoute(event.data.message))) invalid = true;
6725
7233
  else state = {
@@ -6758,21 +7266,22 @@ window.__ModuleLoader__.load({
6758
7266
  //#endregion
6759
7267
  //#region lib/types/client/conversation-nodes/turn-tail.js
6760
7268
  function isSessionEvent(event) {
6761
- return event.type !== "chunkrow/text-chunks" && event.type !== "chunkrow/reasoning-chunks" && event.type !== "chunkrow/tool-call-chunks";
7269
+ return event.type !== "assistant/live-chunk";
6762
7270
  }
6763
7271
  function hasTextAssistant(event) {
6764
7272
  return event.type === "assistant/message" && isAppendSurfaceEvent(event) && toAssistantBlocks(event.data.message.content).some((block) => block.kind === "text" && block.text.trim() !== "");
6765
7273
  }
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;
7274
+ function chunkHasText(chunk) {
6771
7275
  if (chunk.type === "text-delta") return chunk.text.trim() !== "";
6772
7276
  return chunk.type === "block-end" && chunk.block.type === "text" && chunk.block.text.trim() !== "";
6773
7277
  }
7278
+ function eventStreamHasText(event) {
7279
+ if (event.type === "assistant/live-chunk") return chunkHasText(event.data.chunk);
7280
+ if (event.type !== "assistant/attempt") return false;
7281
+ return expandAssistantStream(event.data.stream).some((member) => chunkHasText(member.chunk));
7282
+ }
6774
7283
  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 {
7284
+ if (event.type === "assistant/message" || event.type === "assistant/attempt" || event.type === "assistant/live-chunk" || event.type === "step/start" || event.type === "step/end") return {
6776
7285
  turn: event.data.turn,
6777
7286
  step: event.data.step
6778
7287
  };
@@ -6793,10 +7302,10 @@ window.__ModuleLoader__.load({
6793
7302
  streamedText: false,
6794
7303
  finalized: false
6795
7304
  };
6796
- if (event.type === "assistant/chunk" || event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks") {
7305
+ if (event.type === "assistant/live-chunk" || event.type === "assistant/attempt") {
6797
7306
  steps.set(coordinates.step, {
6798
7307
  ...previous,
6799
- streamedText: previous.streamedText || chunkHasText(event)
7308
+ streamedText: previous.streamedText || eventStreamHasText(event)
6800
7309
  });
6801
7310
  continue;
6802
7311
  }