@jskit-ai/assistant-core 0.2.6 → 0.2.8

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 (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +18 -12
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. package/src/server/lib/assistantToolLoop.js +0 -259
@@ -1,8 +1,19 @@
1
1
  import { effectScope, shallowReactive } from "vue";
2
2
 
3
+ // Each application's retained conversations, by target key; the entry goes when its last reader releases it.
3
4
  const applicationConversations = new WeakMap();
4
5
 
5
- /** Views and voice retain the same readers and delivery state for an exact target. */
6
+ /**
7
+ * Views and voice retain the same readers and delivery state for an exact target. The runtime is created once per
8
+ * application and key, as `createRuntime(readers, retain)`: `readers` holds every current reader, and `retain()`
9
+ * retains the same runtime again for another reader, so the runtime lists it in its result.
10
+ * @template Runtime
11
+ * @param {import("vue").App} app
12
+ * @param {string} key
13
+ * @param {(readers: Map<symbol, { active: boolean }>, retain: () => { runtime: Runtime, release: () => void }) => Runtime} createRuntime
14
+ * @param {{ active: boolean }} [reader]
15
+ * @returns {{ runtime: Runtime, release: () => void }}
16
+ */
6
17
  function retainAssistantConversation(app, key, createRuntime, reader = { active: true }) {
7
18
  let conversations = applicationConversations.get(app);
8
19
  if (!conversations) { conversations = new Map(); applicationConversations.set(app, conversations); }
@@ -10,10 +21,10 @@ function retainAssistantConversation(app, key, createRuntime, reader = { active:
10
21
  if (!entry) {
11
22
  const scope = effectScope(true);
12
23
  const readers = shallowReactive(new Map());
24
+ const retain = () => retainAssistantConversation(app, key, createRuntime);
13
25
  try {
14
- const runtime = app.runWithContext(() => scope.run(() => createRuntime(readers)));
26
+ const runtime = app.runWithContext(() => scope.run(() => createRuntime(readers, retain)));
15
27
  entry = { scope, readers, runtime };
16
- runtime.retain = () => retainAssistantConversation(app, key, createRuntime);
17
28
  conversations.set(key, entry);
18
29
  } catch (error) { scope.stop(); throw error; }
19
30
  }
@@ -3,8 +3,19 @@
3
3
  const CANNOT_SEND_YET = "Your words are in the chat box, but this conversation cannot send yet.";
4
4
  const submissionError = (message, { messageCode }) => Object.assign(new Error(message), { messageCode, messageParams: {} });
5
5
 
6
- /** A companion's text enters the same draft and send action as keyboard input. */
7
- export function createAssistantTextSubmission({ getState, setDraft, submit, afterDraftChange = async () => {} }) {
6
+ /** Without an afterDraftChange, the composer's state is read again as soon as the draft is written. */
7
+ async function readStateAtOnce() {}
8
+
9
+ /**
10
+ * A companion's text enters the same draft and send action as keyboard input.
11
+ * @param {{
12
+ * getState: () => { active: boolean, canSend: boolean, draft: string, id: unknown, turnActive?: boolean },
13
+ * setDraft: (draft: string) => void,
14
+ * submit: () => unknown,
15
+ * afterDraftChange?: () => Promise<void>
16
+ * }} composer
17
+ */
18
+ export function createAssistantTextSubmission({ getState, setDraft, submit, afterDraftChange = readStateAtOnce }) {
8
19
  let revision = 0;
9
20
  return async function submitText(text, { sendImmediately = true, signal, timeoutMs = 2000 } = {}) {
10
21
  const request = ++revision;
@@ -0,0 +1,225 @@
1
+ import { computed } from "vue";
2
+ import { parseNumberedQuestionPrompt } from "../../shared/conversation/numberedQuestionSugar.js";
3
+ import { parseLongTextReviewBlocks } from "../../shared/conversation/longTextBlocks.js";
4
+ import { normalizeThinkingMessageText } from "../../shared/conversation/thinkingText.js";
5
+ import { useJskitText } from "../i18n.js";
6
+
7
+ const USER_MESSAGE_COLLAPSE_MIN_CHARACTERS = 360;
8
+ const USER_MESSAGE_PREVIEW_MAX_CHARACTERS = 280;
9
+ const USER_MESSAGE_PREVIEW_MAX_LINES = 4;
10
+ const DISPLAY_MESSAGE_CACHE_LIMIT = 500;
11
+ const TIME_FORMAT = Object.freeze({
12
+ year: "numeric",
13
+ month: "short",
14
+ day: "2-digit",
15
+ hour: "2-digit",
16
+ minute: "2-digit"
17
+ });
18
+
19
+ /** The collapsed preview of a long user message, or "" when it is short enough to show whole. */
20
+ function userMessagePreviewText(value = "") {
21
+ const text = String(value || "");
22
+ const characters = [...text];
23
+ const lines = text.split(/\r\n?|\n/u);
24
+ if (
25
+ characters.length <= USER_MESSAGE_COLLAPSE_MIN_CHARACTERS &&
26
+ lines.length <= USER_MESSAGE_PREVIEW_MAX_LINES
27
+ ) {
28
+ return "";
29
+ }
30
+ const firstLines = lines.slice(0, USER_MESSAGE_PREVIEW_MAX_LINES).join("\n");
31
+ const preview = [...firstLines]
32
+ .slice(0, USER_MESSAGE_PREVIEW_MAX_CHARACTERS)
33
+ .join("")
34
+ .trimEnd();
35
+ return preview ? `${preview}…` : "";
36
+ }
37
+
38
+ function conversationAgentMessages(turn = {}) {
39
+ if (Array.isArray(turn.messages)) {
40
+ return turn.messages.filter((message) => (
41
+ ["assistant", "commentary", "thinking"].includes(String(message?.role || "").trim())
42
+ ));
43
+ }
44
+ return [
45
+ ...(Array.isArray(turn.thinking) ? turn.thinking : []),
46
+ ...(Array.isArray(turn.commentary) ? turn.commentary : []),
47
+ turn.assistant
48
+ ].filter(Boolean);
49
+ }
50
+
51
+ function conversationMessageKey(message = {}, index = 0) {
52
+ return [
53
+ String(message.messageId || "").trim(),
54
+ String(message.role || "").trim(),
55
+ String(message.at || "").trim(),
56
+ index
57
+ ].join(":");
58
+ }
59
+
60
+ /**
61
+ * The transcript's entries, one per displayed message in chronological order, with consecutive reasoning grouped into
62
+ * one progress entry. Each displayed message is parsed once and cached by its key until `clearDisplayCache()`.
63
+ * @param {{ turns: any[] }} props The transcript's props.
64
+ */
65
+ function useTranscriptEntries(props) {
66
+ const jskitText = useJskitText();
67
+ const { formatDateTime, serverMessage } = jskitText;
68
+ const displayMessageCache = new Map();
69
+
70
+ function displayTime(value = "") {
71
+ const date = new Date(String(value || ""));
72
+ if (Number.isNaN(date.getTime())) {
73
+ return "";
74
+ }
75
+ return formatDateTime(date, TIME_FORMAT);
76
+ }
77
+
78
+ function displayMessage(message = null, {
79
+ allowNumberedQuestions = false,
80
+ preserveParagraphLineBreaks = false,
81
+ previewUserMessage = false
82
+ } = {}, cacheKey = "") {
83
+ if (!message) {
84
+ return null;
85
+ }
86
+ const normalizedCacheKey = String(cacheKey || "").trim();
87
+ const cached = normalizedCacheKey ? displayMessageCache.get(normalizedCacheKey) : null;
88
+ // Times and status texts follow the active locale, so a locale change re-renders them.
89
+ const activeLocale = jskitText.locale;
90
+ if (
91
+ cached &&
92
+ cached.locale === activeLocale &&
93
+ cached.allowNumberedQuestions === allowNumberedQuestions &&
94
+ cached.attachments === message.attachments &&
95
+ cached.at === message.at &&
96
+ cached.messageId === message.messageId &&
97
+ cached.preserveParagraphLineBreaks === preserveParagraphLineBreaks &&
98
+ cached.previewUserMessage === previewUserMessage &&
99
+ cached.role === message.role &&
100
+ cached.text === message.text
101
+ ) {
102
+ return { ...message, ...cached.value };
103
+ }
104
+ const messageText = message.textCode ? serverMessage({ message: message.text, messageCode: message.textCode }) : message.text;
105
+ const questionInput = allowNumberedQuestions
106
+ ? parseNumberedQuestionPrompt(messageText)
107
+ : {
108
+ intro: "",
109
+ outro: "",
110
+ questions: []
111
+ };
112
+ const hasQuestions = questionInput.questions.length > 0;
113
+ const previewText = previewUserMessage ? userMessagePreviewText(message.text) : "";
114
+ const value = {
115
+ blocks: parseLongTextReviewBlocks(hasQuestions ? questionInput.intro : messageText, {
116
+ preserveParagraphLineBreaks
117
+ }),
118
+ outroBlocks: parseLongTextReviewBlocks(hasQuestions ? questionInput.outro : "", {
119
+ preserveParagraphLineBreaks
120
+ }),
121
+ previewBlocks: previewText
122
+ ? parseLongTextReviewBlocks(previewText, { preserveParagraphLineBreaks: true })
123
+ : null,
124
+ questions: hasQuestions ? questionInput.questions : [],
125
+ displayAt: displayTime(message.at)
126
+ };
127
+ if (normalizedCacheKey) {
128
+ if (
129
+ displayMessageCache.size >= DISPLAY_MESSAGE_CACHE_LIMIT &&
130
+ !displayMessageCache.has(normalizedCacheKey)
131
+ ) {
132
+ displayMessageCache.delete(displayMessageCache.keys().next().value);
133
+ }
134
+ displayMessageCache.set(normalizedCacheKey, {
135
+ allowNumberedQuestions,
136
+ attachments: message.attachments,
137
+ at: message.at,
138
+ locale: activeLocale,
139
+ messageId: message.messageId,
140
+ preserveParagraphLineBreaks,
141
+ previewUserMessage,
142
+ role: message.role,
143
+ text: message.text,
144
+ value
145
+ });
146
+ }
147
+ return { ...message, ...value };
148
+ }
149
+
150
+ function displayThinkingMessage(message = null) {
151
+ if (!message) {
152
+ return null;
153
+ }
154
+ const text = normalizeThinkingMessageText(message.text);
155
+ if (!text) {
156
+ return null;
157
+ }
158
+ return {
159
+ ...message,
160
+ text,
161
+ displayAt: displayTime(message.at)
162
+ };
163
+ }
164
+
165
+ // Storage rows are not visual boundaries. Only an intervening message ends a
166
+ // reasoning group, including when history is prepended or execution continues.
167
+ const displayEntries = computed(() => {
168
+ const candidates = [];
169
+ for (const [index, turn] of props.turns.entries()) {
170
+ const turnId = String(turn.turnId || index + 1);
171
+ // A delivery notice belongs to its exact user message, not the previous reply.
172
+ const userDelivery = turn.user && turn.system?.delivery && turn.system.delivery.messageId === turn.user.messageId
173
+ ? turn.system : null;
174
+ const messages = [
175
+ turn.system && !userDelivery && { ...turn.system, role: "system" },
176
+ turn.user && { ...turn.user, role: "user" },
177
+ ...conversationAgentMessages(turn)
178
+ ].filter(Boolean);
179
+ for (const [messageIndex, message] of messages.entries()) {
180
+ const role = String(message.role || "").trim();
181
+ const key = `${turnId}:${conversationMessageKey(message, messageIndex)}`;
182
+ const displayed = role === "thinking" ? displayThinkingMessage(message) : displayMessage(message, {
183
+ allowNumberedQuestions: role !== "user" && role !== "system",
184
+ preserveParagraphLineBreaks: role === "user",
185
+ previewUserMessage: role === "user"
186
+ }, key);
187
+ if (!displayed) continue;
188
+ candidates.push({ key, role, turn: { ...turn, turnId }, message: displayed,
189
+ sortAt: turn.optimistic && turn.optimistic.status !== "accepted" && turn.optimistic.id === message.messageId
190
+ ? NaN : Date.parse(message.at),
191
+ delivery: role === "user" ? userDelivery : null });
192
+ }
193
+ }
194
+ // Native streaming turns and saved authored turns can arrive independently.
195
+ // Local queued timestamps can precede an earlier message's native admission.
196
+ // Keep unconfirmed messages in their supplied positions, along with undated
197
+ // notices. Accepted local copies join chronology even before history arrives.
198
+ const dated = candidates.filter(entry => Number.isFinite(entry.sortAt))
199
+ .sort((a, b) => a.sortAt - b.sortAt)[Symbol.iterator]();
200
+ const entries = [];
201
+ for (const candidate of candidates) {
202
+ const entry = Number.isFinite(candidate.sortAt) ? dated.next().value : candidate;
203
+ const previous = entries.at(-1);
204
+ if (entry.role === "thinking" && previous?.role === "thinking") previous.messages.push({ ...entry.message, key: entry.key });
205
+ else entries.push({ ...entry, ...(entry.role === "thinking" ? { messages: [{ ...entry.message, key: entry.key }] } : {}) });
206
+ }
207
+ // Anchor a progress group to the following message, or to the transcript end.
208
+ // Appending progress or prepending history then preserves its expansion state.
209
+ for (const [index, entry] of entries.entries()) {
210
+ if (entry.role === "thinking") entry.key = `progress:${entries[index + 1]?.key || "tail"}`;
211
+ }
212
+ return entries;
213
+ });
214
+
215
+ function clearDisplayCache() {
216
+ displayMessageCache.clear();
217
+ }
218
+
219
+ return {
220
+ clearDisplayCache,
221
+ displayEntries
222
+ };
223
+ }
224
+
225
+ export { useTranscriptEntries, userMessagePreviewText };