@case-framework/survey-assistant 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/{authoring-references-DYuOAdVx.mjs → authoring-references-ChDTVnms.mjs} +8 -2
  2. package/dist/authoring-references-ChDTVnms.mjs.map +1 -0
  3. package/dist/{capabilities-DlC1l6qy.d.mts → capabilities-CnsVuJ1k.d.mts} +12 -4
  4. package/dist/capabilities-CnsVuJ1k.d.mts.map +1 -0
  5. package/dist/capabilities-default.d.mts +1 -1
  6. package/dist/capabilities-default.d.mts.map +1 -1
  7. package/dist/capabilities-default.mjs +38 -4
  8. package/dist/capabilities-default.mjs.map +1 -1
  9. package/dist/{constants-D-5m_KGB.d.mts → constants-BK1-g6pR.d.mts} +12 -7
  10. package/dist/constants-BK1-g6pR.d.mts.map +1 -0
  11. package/dist/{controller-proxy-Hkuub9dt.d.mts → controller-proxy-CIDRd8Wu.d.mts} +2 -2
  12. package/dist/{controller-proxy-Hkuub9dt.d.mts.map → controller-proxy-CIDRd8Wu.d.mts.map} +1 -1
  13. package/dist/{controller-proxy-0WJZUZYx.mjs → controller-proxy-DY36XFV3.mjs} +2 -2
  14. package/dist/{controller-proxy-0WJZUZYx.mjs.map → controller-proxy-DY36XFV3.mjs.map} +1 -1
  15. package/dist/{digest-BvXpeWcR.d.mts → digest-Dyjch_sC.d.mts} +2 -2
  16. package/dist/{digest-BvXpeWcR.d.mts.map → digest-Dyjch_sC.d.mts.map} +1 -1
  17. package/dist/digest.d.mts +1 -1
  18. package/dist/{engine-0AJrv0hI.mjs → engine-CWTuN3o2.mjs} +866 -663
  19. package/dist/engine-CWTuN3o2.mjs.map +1 -0
  20. package/dist/engine.d.mts +3 -3
  21. package/dist/engine.mjs +3 -3
  22. package/dist/fair-guidance-CEdJYccK.mjs +21 -0
  23. package/dist/fair-guidance-CEdJYccK.mjs.map +1 -0
  24. package/dist/form-authoring-DCd9XfXf.mjs +155 -0
  25. package/dist/form-authoring-DCd9XfXf.mjs.map +1 -0
  26. package/dist/{index-BcyfBeDj.d.mts → index-Bp5W_oPa.d.mts} +25 -5
  27. package/dist/index-Bp5W_oPa.d.mts.map +1 -0
  28. package/dist/{index-JXH-t2EJ.d.mts → index-CPOQgkTN.d.mts} +67 -6
  29. package/dist/index-CPOQgkTN.d.mts.map +1 -0
  30. package/dist/{index-MEOL9_Gr.d.mts → index-EDL1V168.d.mts} +98 -4
  31. package/dist/{index-MEOL9_Gr.d.mts.map → index-EDL1V168.d.mts.map} +1 -1
  32. package/dist/{index-CneD8ibB.d.mts → index-SPHf3OoJ.d.mts} +9 -3
  33. package/dist/index-SPHf3OoJ.d.mts.map +1 -0
  34. package/dist/{memory-thread-repository-1ocPOda8.mjs → memory-thread-repository-acbz6HZJ.mjs} +181 -26
  35. package/dist/memory-thread-repository-acbz6HZJ.mjs.map +1 -0
  36. package/dist/{protocol-DpCYmCjr.mjs → protocol-DATvkr1S.mjs} +125 -6
  37. package/dist/protocol-DATvkr1S.mjs.map +1 -0
  38. package/dist/protocol.d.mts +3 -3
  39. package/dist/protocol.mjs +2 -2
  40. package/dist/{react-DXK5zWNo.mjs → react-3kSE6WtS.mjs} +127 -9
  41. package/dist/react-3kSE6WtS.mjs.map +1 -0
  42. package/dist/react-integration.d.mts +1 -1
  43. package/dist/react-integration.mjs +1 -1
  44. package/dist/react.d.mts +2 -2
  45. package/dist/react.mjs +2 -2
  46. package/dist/references/assistant-operations.md +13 -18
  47. package/dist/references/embedded-forms.md +41 -2
  48. package/dist/references/expressions.md +17 -9
  49. package/dist/references/fair-by-design.md +80 -0
  50. package/dist/references/item-types.md +19 -3
  51. package/dist/references/localization.md +7 -4
  52. package/dist/references/source-material-surveys.md +6 -4
  53. package/dist/{request-context-BBzU3VdG.mjs → request-context-BEjnaaFr.mjs} +10 -5
  54. package/dist/request-context-BEjnaaFr.mjs.map +1 -0
  55. package/dist/server-agent.d.mts +26 -6
  56. package/dist/server-agent.d.mts.map +1 -1
  57. package/dist/server-agent.mjs +70 -13
  58. package/dist/server-agent.mjs.map +1 -1
  59. package/dist/server-runtime.d.mts +22 -11
  60. package/dist/server-runtime.d.mts.map +1 -1
  61. package/dist/server-runtime.mjs +47 -19
  62. package/dist/server-runtime.mjs.map +1 -1
  63. package/dist/server-tasks.d.mts +1 -1
  64. package/dist/server-tasks.mjs +4 -3
  65. package/dist/server-tasks.mjs.map +1 -1
  66. package/dist/server-tools.d.mts +1 -1
  67. package/dist/server-tools.mjs +1 -1
  68. package/dist/server.d.mts +16 -7
  69. package/dist/server.d.mts.map +1 -1
  70. package/dist/server.mjs +4 -4
  71. package/dist/storage-postgres.d.mts +1 -1
  72. package/dist/{tasks-DcfQNJwO.d.mts → tasks-DIZgr4nD.d.mts} +8 -4
  73. package/dist/tasks-DIZgr4nD.d.mts.map +1 -0
  74. package/dist/{thread-documents-BlxNjc7S.d.mts → thread-documents-C3kf0Vds.d.mts} +1 -1
  75. package/dist/{thread-documents-BlxNjc7S.d.mts.map → thread-documents-C3kf0Vds.d.mts.map} +1 -1
  76. package/dist/{thread-handlers-uddXwQjp.d.mts → thread-handlers-DZ1WsorO.d.mts} +9 -4
  77. package/dist/thread-handlers-DZ1WsorO.d.mts.map +1 -0
  78. package/dist/{tools-wgLen05n.mjs → tools-GDd5_GUh.mjs} +403 -77
  79. package/dist/tools-GDd5_GUh.mjs.map +1 -0
  80. package/dist/ui.css +1 -1
  81. package/dist/ui.d.mts +3 -3
  82. package/dist/ui.d.mts.map +1 -1
  83. package/dist/ui.mjs +152 -36
  84. package/dist/ui.mjs.map +1 -1
  85. package/docs/integration.md +73 -5
  86. package/package.json +3 -3
  87. package/dist/authoring-references-DYuOAdVx.mjs.map +0 -1
  88. package/dist/capabilities-DKbNCnBf.mjs +0 -66
  89. package/dist/capabilities-DKbNCnBf.mjs.map +0 -1
  90. package/dist/capabilities-DlC1l6qy.d.mts.map +0 -1
  91. package/dist/constants-D-5m_KGB.d.mts.map +0 -1
  92. package/dist/engine-0AJrv0hI.mjs.map +0 -1
  93. package/dist/index-BcyfBeDj.d.mts.map +0 -1
  94. package/dist/index-CneD8ibB.d.mts.map +0 -1
  95. package/dist/index-JXH-t2EJ.d.mts.map +0 -1
  96. package/dist/memory-thread-repository-1ocPOda8.mjs.map +0 -1
  97. package/dist/protocol-DpCYmCjr.mjs.map +0 -1
  98. package/dist/react-DXK5zWNo.mjs.map +0 -1
  99. package/dist/request-context-BBzU3VdG.mjs.map +0 -1
  100. package/dist/tasks-DcfQNJwO.d.mts.map +0 -1
  101. package/dist/thread-handlers-uddXwQjp.d.mts.map +0 -1
  102. package/dist/tools-wgLen05n.mjs.map +0 -1
package/dist/ui.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { kt as parseSurveyAssistantError } from "./protocol-DpCYmCjr.mjs";
3
- import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-DXK5zWNo.mjs";
4
- import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-0WJZUZYx.mjs";
2
+ import { jt as parseSurveyAssistantError, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-DATvkr1S.mjs";
3
+ import { f as unwrapSurveyAssistantToolOutput, i as createSurveyAssistantThreadClient, l as parseSurveyChangeSet, p as isSurveyAssistantTurnActive, r as useSurveyAssistantModelCatalog, t as useSurveyAssistantSession } from "./react-3kSE6WtS.mjs";
4
+ import { o as connectSurveyAssistantThreadLauncher } from "./controller-proxy-DY36XFV3.mjs";
5
5
  import { getToolName, isFileUIPart, isReasoningUIPart, isToolUIPart } from "ai";
6
6
  import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
7
7
  import { Collapsible, Select, Slot } from "radix-ui";
@@ -15,6 +15,7 @@ import { createLocalizationTranslator, mergeLocalizationCatalogs } from "@case-f
15
15
  import { cva } from "class-variance-authority";
16
16
  import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
17
17
  import { Streamdown } from "streamdown";
18
+ import { bundledLanguages } from "shiki/langs";
18
19
  //#region src/ui/internal/collapsible.tsx
19
20
  function Collapsible$1({ ...props }) {
20
21
  return /* @__PURE__ */ jsx(Collapsible.Root, {
@@ -114,7 +115,25 @@ const surveyAssistantEnMessages = {
114
115
  startConversation: "Start a conversation to see messages here",
115
116
  scrollToLatest: "Scroll to latest message",
116
117
  working: "Working...",
117
- stopped: "Stopped"
118
+ stopped: "Stopped",
119
+ incomplete: "Response interrupted"
120
+ },
121
+ turn: {
122
+ failedTitle: "The assistant did not complete this turn.",
123
+ abortedTitle: "The turn was stopped.",
124
+ reasons: {
125
+ clientAbort: "The request was stopped by the client.",
126
+ firstResponseTimeout: "No meaningful response arrived before the initial deadline.",
127
+ idleTimeout: "The assistant stopped producing activity.",
128
+ maxDuration: "The one-hour execution ceiling was reached.",
129
+ maxSteps: "The assistant reached its tool-step limit before finishing.",
130
+ lengthLimit: "The model reached its output-length limit.",
131
+ contentFilter: "The provider stopped the response because of its content policy.",
132
+ providerError: "The model provider reported an error.",
133
+ streamError: "The response stream ended with an error.",
134
+ incompleteResponse: "The response ended without a normal completion.",
135
+ unknown: "The response ended before completion."
136
+ }
118
137
  },
119
138
  status: {
120
139
  applying: "Applying changes",
@@ -282,7 +301,25 @@ const surveyAssistantNlMessages = {
282
301
  startConversation: "Start een gesprek om hier berichten te zien",
283
302
  scrollToLatest: "Naar het nieuwste bericht scrollen",
284
303
  working: "Bezig...",
285
- stopped: "Gestopt"
304
+ stopped: "Gestopt",
305
+ incomplete: "Antwoord onderbroken"
306
+ },
307
+ turn: {
308
+ failedTitle: "De assistent heeft deze beurt niet voltooid.",
309
+ abortedTitle: "De beurt is gestopt.",
310
+ reasons: {
311
+ clientAbort: "Het verzoek is door de client gestopt.",
312
+ firstResponseTimeout: "Er kwam geen betekenisvol antwoord vóór de eerste tijdslimiet.",
313
+ idleTimeout: "De assistent produceerde geen activiteit meer.",
314
+ maxDuration: "De maximale uitvoertijd van één uur is bereikt.",
315
+ maxSteps: "De assistent bereikte de limiet voor hulpmiddelstappen voordat hij klaar was.",
316
+ lengthLimit: "Het model bereikte de maximale antwoordlengte.",
317
+ contentFilter: "De provider stopte het antwoord vanwege het inhoudsbeleid.",
318
+ providerError: "De modelprovider meldde een fout.",
319
+ streamError: "De antwoordstream eindigde met een fout.",
320
+ incompleteResponse: "Het antwoord eindigde zonder normale voltooiing.",
321
+ unknown: "Het antwoord eindigde voordat het voltooid was."
322
+ }
286
323
  },
287
324
  status: {
288
325
  applying: "Wijzigingen toepassen",
@@ -919,14 +956,6 @@ const stringifyToolTechnicalDetails = (value) => JSON.stringify(value, function
919
956
  return currentValue;
920
957
  }, 2);
921
958
  //#endregion
922
- //#region src/ui/internal/response.tsx
923
- const Response = ({ children, className, controls = false, ...props }) => /* @__PURE__ */ jsx(Streamdown, {
924
- className: cn("leading-6", "[&>:first-child]:mt-0 [&>:last-child]:mb-0", "[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2", "[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_code]:rounded-md [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.85em]", "[&_h1]:mb-2 [&_h1]:mt-4 [&_h1]:text-base [&_h1]:font-semibold", "[&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-sm [&_h2]:font-semibold", "[&_h3]:mb-1.5 [&_h3]:mt-3 [&_h3]:text-sm [&_h3]:font-semibold", "[&_li]:my-1 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5", "[&_p]:my-2", "[&_pre]:my-3 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:border [&_pre]:bg-muted [&_pre]:p-3", "[&_pre_code]:bg-transparent [&_pre_code]:p-0", "[&_strong]:font-semibold", "[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold", className),
925
- controls,
926
- ...props,
927
- children
928
- });
929
- //#endregion
930
959
  //#region src/ui/internal/select.tsx
931
960
  function Select$1({ ...props }) {
932
961
  return /* @__PURE__ */ jsx(Select.Root, {
@@ -1172,6 +1201,30 @@ const CodeBlock = ({ code, language, showLineNumbers = false, className, childre
1172
1201
  });
1173
1202
  };
1174
1203
  //#endregion
1204
+ //#region src/ui/internal/response.tsx
1205
+ const highlightedLanguages = Object.keys(bundledLanguages);
1206
+ const codeHighlighter = {
1207
+ getSupportedLanguages: () => highlightedLanguages,
1208
+ getThemes: () => ["github-light", "github-dark"],
1209
+ highlight: ({ code, language }, callback) => highlightCode(code, language, callback),
1210
+ name: "shiki",
1211
+ supportsLanguage: (language) => language in bundledLanguages,
1212
+ type: "code-highlighter"
1213
+ };
1214
+ const Response = ({ children, className, controls = false, plugins, ...props }) => {
1215
+ const responsePlugins = useMemo(() => ({
1216
+ ...plugins,
1217
+ code: plugins?.code ?? codeHighlighter
1218
+ }), [plugins]);
1219
+ return /* @__PURE__ */ jsx(Streamdown, {
1220
+ className: cn("leading-6", "*:first:mt-0 *:last:mb-0", "[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2", "[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_h1]:mb-2 [&_h1]:mt-4 [&_h1]:text-base [&_h1]:font-semibold", "[&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-sm [&_h2]:font-semibold", "[&_h3]:mb-1.5 [&_h3]:mt-3 [&_h3]:text-sm [&_h3]:font-semibold", "[&_li]:my-1 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5", "[&_p]:my-2", "**:data-[streamdown=code-block]:my-3 **:data-[streamdown=code-block]:gap-0 **:data-[streamdown=code-block]:overflow-hidden **:data-[streamdown=code-block]:rounded-lg **:data-[streamdown=code-block]:bg-muted/40 **:data-[streamdown=code-block]:p-0", "**:data-[streamdown=code-block-header]:px-2", "**:data-[streamdown=code-block-body]:rounded-none **:data-[streamdown=code-block-body]:border-0 **:data-[streamdown=code-block-body]:border-t **:data-[streamdown=code-block-body]:bg-background **:data-[streamdown=code-block-body]:p-3", "[&_strong]:font-semibold", "[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold", className),
1221
+ controls,
1222
+ plugins: responsePlugins,
1223
+ ...props,
1224
+ children
1225
+ });
1226
+ };
1227
+ //#endregion
1175
1228
  //#region src/ui/internal/message-timing.ts
1176
1229
  const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1177
1230
  const asTimestamp = (value) => {
@@ -1198,6 +1251,9 @@ const getPartTimestamps = (part) => {
1198
1251
  * time here keeps restored chats informative without a second timing store.
1199
1252
  */
1200
1253
  const getPersistedMessageDurationSeconds = (message) => {
1254
+ const turnFinalPart = message.parts.find((part) => part.type === SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE);
1255
+ const turnFinal = surveyAssistantTurnFinalSchema.safeParse(turnFinalPart && "data" in turnFinalPart ? turnFinalPart.data : void 0);
1256
+ if (turnFinal.success && turnFinal.data.durationMs !== void 0) return Math.max(1, Math.ceil(turnFinal.data.durationMs / 1e3));
1201
1257
  if (!isRecord$1(message.metadata)) return void 0;
1202
1258
  const startedAt = asTimestamp(message.metadata.createdAt);
1203
1259
  if (startedAt === void 0) return void 0;
@@ -1206,6 +1262,16 @@ const getPersistedMessageDurationSeconds = (message) => {
1206
1262
  return Math.max(1, Math.ceil((completedAt - startedAt) / 1e3));
1207
1263
  };
1208
1264
  //#endregion
1265
+ //#region src/ui/internal/message-turn-status.ts
1266
+ const getPersistedMessageTurnStatus = (message) => {
1267
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) {
1268
+ const part = message.parts[index];
1269
+ if (part?.type !== SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE || !("data" in part)) continue;
1270
+ const parsed = surveyAssistantTurnFinalSchema.safeParse(part.data);
1271
+ if (parsed.success) return parsed.data.status;
1272
+ }
1273
+ };
1274
+ //#endregion
1209
1275
  //#region src/ui/survey-assistant-panel.tsx
1210
1276
  const getAssistantQuickPrompts = (t) => [
1211
1277
  {
@@ -1373,7 +1439,7 @@ const getStoppedToolStatusMeta = (t) => ({
1373
1439
  label: t("activity.stopped"),
1374
1440
  stepStatus: "complete"
1375
1441
  });
1376
- const SurveyAssistantToolStep = ({ applyResultsByChangeId, isAborted, part, isLast }) => {
1442
+ const SurveyAssistantToolStep = ({ applyResultsByChangeId, terminalTurnStatus, part, isLast }) => {
1377
1443
  const t = useSurveyAssistantTranslation();
1378
1444
  const toolStatusMeta = getToolStatusMeta(t);
1379
1445
  const toolName = getToolName(part);
@@ -1382,7 +1448,11 @@ const SurveyAssistantToolStep = ({ applyResultsByChangeId, isAborted, part, isLa
1382
1448
  const changeSet = output === void 0 ? null : getChangeSet(output);
1383
1449
  const applyResult = changeSet ? applyResultsByChangeId[changeSet.id] : void 0;
1384
1450
  const title = changeSet ? toolName === "survey_expression" ? t("activity.expressionChange") : t("activity.surveyChange") : getToolTitle(t, toolName, part.state);
1385
- const meta = isAborted && toolStatusMeta[part.state].stepStatus === "active" ? getStoppedToolStatusMeta(t) : toolStatusMeta[part.state];
1451
+ const meta = terminalTurnStatus && toolStatusMeta[part.state].stepStatus === "active" ? terminalTurnStatus === "aborted" ? getStoppedToolStatusMeta(t) : {
1452
+ icon: /* @__PURE__ */ jsx(XCircleIcon, { className: "size-3 text-red-600" }),
1453
+ label: t("activity.failed"),
1454
+ stepStatus: "complete"
1455
+ } : toolStatusMeta[part.state];
1386
1456
  const metaTitle = meta.label ?? (part.state === "output-denied" ? t("activity.skipped") : part.state === "output-error" ? t("activity.failed") : meta.stepStatus === "active" ? t("activity.workingShort") : t("activity.done"));
1387
1457
  const [detailsOpen, setDetailsOpen] = useState(part.state === "output-error");
1388
1458
  const hasDetails = part.input !== void 0 || output !== void 0 || Boolean(errorText);
@@ -1508,7 +1578,7 @@ const buildMessageBlocks = (parts) => {
1508
1578
  flushChain();
1509
1579
  return blocks;
1510
1580
  };
1511
- const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLastMessage, isStreaming, message, showProgress }) => {
1581
+ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isLastMessage, isStreaming, message, showProgress, terminalTurnStatus }) => {
1512
1582
  const blocks = useMemo(() => buildMessageBlocks(message.parts), [message.parts]);
1513
1583
  const persistedDuration = useMemo(() => getPersistedMessageDurationSeconds(message), [message]);
1514
1584
  const lastPart = message.parts.at(-1);
@@ -1522,11 +1592,9 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1522
1592
  }, block.key);
1523
1593
  return /* @__PURE__ */ jsx("span", { children: block.text }, block.key);
1524
1594
  }
1525
- if (block.type === "file") return /* @__PURE__ */ jsxs("a", {
1526
- className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs hover:bg-muted/80",
1527
- href: block.part.url,
1528
- rel: "noreferrer",
1529
- target: "_blank",
1595
+ if (block.type === "file") return /* @__PURE__ */ jsxs("span", {
1596
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-2 py-1 text-xs",
1597
+ title: getFilePartLabel(block.part),
1530
1598
  children: [
1531
1599
  /* @__PURE__ */ jsx(FileIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
1532
1600
  /* @__PURE__ */ jsx("span", {
@@ -1560,6 +1628,7 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1560
1628
  className: "w-full rounded-md bg-muted/35 px-2 py-1.5",
1561
1629
  defaultDuration: persistedDuration,
1562
1630
  defaultOpen: isChainActive,
1631
+ duration: isChainActive ? void 0 : persistedDuration,
1563
1632
  isStreaming: isChainActive,
1564
1633
  children: [/* @__PURE__ */ jsx(ChainOfThoughtHeader, { className: "text-xs font-medium" }), /* @__PURE__ */ jsxs(ChainOfThoughtContent, {
1565
1634
  className: "relative mt-2",
@@ -1574,9 +1643,9 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1574
1643
  }, entry.key);
1575
1644
  return /* @__PURE__ */ jsx(SurveyAssistantToolStep, {
1576
1645
  applyResultsByChangeId,
1577
- isAborted,
1578
1646
  isLast: isLastEntry,
1579
- part: entry.part
1647
+ part: entry.part,
1648
+ terminalTurnStatus
1580
1649
  }, entry.key);
1581
1650
  })]
1582
1651
  })]
@@ -1585,6 +1654,33 @@ const SurveyAssistantMessageParts = ({ applyResultsByChangeId, isAborted, isLast
1585
1654
  };
1586
1655
  const SurveyAssistantTurnOutcomeNotice = (outcome) => {
1587
1656
  const t = useSurveyAssistantTranslation();
1657
+ if (outcome.status === "failed" || outcome.status === "aborted") {
1658
+ const reason = (() => {
1659
+ switch (outcome.terminationReason) {
1660
+ case "client-abort": return t("turn.reasons.clientAbort");
1661
+ case "first-response-timeout": return t("turn.reasons.firstResponseTimeout");
1662
+ case "idle-timeout": return t("turn.reasons.idleTimeout");
1663
+ case "max-duration": return t("turn.reasons.maxDuration");
1664
+ case "max-steps": return t("turn.reasons.maxSteps");
1665
+ case "length-limit": return t("turn.reasons.lengthLimit");
1666
+ case "content-filter": return t("turn.reasons.contentFilter");
1667
+ case "provider-error": return t("turn.reasons.providerError");
1668
+ case "stream-error": return t("turn.reasons.streamError");
1669
+ case "incomplete-response": return t("turn.reasons.incompleteResponse");
1670
+ default: return t("turn.reasons.unknown");
1671
+ }
1672
+ })();
1673
+ return /* @__PURE__ */ jsxs("div", {
1674
+ className: cn("rounded-lg border px-3 py-2 text-xs", outcome.status === "aborted" ? "border-border bg-muted/50 text-foreground" : "border-destructive/20 bg-destructive/5 text-foreground"),
1675
+ children: [/* @__PURE__ */ jsx("div", {
1676
+ className: "font-medium",
1677
+ children: t(outcome.status === "aborted" ? "turn.abortedTitle" : "turn.failedTitle")
1678
+ }), /* @__PURE__ */ jsx("div", {
1679
+ className: "mt-1 text-muted-foreground",
1680
+ children: reason
1681
+ })]
1682
+ });
1683
+ }
1588
1684
  const description = outcome.status === "blocked" ? t(outcome.blockedChangeSetCount === 1 ? "changes.blocked" : "changes.blockedPlural", { count: outcome.blockedChangeSetCount }) : t(outcome.appliedChangeSetCount === 1 ? "changes.partial" : "changes.partialPlural", {
1589
1685
  applied: outcome.appliedChangeSetCount,
1590
1686
  failed: outcome.failedChangeSetCount
@@ -1642,6 +1738,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1642
1738
  surveyId,
1643
1739
  threadId
1644
1740
  });
1741
+ const hasSubmittedMessageRef = useRef((initialMessages?.length ?? 0) > 0);
1645
1742
  const showAssistantError = Boolean(authorizationError) || Boolean(error);
1646
1743
  const assistantErrorPresentation = getAssistantErrorPresentation(authorizationError ?? error, t);
1647
1744
  const selectedModelLabel = models.find((model) => model.id === modelId)?.label ?? modelId ?? t("input.defaultModel");
@@ -1691,7 +1788,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1691
1788
  onTurnSettled
1692
1789
  ]);
1693
1790
  const showPendingAssistantRow = isAssistantTurnActive && (!lastMessage || lastMessage.role !== "assistant");
1694
- const sessionStatusLabel = state === "applying" ? t("status.applying") : state === "applied" ? t("status.applied") : state === "aborted" ? t("status.stopped") : isAssistantTurnActive ? getStreamingProgressLabel(t, lastMessage) : showAssistantError ? t("status.attention") : t("status.ready");
1791
+ const sessionStatusLabel = state === "applying" ? t("status.applying") : state === "applied" ? t("status.applied") : state === "aborted" ? t("status.stopped") : isAssistantTurnActive ? getStreamingProgressLabel(t, lastMessage) : showAssistantError || state === "failed" ? t("status.attention") : t("status.ready");
1695
1792
  const handleSubmit = useCallback((message) => {
1696
1793
  if (isAssistantTurnActive) return false;
1697
1794
  const submittedFromFocusedPrompt = document.activeElement === promptTextareaRef.current;
@@ -1701,7 +1798,10 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1701
1798
  });
1702
1799
  if (accepted) {
1703
1800
  shouldRestorePromptFocusRef.current = submittedFromFocusedPrompt;
1704
- onFirstSubmit(message.text);
1801
+ if (!hasSubmittedMessageRef.current) {
1802
+ hasSubmittedMessageRef.current = true;
1803
+ onFirstSubmit(message.text);
1804
+ }
1705
1805
  }
1706
1806
  return accepted;
1707
1807
  }, [
@@ -1720,7 +1820,10 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1720
1820
  text: initialLaunch.prompt,
1721
1821
  uiContext: initialLaunch.uiContext
1722
1822
  })) {
1723
- onFirstSubmit(initialLaunch.prompt);
1823
+ if (!hasSubmittedMessageRef.current) {
1824
+ hasSubmittedMessageRef.current = true;
1825
+ onFirstSubmit(initialLaunch.prompt);
1826
+ }
1724
1827
  onInitialLaunchHandled();
1725
1828
  }
1726
1829
  }, [
@@ -1736,7 +1839,7 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1736
1839
  /* @__PURE__ */ jsxs("header", {
1737
1840
  className: "box-content flex h-9 shrink-0 items-center justify-between gap-2 border-b border-border bg-muted/20 px-2.5",
1738
1841
  children: [/* @__PURE__ */ jsx(SurveyAssistantHeaderIdentity, {
1739
- indicatorClassName: showAssistantError ? "bg-destructive" : isAssistantTurnActive ? "animate-pulse bg-blue-500" : state === "aborted" ? "bg-muted-foreground" : "bg-emerald-500",
1842
+ indicatorClassName: showAssistantError || state === "failed" ? "bg-destructive" : isAssistantTurnActive ? "animate-pulse bg-blue-500" : state === "aborted" ? "bg-muted-foreground" : "bg-emerald-500",
1740
1843
  status: sessionStatusLabel,
1741
1844
  title: threadTitle
1742
1845
  }), threadControls]
@@ -1791,6 +1894,8 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1791
1894
  })
1792
1895
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [messages.map((message, index) => {
1793
1896
  const isLastMessage = index === messages.length - 1;
1897
+ const persistedTurnStatus = getPersistedMessageTurnStatus(message);
1898
+ const terminalTurnStatus = persistedTurnStatus === "aborted" || persistedTurnStatus === "failed" ? persistedTurnStatus : isLastMessage && (state === "aborted" || state === "failed") ? state : void 0;
1794
1899
  const shouldShowProgress = isAssistantTurnActive && isLastMessage && message.role === "assistant";
1795
1900
  return /* @__PURE__ */ jsx(Message, {
1796
1901
  className: "max-w-[96%] gap-1",
@@ -1799,17 +1904,17 @@ const SurveyAssistantChatPanel = ({ api, applyPolicy, controller, embedded = fal
1799
1904
  className: cn(message.role === "assistant" && "w-full space-y-2"),
1800
1905
  children: hasRenderableParts(message) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SurveyAssistantMessageParts, {
1801
1906
  applyResultsByChangeId,
1802
- isAborted: state === "aborted",
1803
1907
  isLastMessage,
1804
1908
  isStreaming: isAssistantTurnActive,
1805
1909
  message,
1806
- showProgress: shouldShowProgress
1807
- }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
1910
+ showProgress: shouldShowProgress,
1911
+ terminalTurnStatus
1912
+ }), isLastMessage && latestTurnOutcome && /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome })] }) : shouldShowProgress ? /* @__PURE__ */ jsx(SurveyAssistantProgressIndicator, { message }) : isLastMessage && latestTurnOutcome ? /* @__PURE__ */ jsx(SurveyAssistantTurnOutcomeNotice, { ...latestTurnOutcome }) : state === "aborted" ? /* @__PURE__ */ jsx("span", {
1808
1913
  className: "text-muted-foreground text-sm",
1809
1914
  children: t("conversation.stopped")
1810
1915
  }) : /* @__PURE__ */ jsx("span", {
1811
1916
  className: "text-muted-foreground text-sm",
1812
- children: t("conversation.working")
1917
+ children: t("conversation.incomplete")
1813
1918
  })
1814
1919
  })
1815
1920
  }, message.id);
@@ -1924,7 +2029,7 @@ const createThreadTitle = (text, fallback) => {
1924
2029
  const words = normalized.split(" ").slice(0, 8).join(" ");
1925
2030
  return words.length > 64 ? `${words.slice(0, 61).trimEnd()}...` : words;
1926
2031
  };
1927
- const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
2032
+ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], onConfirmDeleteThread, onModelChange = () => {}, onTurnActiveChange: onAssistantTurnActiveChange, resourceId, selectedThreadStorageKey, surveyId, threadApi, threadClient: providedThreadClient, threadLauncher, ...sessionProps }) => {
1928
2033
  const t = useSurveyAssistantTranslation();
1929
2034
  const threadClient = useMemo(() => providedThreadClient ?? createSurveyAssistantThreadClient(threadApi), [providedThreadClient, threadApi]);
1930
2035
  const storageKey = selectedThreadStorageKey === false ? null : selectedThreadStorageKey ?? `${SELECTED_THREAD_STORAGE_KEY}:${encodeURIComponent(resourceId)}${surveyId ? `:${encodeURIComponent(surveyId)}` : ""}`;
@@ -1936,6 +2041,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
1936
2041
  const [editingTitle, setEditingTitle] = useState("");
1937
2042
  const [threadError, setThreadError] = useState(null);
1938
2043
  const [loadingThread, setLoadingThread] = useState(true);
2044
+ const [assistantTurnActive, setAssistantTurnActive] = useState(false);
1939
2045
  const [initialLaunch, setInitialLaunch] = useState();
1940
2046
  const historyControlsRef = useRef(null);
1941
2047
  const selectModel = useCallback((modelId) => {
@@ -2062,6 +2168,14 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2062
2168
  setEditingThreadId(null);
2063
2169
  setEditingTitle("");
2064
2170
  }, []);
2171
+ const handleTurnActiveChange = useCallback((active) => {
2172
+ setAssistantTurnActive(active);
2173
+ onAssistantTurnActiveChange?.(active);
2174
+ if (active) {
2175
+ cancelEditingThread();
2176
+ setHistoryOpen(false);
2177
+ }
2178
+ }, [cancelEditingThread, onAssistantTurnActiveChange]);
2065
2179
  const commitEditingThread = useCallback(async () => {
2066
2180
  if (!editingThreadId) return;
2067
2181
  const thread = threads.find((item) => item.id === editingThreadId);
@@ -2094,10 +2208,9 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2094
2208
  threads
2095
2209
  ]);
2096
2210
  const handleFirstSubmit = useCallback((text) => {
2097
- if (!selectedThread || initialMessages.length > 0) return;
2211
+ if (!selectedThread || selectedThread.titleMode === "manual") return;
2098
2212
  setAutomaticTitle(selectedThread, createThreadTitle(text, t("threads.newChat")));
2099
2213
  }, [
2100
- initialMessages.length,
2101
2214
  selectedThread,
2102
2215
  setAutomaticTitle,
2103
2216
  t
@@ -2193,7 +2306,8 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2193
2306
  children: [
2194
2307
  /* @__PURE__ */ jsx("button", {
2195
2308
  "aria-label": t("threads.newChat"),
2196
- className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground",
2309
+ className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
2310
+ disabled: assistantTurnActive,
2197
2311
  onClick: () => void createThread(),
2198
2312
  title: t("threads.newChat"),
2199
2313
  type: "button",
@@ -2202,7 +2316,8 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2202
2316
  /* @__PURE__ */ jsx("button", {
2203
2317
  "aria-expanded": historyOpen,
2204
2318
  "aria-label": t("threads.history"),
2205
- className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground",
2319
+ className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
2320
+ disabled: assistantTurnActive,
2206
2321
  onClick: toggleHistory,
2207
2322
  title: t("threads.history"),
2208
2323
  type: "button",
@@ -2286,6 +2401,7 @@ const SurveyAssistantPanelContent = ({ embedded = false, modelId, models = [], o
2286
2401
  onModelChange: selectModel,
2287
2402
  onFirstSubmit: handleFirstSubmit,
2288
2403
  onTurnSettled: handleTurnSettled,
2404
+ onTurnActiveChange: handleTurnActiveChange,
2289
2405
  resourceId,
2290
2406
  surveyId,
2291
2407
  threadControls,