@jini-ai/chat 0.6.5 → 0.6.7

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 (161) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/core/accepted-input.d.ts +54 -0
  3. package/dist/core/accepted-input.js +165 -0
  4. package/dist/core/agent-plugin-chat-state.d.ts +43 -0
  5. package/dist/core/agent-plugin-chat-state.js +48 -0
  6. package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
  7. package/dist/core/assistant-client/messages.d.ts +1 -1
  8. package/dist/core/assistant-client/messages.js +2 -2
  9. package/dist/core/assistant-client/transport.d.ts +2 -2
  10. package/dist/core/assistant-client/transport.js +184 -97
  11. package/dist/core/assistant-content.d.ts +2 -1
  12. package/dist/core/assistant-content.js +9 -11
  13. package/dist/core/browser/page-actions.js +4 -2
  14. package/dist/core/composer-history.d.ts +16 -9
  15. package/dist/core/composer-history.js +28 -16
  16. package/dist/core/conversation-title.d.ts +3 -0
  17. package/dist/core/conversation-title.js +3 -0
  18. package/dist/core/durable-projection.js +20 -13
  19. package/dist/core/durable-runs/continuation.d.ts +4 -0
  20. package/dist/core/durable-runs/continuation.js +35 -6
  21. package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
  22. package/dist/core/durable-runs/policies.d.ts +1 -0
  23. package/dist/core/durable-runs/policies.js +1 -0
  24. package/dist/core/durable-runs/ports.d.ts +6 -2
  25. package/dist/core/index.d.ts +2 -0
  26. package/dist/core/index.js +2 -0
  27. package/dist/core/messages.d.ts +2 -0
  28. package/dist/core/partial-json.d.ts +6 -7
  29. package/dist/core/partial-json.js +81 -47
  30. package/dist/core/persistence/title.js +4 -2
  31. package/dist/core/question-answer.d.ts +23 -0
  32. package/dist/core/question-answer.js +66 -0
  33. package/dist/core/question-form/format.d.ts +6 -6
  34. package/dist/core/question-form/format.js +16 -17
  35. package/dist/core/question-form/normalize.d.ts +18 -0
  36. package/dist/core/question-form/normalize.js +112 -122
  37. package/dist/core/question-form/scan.js +141 -203
  38. package/dist/core/run-activity.js +62 -49
  39. package/dist/core/run-events/entry.d.ts +10 -1
  40. package/dist/core/run-events/entry.js +117 -87
  41. package/dist/core/todos.d.ts +1 -1
  42. package/dist/core/todos.js +60 -85
  43. package/dist/core/tool-events.js +14 -12
  44. package/dist/core/transcript.js +62 -16
  45. package/dist/core/turn-usage.d.ts +2 -1
  46. package/dist/core/turn-usage.js +56 -27
  47. package/dist/core/user-text-redaction.js +3 -0
  48. package/dist/core/util/manifest.js +77 -82
  49. package/dist/core/util/markdown-context.js +51 -53
  50. package/dist/core/util/parser.js +136 -144
  51. package/dist/core/util/pointer.js +16 -16
  52. package/dist/core/util/strip.js +57 -67
  53. package/dist/core/util/validate.js +15 -17
  54. package/dist/embed-widget/browser/session-store.js +21 -12
  55. package/dist/embed-widget/transports/fetch-sse.js +108 -95
  56. package/dist/embed-widget/vanilla/index.js +81 -70
  57. package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
  58. package/dist/react/components/AttachmentPreviewModal.js +25 -1
  59. package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
  60. package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
  61. package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
  62. package/dist/react/components/ChatSettingsDialog.js +72 -0
  63. package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
  64. package/dist/react/components/ChatSettingsSegmented.js +11 -0
  65. package/dist/react/components/Composer.d.ts +5 -1
  66. package/dist/react/components/Composer.js +126 -82
  67. package/dist/react/components/ConversationList.js +25 -39
  68. package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
  69. package/dist/react/components/ConversationTitleEditor.js +54 -0
  70. package/dist/react/components/Icon.d.ts +1 -1
  71. package/dist/react/components/Icon.js +25 -45
  72. package/dist/react/components/Markdown.js +134 -123
  73. package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
  74. package/dist/react/components/McpUiSurfaceCard.js +30 -9
  75. package/dist/react/components/MessageList.d.ts +13 -13
  76. package/dist/react/components/MessageList.hooks.d.ts +6 -1
  77. package/dist/react/components/MessageList.hooks.js +19 -3
  78. package/dist/react/components/MessageList.js +34 -10
  79. package/dist/react/components/MessageRow.js +48 -32
  80. package/dist/react/components/MessageRow.usage.d.ts +5 -2
  81. package/dist/react/components/MessageRow.usage.js +20 -11
  82. package/dist/react/components/MidRunUserMessage.js +16 -3
  83. package/dist/react/components/QuestionForm.js +148 -76
  84. package/dist/react/components/QuestionsPanel.js +1 -1
  85. package/dist/react/components/TodoCard.js +12 -6
  86. package/dist/react/components/ToolCard.js +47 -40
  87. package/dist/react/components/composer-discovery.js +6 -1
  88. package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
  89. package/dist/react/components/mcp-ui-surface-state.js +23 -8
  90. package/dist/react/ext-event-renderer-registry.d.ts +8 -0
  91. package/dist/react/ext-event-renderer-registry.js +5 -2
  92. package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
  93. package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
  94. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
  95. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  96. package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
  97. package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
  98. package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
  99. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
  100. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
  101. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
  102. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
  103. package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
  104. package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
  105. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
  106. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
  107. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
  108. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
  109. package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
  110. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
  111. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
  112. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
  113. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
  114. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
  115. package/dist/react/features/chat-pane/model-options.d.ts +18 -1
  116. package/dist/react/features/chat-pane/model-options.js +49 -20
  117. package/dist/react/features/chat-pane/rules.d.ts +8 -9
  118. package/dist/react/features/chat-pane/rules.js +84 -46
  119. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  120. package/dist/react/features/chat-pane/styles.js +156 -37
  121. package/dist/react/features/chat-pane/types.d.ts +17 -8
  122. package/dist/react/hooks/assistant-chats.js +12 -5
  123. package/dist/react/hooks/composer-draft-cache.js +116 -118
  124. package/dist/react/hooks/composer-history-storage.js +5 -5
  125. package/dist/react/hooks/fab-position.js +7 -4
  126. package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
  127. package/dist/react/hooks/useAnsweredCards.js +38 -11
  128. package/dist/react/hooks/useArtifactStream.js +28 -22
  129. package/dist/react/hooks/useComposer.d.ts +20 -0
  130. package/dist/react/hooks/useComposer.js +22 -9
  131. package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
  132. package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
  133. package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
  134. package/dist/react/hooks/useConversation.d.ts +7 -0
  135. package/dist/react/hooks/useConversation.js +23 -10
  136. package/dist/react/hooks/useExtEventGroups.js +39 -39
  137. package/dist/react/hooks/useQuestionForms.js +21 -26
  138. package/dist/react/hooks/useRunStream.js +10 -2
  139. package/dist/react/index.d.ts +7 -1
  140. package/dist/react/index.js +9 -0
  141. package/dist/react/message-blocks.d.ts +6 -4
  142. package/dist/react/message-blocks.js +94 -72
  143. package/dist/react/slots.d.ts +2 -2
  144. package/dist/react/tool-call-display.js +19 -17
  145. package/dist/server/durable-runs/recover.d.ts +1 -1
  146. package/dist/server/durable-runs/recover.js +139 -76
  147. package/dist/server/run-finalizer/entry.js +25 -15
  148. package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
  149. package/dist/store/legacy/sqlite/conversations.js +69 -98
  150. package/dist/store/legacy/sqlite/messages.d.ts +10 -2
  151. package/dist/store/legacy/sqlite/messages.js +113 -71
  152. package/dist/store/runtime/chat-history-store.js +9 -3
  153. package/dist/store/runtime/durable-run-store.d.ts +2 -2
  154. package/dist/store/runtime/durable-run-store.js +165 -117
  155. package/dist/store/runtime/history-tools.js +118 -82
  156. package/dist/store/runtime/run-ledger.d.ts +4 -1
  157. package/dist/store/runtime/run-ledger.js +56 -16
  158. package/dist/store/sql/cursor.js +38 -18
  159. package/dist/store/sql/store.d.ts +18 -0
  160. package/dist/store/sql/store.js +46 -15
  161. package/package.json +12 -10
@@ -77,60 +77,92 @@ export function upsertMessage({ db, conversationId, m: incoming }, options = {})
77
77
  ? (options.redactUserText ?? redactUserText)({ text: incoming.content })
78
78
  : null;
79
79
  const m = redaction ? { ...incoming, content: redaction.text } : incoming;
80
- const runUpsert = db.transaction(() => {
81
- const existing = db
82
- .prepare(`SELECT conversation_id AS conversationId, position FROM messages WHERE id = ?`)
83
- .get(m.id);
84
- if (existing && existing.conversationId !== conversationId) {
85
- throw new MessageConversationMismatchError({ messageId: m.id, actualConversationId: existing.conversationId, requestedConversationId: conversationId });
86
- }
87
- const now = Date.now();
88
- if (existing) {
89
- db.prepare(`UPDATE messages
90
- SET role = ?, content = ?, agent_id = ?, agent_name = ?,
91
- run_id = ?, run_status = ?, last_run_event_id = ?,
92
- events_json = ?, attachments_json = ?,
93
- session_mode = ?, run_context_json = ?,
94
- telemetry_finalized_at = CASE
95
- WHEN ? THEN COALESCE(telemetry_finalized_at, ?)
96
- ELSE telemetry_finalized_at
97
- END,
98
- started_at = ?, ended_at = ?
99
- WHERE id = ? AND conversation_id = ?`).run(m.role, m.content, m.agentId ?? null, m.agentName ?? null, m.runId ?? null, m.runStatus ?? null, m.lastRunEventId ?? null, m.events ? JSON.stringify(m.events) : null, m.attachments ? JSON.stringify(m.attachments) : null, normalizeMessageSessionModeForStorage(m.sessionMode), m.runContext ? JSON.stringify(m.runContext) : null, m.telemetryFinalized === true ? 1 : 0, now, m.startedAt ?? null, m.endedAt ?? null, m.id, conversationId);
100
- }
101
- else {
102
- const max = db
103
- .prepare(`SELECT COALESCE(MAX(position), -1) AS m FROM messages WHERE conversation_id = ?`)
104
- .get(conversationId);
105
- const position = (max?.m ?? -1) + 1;
106
- const createdAt = typeof m.createdAt === 'number' && Number.isFinite(m.createdAt)
107
- ? m.createdAt
108
- : now;
109
- // 18 values: id, conversation_id, role, content, agent_id, agent_name,
110
- // run_id, run_status, last_run_event_id, events_json, attachments_json,
111
- // session_mode, run_context_json, telemetry_finalized_at, started_at,
112
- // ended_at, position, created_at.
113
- db.prepare(`INSERT INTO messages
114
- (id, conversation_id, role, content, agent_id, agent_name,
115
- run_id, run_status, last_run_event_id, events_json,
116
- attachments_json, session_mode, run_context_json,
117
- telemetry_finalized_at, started_at, ended_at, position, created_at)
118
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(m.id, conversationId, m.role, m.content, m.agentId ?? null, m.agentName ?? null, m.runId ?? null, m.runStatus ?? null, m.lastRunEventId ?? null, m.events ? JSON.stringify(m.events) : null, m.attachments ? JSON.stringify(m.attachments) : null, normalizeMessageSessionModeForStorage(m.sessionMode), m.runContext ? JSON.stringify(m.runContext) : null, m.telemetryFinalized === true ? now : null, m.startedAt ?? null, m.endedAt ?? null, position, createdAt);
119
- }
120
- // Bump conversation activity so recency-sorted listings (e.g. `listConversations`) reflect this write.
121
- db.prepare(`UPDATE conversations SET updated_at = ? WHERE id = ?`).run(now, conversationId);
122
- const row = db
123
- .prepare(`SELECT ${MESSAGE_COLS} FROM messages WHERE id = ? AND conversation_id = ?`)
124
- .get(m.id, conversationId);
125
- return row ? normalizeMessage(row) : null;
126
- });
127
- const saved = runUpsert.immediate();
80
+ const saved = db.transaction(() => writeMessage({ db, conversationId, m })).immediate();
128
81
  if (!saved || !redaction?.secretRedacted)
129
82
  return saved;
130
83
  const signal = { secretRedacted: true, count: redaction.count };
131
84
  options.onSecretRedacted?.(signal);
132
85
  return { ...saved, secretRedaction: signal };
133
86
  }
87
+ /**
88
+ * @internal `upsertMessage`'s transaction body: mismatch check, insert-or-update, conversation
89
+ * `updated_at` bump, and re-fetch. Must run inside the caller's `BEGIN IMMEDIATE` transaction.
90
+ */
91
+ function writeMessage({ db, conversationId, m }) {
92
+ const existing = db
93
+ .prepare(`SELECT conversation_id AS conversationId FROM messages WHERE id = ?`)
94
+ .get(m.id);
95
+ if (existing && existing.conversationId !== conversationId) {
96
+ throw new MessageConversationMismatchError({ messageId: m.id, actualConversationId: existing.conversationId, requestedConversationId: conversationId });
97
+ }
98
+ const now = Date.now();
99
+ if (existing)
100
+ updateMessageRow({ db, conversationId, m, now });
101
+ else
102
+ insertMessageRow({ db, conversationId, m, now });
103
+ // Bump conversation activity so recency-sorted listings (e.g. `listConversations`) reflect this write.
104
+ db.prepare(`UPDATE conversations SET updated_at = ? WHERE id = ?`).run(now, conversationId);
105
+ const row = db
106
+ .prepare(`SELECT ${MESSAGE_COLS} FROM messages WHERE id = ? AND conversation_id = ?`)
107
+ .get(m.id, conversationId);
108
+ return row ? normalizeMessage(row) : null;
109
+ }
110
+ /**
111
+ * @internal Values for the columns both write paths set, in this order: role, content, agent_id,
112
+ * agent_name, run_id, run_status, last_run_event_id, events_json, attachments_json, session_mode,
113
+ * run_context_json. JSON sub-fields are stringified; absent values become null.
114
+ */
115
+ function messageFieldValues(m) {
116
+ return [
117
+ m.role,
118
+ m.content,
119
+ ...[m.agentId, m.agentName, m.runId, m.runStatus, m.lastRunEventId].map(orNull),
120
+ jsonOrNull(m.events),
121
+ jsonOrNull(m.attachments),
122
+ normalizeMessageSessionMode(m.sessionMode) ?? null,
123
+ jsonOrNull(m.runContext),
124
+ ];
125
+ }
126
+ /** @internal Maps undefined/null to null (SQLite-safe) and keeps every other value. */
127
+ function orNull(value) {
128
+ return value ?? null;
129
+ }
130
+ /** @internal Stringifies a truthy JSON sub-field; falsy values are stored as null. */
131
+ function jsonOrNull(value) {
132
+ return value ? JSON.stringify(value) : null;
133
+ }
134
+ /** @internal Rewrites an existing row in place; preserves `telemetry_finalized_at` once set (one-way latch). */
135
+ function updateMessageRow({ db, conversationId, m, now }) {
136
+ db.prepare(`UPDATE messages
137
+ SET role = ?, content = ?, agent_id = ?, agent_name = ?,
138
+ run_id = ?, run_status = ?, last_run_event_id = ?,
139
+ events_json = ?, attachments_json = ?,
140
+ session_mode = ?, run_context_json = ?,
141
+ telemetry_finalized_at = CASE
142
+ WHEN ? THEN COALESCE(telemetry_finalized_at, ?)
143
+ ELSE telemetry_finalized_at
144
+ END,
145
+ started_at = ?, ended_at = ?
146
+ WHERE id = ? AND conversation_id = ?`).run(...messageFieldValues(m), m.telemetryFinalized === true ? 1 : 0, now, orNull(m.startedAt), orNull(m.endedAt), m.id, conversationId);
147
+ }
148
+ /** @internal Inserts a new row at the end of the conversation's position sequence. */
149
+ function insertMessageRow({ db, conversationId, m, now }) {
150
+ const max = db
151
+ .prepare(`SELECT COALESCE(MAX(position), -1) AS m FROM messages WHERE conversation_id = ?`)
152
+ .get(conversationId);
153
+ const position = (max?.m ?? -1) + 1;
154
+ const createdAt = Number.isFinite(m.createdAt) ? m.createdAt : now;
155
+ // 18 values: id, conversation_id, role, content, agent_id, agent_name,
156
+ // run_id, run_status, last_run_event_id, events_json, attachments_json,
157
+ // session_mode, run_context_json, telemetry_finalized_at, started_at,
158
+ // ended_at, position, created_at.
159
+ db.prepare(`INSERT INTO messages
160
+ (id, conversation_id, role, content, agent_id, agent_name,
161
+ run_id, run_status, last_run_event_id, events_json,
162
+ attachments_json, session_mode, run_context_json,
163
+ telemetry_finalized_at, started_at, ended_at, position, created_at)
164
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(m.id, conversationId, ...messageFieldValues(m), m.telemetryFinalized === true ? now : null, orNull(m.startedAt), orNull(m.endedAt), position, createdAt);
165
+ }
134
166
  /**
135
167
  * Reads whether telemetry has been finalized for a message without loading the full row.
136
168
  * Used to decide whether to enqueue a finalization job.
@@ -162,19 +194,14 @@ export function getMessageTelemetryFinalizationState({ db, messageId }) {
162
194
  * @returns The updated events array, or null if the message does not exist or label is empty.
163
195
  */
164
196
  export function appendMessageStatusEvent({ db, messageId, event }) {
165
- const label = typeof event?.label === 'string' ? event.label.trim() : '';
166
- const detail = typeof event?.detail === 'string' ? event.detail.trim() : '';
197
+ const label = trimmedString(event?.label);
198
+ const detail = trimmedString(event?.detail);
167
199
  if (!label)
168
200
  return null;
169
- const row = db
170
- .prepare(`SELECT events_json AS eventsJson FROM messages WHERE id = ?`)
171
- .get(messageId);
172
- if (!row)
201
+ const events = readStoredEvents({ db, messageId });
202
+ if (!events)
173
203
  return null;
174
- const parsed = parseJsonOrUndef(row.eventsJson);
175
- const events = Array.isArray(parsed) ? parsed : [];
176
- const last = events[events.length - 1];
177
- if (last?.kind === 'status' && last.label === label && (last.detail ?? '') === detail) {
204
+ if (isRepeatedStatus(events[events.length - 1], { label, detail })) {
178
205
  return events;
179
206
  }
180
207
  const nextEvent = detail
@@ -185,6 +212,24 @@ export function appendMessageStatusEvent({ db, messageId, event }) {
185
212
  .run(JSON.stringify(next), messageId);
186
213
  return next;
187
214
  }
215
+ /** @internal Trimmed string value, or '' for anything that is not a string. */
216
+ function trimmedString(value) {
217
+ return typeof value === 'string' ? value.trim() : '';
218
+ }
219
+ /** @internal True when `last` is a status event with the same label and (absent-as-empty) detail. */
220
+ function isRepeatedStatus(last, { label, detail }) {
221
+ return last?.kind === 'status' && last.label === label && (last.detail ?? '') === detail;
222
+ }
223
+ /** @internal Reads a message's stored events (non-array JSON reads as []), or null when the message does not exist. */
224
+ function readStoredEvents({ db, messageId }) {
225
+ const row = db
226
+ .prepare(`SELECT events_json AS eventsJson FROM messages WHERE id = ?`)
227
+ .get(messageId);
228
+ if (!row)
229
+ return null;
230
+ const parsed = parseJsonOrUndef(row.eventsJson);
231
+ return Array.isArray(parsed) ? parsed : [];
232
+ }
188
233
  /**
189
234
  * Appends an arbitrary agent event to the message's events array, also streaming `text` deltas
190
235
  * into the `content` column so the message text grows incrementally during a run.
@@ -195,18 +240,12 @@ export function appendMessageStatusEvent({ db, messageId, event }) {
195
240
  * @returns The updated events array, or null if the message does not exist or kind is empty.
196
241
  */
197
242
  export function appendMessageAgentEvent({ db, messageId, event }) {
198
- if (!event || typeof event !== 'object')
199
- return null;
200
- const kind = typeof event.kind === 'string' ? event.kind : '';
243
+ const kind = agentEventKind(event);
201
244
  if (!kind)
202
245
  return null;
203
- const row = db
204
- .prepare(`SELECT content, events_json AS eventsJson FROM messages WHERE id = ?`)
205
- .get(messageId);
206
- if (!row)
246
+ const events = readStoredEvents({ db, messageId });
247
+ if (!events)
207
248
  return null;
208
- const parsed = parseJsonOrUndef(row.eventsJson);
209
- const events = Array.isArray(parsed) ? parsed : [];
210
249
  const last = events[events.length - 1];
211
250
  if (last && JSON.stringify(last) === JSON.stringify(event)) {
212
251
  return events;
@@ -217,6 +256,13 @@ export function appendMessageAgentEvent({ db, messageId, event }) {
217
256
  .run(textDelta, JSON.stringify(next), messageId);
218
257
  return next;
219
258
  }
259
+ /** @internal An agent event's `kind`, or '' when the event is not an object or has no string kind. */
260
+ function agentEventKind(event) {
261
+ if (!event || typeof event !== 'object')
262
+ return '';
263
+ const { kind } = event;
264
+ return typeof kind === 'string' ? kind : '';
265
+ }
220
266
  /**
221
267
  * Hard-deletes a single message row by id.
222
268
  * @param db - Open SQLite connection.
@@ -249,8 +295,4 @@ function normalizeMessage(row) {
249
295
  function normalizeMessageSessionMode(value) {
250
296
  return value === 'chat' || value === 'design' || value === 'plan' ? value : undefined;
251
297
  }
252
- /** @internal Same validation as normalizeMessageSessionMode but returns null (SQLite-safe) for unrecognised values. */
253
- function normalizeMessageSessionModeForStorage(value) {
254
- return value === 'chat' || value === 'design' || value === 'plan' ? value : null;
255
- }
256
298
  //# sourceMappingURL=messages.js.map
@@ -3,6 +3,13 @@ import { createSqliteChatStore } from "../sqlite/index.js";
3
3
  import { createPgliteChatStore } from "../pglite/index.js";
4
4
  import { createPostgresChatStore } from "../postgres/index.js";
5
5
  import { chatKernelTables } from "../sql/boundary.js";
6
+ function isOrphanDaemonAnswer({ previous, answer, isDaemonRunId }, _optional = {}) {
7
+ return previous?.role !== "user" && answer.role === "assistant" && Boolean(answer.runId && isDaemonRunId({ runId: answer.runId }));
8
+ }
9
+ function isEarlierOrEqualQuestion({ question, answer }, _optional = {}) {
10
+ return question.role === "user" && question.createdAt !== undefined && answer.createdAt !== undefined
11
+ && question.createdAt <= answer.createdAt;
12
+ }
6
13
  function repairLegacyTurnOrder({ messages, isDaemonRunId }, _optional = {}) {
7
14
  const ordered = [];
8
15
  for (let index = 0; index < messages.length; index++) {
@@ -11,9 +18,8 @@ function repairLegacyTurnOrder({ messages, isDaemonRunId }, _optional = {}) {
11
18
  // Legacy acceptance saved no question ID. Repair only an orphan daemon answer immediately
12
19
  // followed by an earlier/equal-clock user row; a preceding question already owns its answer.
13
20
  // A global timestamp sort would reorder valid follow-ups when browser/server clocks disagree.
14
- if (ordered.at(-1)?.role !== "user" && answer.role === "assistant" && answer.runId && isDaemonRunId({ runId: answer.runId })
15
- && question?.role === "user" && question.createdAt !== undefined && answer.createdAt !== undefined
16
- && question.createdAt <= answer.createdAt) {
21
+ if (isOrphanDaemonAnswer({ previous: ordered.at(-1), answer, isDaemonRunId }) && question !== undefined
22
+ && isEarlierOrEqualQuestion({ question, answer })) {
17
23
  ordered.push(question, answer);
18
24
  index++;
19
25
  }
@@ -1,13 +1,13 @@
1
1
  import type { RunIdPolicy } from "../../core/durable-runs/ports.js";
2
2
  import { type AgentEvent } from "../../core/index.js";
3
3
  import type { ChatKernel, ChatDatabase } from "./kernel.js";
4
- import type { DurableRun, DurableRunStore } from "../../core/durable-runs/ports.js";
4
+ import type { DurableRunHead, DurableRunStore } from "../../core/durable-runs/ports.js";
5
5
  export declare const RUN_SLOT_BUSY = "Another answer in this chat is still running. This turn is queued in your browser.";
6
6
  export declare class RunSlotBusyError extends Error {
7
7
  constructor();
8
8
  }
9
9
  export declare function attemptCanExecute({ run }: {
10
- run: DurableRun | null;
10
+ run: DurableRunHead | null;
11
11
  }, _optional?: {}): boolean;
12
12
  export declare function savedRunEvents({ json }: {
13
13
  json: string | null;