@memberjunction/ng-conversations 6.2.0-edge.4 → 6.2.0-edge.6

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 (144) hide show
  1. package/README.md +29 -0
  2. package/dist/lib/components/attachment/image-viewer.component.d.ts +3 -1
  3. package/dist/lib/components/attachment/image-viewer.component.d.ts.map +1 -1
  4. package/dist/lib/components/attachment/image-viewer.component.js +5 -1
  5. package/dist/lib/components/attachment/image-viewer.component.js.map +1 -1
  6. package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts +1 -1
  7. package/dist/lib/components/conversation/conversation-agent-picker.component.js +3 -3
  8. package/dist/lib/components/conversation/conversation-agent-picker.component.js.map +1 -1
  9. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +88 -20
  10. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  11. package/dist/lib/components/conversation/conversation-chat-area.component.js +785 -387
  12. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  13. package/dist/lib/components/message/message-input.component.d.ts +60 -20
  14. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  15. package/dist/lib/components/message/message-input.component.js +290 -389
  16. package/dist/lib/components/message/message-input.component.js.map +1 -1
  17. package/dist/lib/components/message/message-item.component.d.ts +153 -17
  18. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  19. package/dist/lib/components/message/message-item.component.js +1400 -299
  20. package/dist/lib/components/message/message-item.component.js.map +1 -1
  21. package/dist/lib/components/message/message-list.component.d.ts +25 -2
  22. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  23. package/dist/lib/components/message/message-list.component.js +79 -40
  24. package/dist/lib/components/message/message-list.component.js.map +1 -1
  25. package/dist/lib/components/overlay/chat-overlay.component.d.ts +19 -0
  26. package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
  27. package/dist/lib/components/overlay/chat-overlay.component.js +125 -13
  28. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  29. package/dist/lib/components/project/project-form-modal.component.js +2 -2
  30. package/dist/lib/components/project/project-form-modal.component.js.map +1 -1
  31. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +6 -1
  32. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  33. package/dist/lib/components/realtime/realtime-agent-banner.component.js +110 -82
  34. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  35. package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts +13 -0
  36. package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts.map +1 -0
  37. package/dist/lib/components/realtime/realtime-call-failure-notice.js +21 -0
  38. package/dist/lib/components/realtime/realtime-call-failure-notice.js.map +1 -0
  39. package/dist/lib/components/realtime/realtime-composer.component.js +114 -102
  40. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  41. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +21 -2
  42. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  43. package/dist/lib/components/realtime/realtime-session-overlay.component.js +113 -76
  44. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  45. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  46. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +7 -1
  47. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  48. package/dist/lib/components/sidebar/routines-section.component.js +1 -1
  49. package/dist/lib/components/sidebar/routines-section.component.js.map +1 -1
  50. package/dist/lib/components/task/tasks-full-view.component.d.ts +152 -31
  51. package/dist/lib/components/task/tasks-full-view.component.d.ts.map +1 -1
  52. package/dist/lib/components/task/tasks-full-view.component.js +2227 -214
  53. package/dist/lib/components/task/tasks-full-view.component.js.map +1 -1
  54. package/dist/lib/components/tasks/tasks-dropdown.component.js +4 -4
  55. package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
  56. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +11 -1
  57. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  58. package/dist/lib/components/workspace/conversation-workspace.component.js +25 -28
  59. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  60. package/dist/lib/services/agent-state.service.d.ts.map +1 -1
  61. package/dist/lib/services/agent-state.service.js +8 -8
  62. package/dist/lib/services/agent-state.service.js.map +1 -1
  63. package/dist/lib/services/browser-realtime-media-host.d.ts +7 -7
  64. package/dist/lib/services/browser-realtime-media-host.js +8 -78
  65. package/dist/lib/services/browser-realtime-media-host.js.map +1 -1
  66. package/dist/lib/services/conversation-agent.service.d.ts +53 -20
  67. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  68. package/dist/lib/services/conversation-agent.service.js +108 -54
  69. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  70. package/dist/lib/services/conversation-detail-window.store.d.ts +5 -0
  71. package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
  72. package/dist/lib/services/conversation-detail-window.store.js +52 -5
  73. package/dist/lib/services/conversation-detail-window.store.js.map +1 -1
  74. package/dist/lib/services/conversation-naming.d.ts.map +1 -1
  75. package/dist/lib/services/conversation-naming.js +9 -2
  76. package/dist/lib/services/conversation-naming.js.map +1 -1
  77. package/dist/lib/services/conversation-tasks-filter.d.ts +15 -0
  78. package/dist/lib/services/conversation-tasks-filter.d.ts.map +1 -0
  79. package/dist/lib/services/conversation-tasks-filter.js +69 -0
  80. package/dist/lib/services/conversation-tasks-filter.js.map +1 -0
  81. package/dist/lib/services/realtime-audio-recorder.d.ts +94 -86
  82. package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -1
  83. package/dist/lib/services/realtime-audio-recorder.js +222 -195
  84. package/dist/lib/services/realtime-audio-recorder.js.map +1 -1
  85. package/dist/lib/services/realtime-session.service.d.ts +30 -2
  86. package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
  87. package/dist/lib/services/realtime-session.service.js +35 -7
  88. package/dist/lib/services/realtime-session.service.js.map +1 -1
  89. package/dist/lib/services/recording/libopus-wasm-binary.d.ts +3 -0
  90. package/dist/lib/services/recording/libopus-wasm-binary.d.ts.map +1 -0
  91. package/dist/lib/services/recording/libopus-wasm-binary.js +489 -0
  92. package/dist/lib/services/recording/libopus-wasm-binary.js.map +1 -0
  93. package/dist/lib/services/recording/libopus-worker-source.d.ts +48 -0
  94. package/dist/lib/services/recording/libopus-worker-source.d.ts.map +1 -0
  95. package/dist/lib/services/recording/libopus-worker-source.js +170 -0
  96. package/dist/lib/services/recording/libopus-worker-source.js.map +1 -0
  97. package/dist/lib/services/recording/linear-resampler.d.ts +45 -0
  98. package/dist/lib/services/recording/linear-resampler.d.ts.map +1 -0
  99. package/dist/lib/services/recording/linear-resampler.js +85 -0
  100. package/dist/lib/services/recording/linear-resampler.js.map +1 -0
  101. package/dist/lib/services/recording/opus-packet-encoder.d.ts +50 -0
  102. package/dist/lib/services/recording/opus-packet-encoder.d.ts.map +1 -0
  103. package/dist/lib/services/recording/opus-packet-encoder.js +41 -0
  104. package/dist/lib/services/recording/opus-packet-encoder.js.map +1 -0
  105. package/dist/lib/services/recording/wasm-opus-encoder.d.ts +86 -0
  106. package/dist/lib/services/recording/wasm-opus-encoder.d.ts.map +1 -0
  107. package/dist/lib/services/recording/wasm-opus-encoder.js +232 -0
  108. package/dist/lib/services/recording/wasm-opus-encoder.js.map +1 -0
  109. package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts +39 -0
  110. package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts.map +1 -0
  111. package/dist/lib/services/recording/webcodecs-opus-encoder.js +101 -0
  112. package/dist/lib/services/recording/webcodecs-opus-encoder.js.map +1 -0
  113. package/dist/lib/services/search.service.d.ts.map +1 -1
  114. package/dist/lib/services/search.service.js +3 -16
  115. package/dist/lib/services/search.service.js.map +1 -1
  116. package/dist/lib/utils/agent-failure-display.d.ts +14 -0
  117. package/dist/lib/utils/agent-failure-display.d.ts.map +1 -0
  118. package/dist/lib/utils/agent-failure-display.js +26 -0
  119. package/dist/lib/utils/agent-failure-display.js.map +1 -0
  120. package/dist/lib/utils/distinct-artifacts.d.ts +9 -3
  121. package/dist/lib/utils/distinct-artifacts.d.ts.map +1 -1
  122. package/dist/lib/utils/distinct-artifacts.js +24 -5
  123. package/dist/lib/utils/distinct-artifacts.js.map +1 -1
  124. package/dist/lib/utils/message-list-order.d.ts +13 -0
  125. package/dist/lib/utils/message-list-order.d.ts.map +1 -0
  126. package/dist/lib/utils/message-list-order.js +31 -0
  127. package/dist/lib/utils/message-list-order.js.map +1 -0
  128. package/dist/lib/utils/pin-state.d.ts +11 -0
  129. package/dist/lib/utils/pin-state.d.ts.map +1 -0
  130. package/dist/lib/utils/pin-state.js +9 -0
  131. package/dist/lib/utils/pin-state.js.map +1 -0
  132. package/dist/public-api.d.ts +1 -0
  133. package/dist/public-api.d.ts.map +1 -1
  134. package/dist/public-api.js +1 -0
  135. package/dist/public-api.js.map +1 -1
  136. package/package.json +36 -36
  137. package/dist/lib/services/browser-recording-unload-guard.d.ts +0 -30
  138. package/dist/lib/services/browser-recording-unload-guard.d.ts.map +0 -1
  139. package/dist/lib/services/browser-recording-unload-guard.js +0 -43
  140. package/dist/lib/services/browser-recording-unload-guard.js.map +0 -1
  141. package/dist/lib/services/realtime-pcm-wav.d.ts +0 -79
  142. package/dist/lib/services/realtime-pcm-wav.d.ts.map +0 -1
  143. package/dist/lib/services/realtime-pcm-wav.js +0 -213
  144. package/dist/lib/services/realtime-pcm-wav.js.map +0 -1
@@ -1,12 +1,12 @@
1
1
  import { Injectable } from '@angular/core';
2
- import { Metadata, RunView } from '@memberjunction/core';
2
+ import { LogStatusEx, Metadata, RunView } from '@memberjunction/core';
3
3
  import { GraphQLAIClient } from '@memberjunction/graphql-dataprovider';
4
4
  import { coerceFailedExecuteAgentResult } from '@memberjunction/ai-core-plus';
5
5
  import { AIEngineBase } from '@memberjunction/ai-engine-base';
6
6
  import { ConversationEngine } from '@memberjunction/core-entities';
7
7
  import { MJNotificationService } from '@memberjunction/ng-notifications';
8
8
  import { ConversationsRuntime } from '@memberjunction/conversations-runtime';
9
- import { UUIDsEqual } from '@memberjunction/global';
9
+ import { EscapeSQLString, UUIDsEqual } from '@memberjunction/global';
10
10
  import { GroupVersionsByArtifact } from '../utils/agent-artifact-summary';
11
11
  import * as i0 from "@angular/core";
12
12
  import * as i1 from "./conversations-runtime-bootstrap.service";
@@ -115,6 +115,11 @@ export class ConversationAgentService {
115
115
  if (this._conversationManagerAgent) {
116
116
  return this._conversationManagerAgent;
117
117
  }
118
+ // A user who may not read the agents has no manager agent to resolve: say nothing, rather than an error for a state
119
+ // that is theirs by design (MJ#5240)
120
+ if (AIEngineBase.Instance.IsPermissionConstrained && AIEngineBase.Instance.ReadableAgents.length === 0) {
121
+ return null;
122
+ }
118
123
  try {
119
124
  const provider = this.Provider;
120
125
  const agent = await ConversationsRuntime.Instance.DefaultAgent.resolve({
@@ -158,17 +163,19 @@ export class ConversationAgentService {
158
163
  * Process a message through the conversation manager agent. Delegates to
159
164
  * `ConversationsRuntime.Instance.AgentRunner.processMessage(...)`.
160
165
  *
161
- * Signature is preserved verbatim from the original service so existing
162
- * call sites continue to compile. The unused `conversationHistory`
163
- * parameter is kept for backwards compatibility (it was already
164
- * documented as "kept for backwards compatibility but not used" in the
165
- * original).
166
+ * The unused `conversationHistory` parameter is kept for backwards compatibility (it was already
167
+ * documented as "kept for backwards compatibility but not used" in the original).
168
+ *
169
+ * @param onReplyAccepted Called with the reply row the server creates for the turn (only the server
170
+ * writes agent replies), before any progress: key the turn's task and completion by its ID.
166
171
  */
167
- async ProcessMessage(conversationId, message, conversationHistory, conversationDetailId, onProgress, appContext, planMode, requestedSkillIDs,
172
+ async ProcessMessage(conversationId, message, conversationHistory, onReplyAccepted, onProgress, appContext, planMode, requestedSkillIDs,
168
173
  /** The agents the conversation manager may delegate to. Null or omitted allows every agent. */
169
174
  allowedAgentIDs,
170
175
  /** The first moment of the conversation the run may read. Null or omitted reads all of it. */
171
- agentHistoryFrom) {
176
+ agentHistoryFrom,
177
+ /** The branch the turn runs on when it forks off an earlier message on another branch. */
178
+ replyBranchId) {
172
179
  // Warm the cached default-agent name for any synchronous consumers
173
180
  // before the runtime resolves on its own.
174
181
  if (!this._conversationManagerAgent) {
@@ -177,18 +184,19 @@ export class ConversationAgentService {
177
184
  return ConversationsRuntime.Instance.AgentRunner.processMessage({
178
185
  conversationId,
179
186
  message,
180
- conversationDetailId,
187
+ onReplyAccepted,
181
188
  appContext,
182
189
  onProgress,
183
190
  ...(planMode ? { planMode: true } : {}),
184
191
  ...(requestedSkillIDs?.length ? { requestedSkillIDs } : {}),
185
192
  ...(allowedAgentIDs != null ? { AllowedAgentIDs: allowedAgentIDs } : {}),
186
193
  ...(agentHistoryFrom ? { AgentHistoryFrom: agentHistoryFrom } : {}),
194
+ ...(replyBranchId ? { ReplyBranchID: replyBranchId } : {}),
187
195
  });
188
196
  }
189
197
  /** @deprecated Use {@link ProcessMessage}. */
190
- async processMessage(conversationId, message, conversationHistory, conversationDetailId, onProgress, appContext, planMode, requestedSkillIDs, allowedAgentIDs, agentHistoryFrom) {
191
- return this.ProcessMessage(conversationId, message, conversationHistory, conversationDetailId, onProgress, appContext, planMode, requestedSkillIDs, allowedAgentIDs, agentHistoryFrom);
198
+ async processMessage(conversationId, message, conversationHistory, onReplyAccepted, onProgress, appContext, planMode, requestedSkillIDs, allowedAgentIDs, agentHistoryFrom, replyBranchId) {
199
+ return this.ProcessMessage(conversationId, message, conversationHistory, onReplyAccepted, onProgress, appContext, planMode, requestedSkillIDs, allowedAgentIDs, agentHistoryFrom, replyBranchId);
192
200
  }
193
201
  /**
194
202
  * Configuration preset an agent was pinned to by a previous `@mention` in this conversation.
@@ -232,7 +240,7 @@ export class ConversationAgentService {
232
240
  for (const row of result.Results || []) {
233
241
  const configurationId = this.readAgentConfigFromMention(row.Message, agentId);
234
242
  if (configurationId) {
235
- console.log(`\u{1F3AF} Found configuration preset from @mention: ${configurationId}`);
243
+ LogStatusEx({ message: `\u{1F3AF} Found configuration preset from @mention: ${configurationId}`, verboseOnly: true });
236
244
  return configurationId;
237
245
  }
238
246
  }
@@ -243,7 +251,7 @@ export class ConversationAgentService {
243
251
  if (!message) {
244
252
  return null;
245
253
  }
246
- const parsed = this.mentionParser.parseMentions(message, AIEngineBase.Instance.Agents, []);
254
+ const parsed = this.mentionParser.parseMentions(message, AIEngineBase.Instance.ReadableAgents, []);
247
255
  const mention = parsed.agentMention;
248
256
  return mention?.id === agentId && mention.configurationId ? mention.configurationId : null;
249
257
  }
@@ -280,15 +288,20 @@ export class ConversationAgentService {
280
288
  *
281
289
  * Stays on the Angular shim because it directly uses AgentClientService.
282
290
  * Could move to the runtime in a follow-up if needed.
291
+ *
292
+ * @param onReplyAccepted Called with the reply row the server creates for the run (only the server
293
+ * writes agent replies), before any progress: key the run's task and completion by its ID.
283
294
  */
284
- async InvokeSubAgent(agentName, conversationId, message, conversationHistory, reasoning, conversationDetailId, payload, onProgress, sourceArtifactId, sourceArtifactVersionId, agentConfigurationPresetId, appContext, planMode, requestedSkillIDs,
295
+ async InvokeSubAgent(agentName, conversationId, message, conversationHistory, reasoning, onReplyAccepted, payload, onProgress, sourceArtifactId, sourceArtifactVersionId, agentConfigurationPresetId, appContext, planMode, requestedSkillIDs,
285
296
  /** The first moment of the conversation the run may read. Null or omitted reads all of it. */
286
- agentHistoryFrom) {
297
+ agentHistoryFrom,
298
+ /** Where the reply sits in the conversation. Omitted, it answers `message` directly. */
299
+ threading) {
287
300
  try {
288
301
  // Ensure AIEngineBase is configured
289
302
  await AIEngineBase.Instance.Config(false);
290
303
  // Find the agent by name
291
- const agent = AIEngineBase.Instance.Agents.find(a => a.Name === agentName);
304
+ const agent = AIEngineBase.Instance.ReadableAgents.find(a => a.Name === agentName);
292
305
  if (!agent || !agent.ID) {
293
306
  const errorMsg = `Sub-agent "${agentName}" not found`;
294
307
  console.warn(`${errorMsg}`);
@@ -298,7 +311,11 @@ export class ConversationAgentService {
298
311
  if (requestedSkillIDs?.length) {
299
312
  this.warnOnUnacceptedSkills(agent, requestedSkillIDs);
300
313
  }
301
- console.log(`Invoking sub-agent: ${agentName}`, { reasoning, hasPayload: !!payload, hasConfigPreset: !!agentConfigurationPresetId });
314
+ LogStatusEx({
315
+ message: `Invoking sub-agent: ${agentName}`,
316
+ verboseOnly: true,
317
+ additionalArgs: [{ reasoning, hasPayload: !!payload, hasConfigPreset: !!agentConfigurationPresetId }]
318
+ });
302
319
  // Map AIAgentConfiguration preset ID to actual AIConfiguration ID
303
320
  let aiConfigurationId = undefined;
304
321
  if (agentConfigurationPresetId) {
@@ -306,14 +323,18 @@ export class ConversationAgentService {
306
323
  const preset = presets.find(p => UUIDsEqual(p.ID, agentConfigurationPresetId) || UUIDsEqual(p.AIConfigurationID, agentConfigurationPresetId));
307
324
  if (preset) {
308
325
  aiConfigurationId = preset.AIConfigurationID || undefined;
309
- console.log(`Mapped agent configuration preset "${preset.Name}" to AIConfigurationID: ${aiConfigurationId || 'default'}`);
326
+ LogStatusEx({ message: `Mapped agent configuration preset "${preset.Name}" to AIConfigurationID: ${aiConfigurationId || 'default'}`, verboseOnly: true });
310
327
  }
311
328
  else {
312
329
  console.warn(`Agent configuration preset ${agentConfigurationPresetId} not found for agent ${agent.ID}`);
313
330
  }
314
331
  }
315
332
  const agentParams = {
316
- ConversationDetailId: conversationDetailId,
333
+ UserMessageId: message.ID,
334
+ OnAccepted: onReplyAccepted,
335
+ ...(threading?.ReplyParentId ? { ReplyParentId: threading.ReplyParentId } : {}),
336
+ ...(threading?.AnnouncedByAgentId ? { AnnouncedByAgentId: threading.AnnouncedByAgentId } : {}),
337
+ ...(threading?.ReplyBranchId ? { ReplyBranchId: threading.ReplyBranchId } : {}),
317
338
  AgentId: agent.ID,
318
339
  MaxHistoryMessages: 20,
319
340
  Data: {
@@ -358,8 +379,8 @@ export class ConversationAgentService {
358
379
  }
359
380
  }
360
381
  /** @deprecated Use {@link InvokeSubAgent}. */
361
- async invokeSubAgent(agentName, conversationId, message, conversationHistory, reasoning, conversationDetailId, payload, onProgress, sourceArtifactId, sourceArtifactVersionId, agentConfigurationPresetId, appContext, planMode, requestedSkillIDs, agentHistoryFrom) {
362
- return this.InvokeSubAgent(agentName, conversationId, message, conversationHistory, reasoning, conversationDetailId, payload, onProgress, sourceArtifactId, sourceArtifactVersionId, agentConfigurationPresetId, appContext, planMode, requestedSkillIDs, agentHistoryFrom);
382
+ async invokeSubAgent(agentName, conversationId, message, conversationHistory, reasoning, onReplyAccepted, payload, onProgress, sourceArtifactId, sourceArtifactVersionId, agentConfigurationPresetId, appContext, planMode, requestedSkillIDs, agentHistoryFrom, threading) {
383
+ return this.InvokeSubAgent(agentName, conversationId, message, conversationHistory, reasoning, onReplyAccepted, payload, onProgress, sourceArtifactId, sourceArtifactVersionId, agentConfigurationPresetId, appContext, planMode, requestedSkillIDs, agentHistoryFrom, threading);
363
384
  }
364
385
  /**
365
386
  * Check if user's latest message should continue with the previous agent or route through Sage.
@@ -383,7 +404,7 @@ export class ConversationAgentService {
383
404
  console.warn('⚠️ Check Sage Intent prompt not found, defaulting to UNSURE');
384
405
  return { decision: 'UNSURE', reasoning: 'Check Sage Intent prompt not found' };
385
406
  }
386
- const agent = AIEngineBase.Instance.Agents.find(a => UUIDsEqual(a.ID, agentId));
407
+ const agent = AIEngineBase.Instance.ReadableAgents.find(a => UUIDsEqual(a.ID, agentId));
387
408
  if (!agent) {
388
409
  console.warn('⚠️ Previous agent not found, defaulting to UNSURE');
389
410
  return { decision: 'UNSURE', reasoning: 'Previous agent not found' };
@@ -419,10 +440,14 @@ export class ConversationAgentService {
419
440
  ${compactHistory}${artifactContext}
420
441
 
421
442
  **Latest User Message**: "${latestMessage}"`;
422
- console.log('🔍 Checking agent continuity intent...', {
423
- agentName: agent.Name,
424
- messagePreview: latestMessage.substring(0, 50),
425
- artifactCount: agentArtifacts.length
443
+ LogStatusEx({
444
+ message: '🔍 Checking agent continuity intent...',
445
+ verboseOnly: true,
446
+ additionalArgs: [{
447
+ agentName: agent.Name,
448
+ messagePreview: latestMessage.substring(0, 50),
449
+ artifactCount: agentArtifacts.length
450
+ }]
426
451
  });
427
452
  const result = await this._aiClient.RunAIPrompt({
428
453
  promptId: prompt.ID,
@@ -439,10 +464,14 @@ ${compactHistory}${artifactContext}
439
464
  const decision = parsed.continuesWith.toUpperCase();
440
465
  const reasoning = parsed.reasoning || 'No reasoning provided';
441
466
  const targetArtifactVersionId = parsed.targetArtifactVersionId || undefined;
442
- console.log(`✅ Intent check result: ${decision}`, {
443
- reasoning,
444
- targetArtifactVersionId,
445
- latency: result.executionTimeMs || 'unknown'
467
+ LogStatusEx({
468
+ message: `✅ Intent check result: ${decision}`,
469
+ verboseOnly: true,
470
+ additionalArgs: [{
471
+ reasoning,
472
+ targetArtifactVersionId,
473
+ latency: result.executionTimeMs || 'unknown'
474
+ }]
446
475
  });
447
476
  if (decision === 'YES' || decision === 'NO' || decision === 'UNSURE') {
448
477
  return {
@@ -510,21 +539,21 @@ ${compactHistory}${artifactContext}
510
539
  * plausible, and wrong: the same failure class as {@link FindLatestAgentOutputVersion},
511
540
  * closed the same way.
512
541
  *
513
- * Two reads, not the four the old shape would have needed — see
542
+ * Three reads: the agent's replies, their Output versions, and the artifacts' names. See
514
543
  * {@link AgentArtifactSummary} for why dropping `runId` removes the join back to agent runs.
515
544
  *
516
545
  * @param historyFrom The chat's history floor, when set (see {@link FindAgentArtifacts}).
517
546
  * @param branchId The branch whose path to read; null or omitted reads the trunk.
518
547
  */
519
548
  async findAllAgentArtifacts(conversationId, agentId, historyFrom, branchId) {
520
- const pathFilter = await this.branchPathFilter(conversationId, branchId);
521
- if (pathFilter == null) {
549
+ const versionFilter = await this.agentOutputVersionFilter(conversationId, agentId, historyFrom, branchId);
550
+ if (!versionFilter) {
522
551
  return []; // the classifier degrades to no artifact context
523
552
  }
524
553
  const rv = RunView.FromMetadataProvider(this.Provider);
525
554
  const versionResult = await rv.RunView({
526
555
  EntityName: 'MJ: Artifact Versions',
527
- ExtraFilter: this.agentOutputVersionFilter(pathFilter, agentId, historyFrom),
556
+ ExtraFilter: versionFilter,
528
557
  OrderBy: '__mj_CreatedAt DESC',
529
558
  MaxRows: MAX_AGENT_ARTIFACT_VERSIONS,
530
559
  Fields: ['ID', 'ArtifactID', 'VersionNumber', 'Name'],
@@ -566,7 +595,7 @@ ${compactHistory}${artifactContext}
566
595
  * again" exchanges where payload continuity matters most. A null payload is a LEGAL agent
567
596
  * input, so the failure is silent: the agent regenerates from scratch instead of modifying.
568
597
  *
569
- * One round trip. The subquery walks details -> Output junctions, and versions carry
598
+ * Two reads: the agent's replies, then their newest Output version. Versions carry
570
599
  * `__mj_CreatedAt`, so "newest" is expressible without joining back for `Sequence`.
571
600
  *
572
601
  * @param historyFrom The chat's history floor. When set, only artifacts on replies written at
@@ -576,35 +605,60 @@ ${compactHistory}${artifactContext}
576
605
  * another branch is never the latest here; null or omitted reads the trunk.
577
606
  */
578
607
  async FindLatestAgentOutputVersion(conversationId, agentId, historyFrom, branchId) {
579
- const pathFilter = await this.branchPathFilter(conversationId, branchId);
580
- if (pathFilter == null) {
608
+ const versionFilter = await this.agentOutputVersionFilter(conversationId, agentId, historyFrom, branchId);
609
+ return versionFilter ? this.runPayloadSourceQuery(versionFilter, '__mj_CreatedAt DESC') : null;
610
+ }
611
+ /**
612
+ * `ArtifactVersion.ID IN (...)` for every OUTPUT artifact an agent produced on one
613
+ * conversation path, in messages no rerun replaced, or null when the agent has no such reply
614
+ * there. Shared by {@link FindLatestAgentOutputVersion} and {@link findAllAgentArtifacts} so
615
+ * the two can never disagree about what "this agent's artifacts" means.
616
+ *
617
+ * The replies are read with their own RunView ({@link agentReplyIds}), so the row-level
618
+ * security on conversation messages applies. A subquery over their view would skip it, and the
619
+ * server refuses one for a user whose read of messages is row-filtered.
620
+ */
621
+ async agentOutputVersionFilter(conversationId, agentId, historyFrom, branchId) {
622
+ const replyIds = await this.agentReplyIds(conversationId, agentId, historyFrom, branchId);
623
+ if (replyIds.length === 0) {
581
624
  return null;
582
625
  }
583
- return this.runPayloadSourceQuery(this.agentOutputVersionFilter(pathFilter, agentId, historyFrom), '__mj_CreatedAt DESC');
626
+ const inReplies = replyIds.map(id => `'${EscapeSQLString(id)}'`).join(',');
627
+ return `ID IN (
628
+ SELECT ArtifactVersionID FROM [__mj].[vwConversationDetailArtifacts]
629
+ WHERE Direction='Output' AND ConversationDetailID IN (${inReplies})
630
+ )`;
584
631
  }
585
632
  /**
586
- * `ArtifactVersion.ID IN (...)` for every OUTPUT artifact an agent produced on one
587
- * conversation path, in messages no rerun replaced. Shared by {@link FindLatestAgentOutputVersion} and
588
- * {@link findAllAgentArtifacts} so the two can never disagree about what "this agent's
589
- * artifacts" means.
633
+ * IDs of the agent's replies on one conversation path, in messages no rerun replaced, written
634
+ * at or after the history floor when one is set. Empty when the path cannot be built
635
+ * ({@link branchPathFilter}) or the read fails.
590
636
  *
591
637
  * Filters on the DETAIL's `Status <> 'Error'` rather than the agent run's
592
638
  * `Status = 'Completed'` (which is what the array scan this replaced used). Doing it on the
593
639
  * run would mean joining back to `MJ: AI Agent Runs` purely to restate a condition the
594
640
  * detail already carries.
595
- *
596
- * @param pathFilter The conversation-and-branch predicate from {@link branchPathFilter}.
597
641
  */
598
- agentOutputVersionFilter(pathFilter, agentId, historyFrom) {
642
+ async agentReplyIds(conversationId, agentId, historyFrom, branchId) {
643
+ const pathFilter = await this.branchPathFilter(conversationId, branchId);
644
+ if (pathFilter == null) {
645
+ return [];
646
+ }
599
647
  const floor = historyFrom ? ` AND ${ConversationEngine.HistoryFromFilter(historyFrom)}` : '';
600
- return `ID IN (
601
- SELECT ArtifactVersionID FROM [__mj].[vwConversationDetailArtifacts]
602
- WHERE Direction='Output' AND ConversationDetailID IN (
603
- SELECT ID FROM [__mj].[vwConversationDetails]
604
- WHERE ${ConversationEngine.LiveRowsFilter(pathFilter)} AND AgentID='${agentId}'
605
- AND Role='AI' AND Status <> 'Error'${floor}
606
- )
607
- )`;
648
+ const rv = RunView.FromMetadataProvider(this.Provider);
649
+ const result = await rv.RunView({
650
+ EntityName: 'MJ: Conversation Details',
651
+ ExtraFilter: `${ConversationEngine.LiveRowsFilter(pathFilter)} AND AgentID='${EscapeSQLString(agentId)}'` +
652
+ ` AND Role='AI' AND Status <> 'Error'${floor}`,
653
+ Fields: ['ID'],
654
+ ResultType: 'simple',
655
+ IgnoreMaxRows: true
656
+ }, this.Provider.CurrentUser ?? undefined);
657
+ if (!result.Success) {
658
+ console.error('Failed to load agent replies:', result.ErrorMessage);
659
+ return [];
660
+ }
661
+ return (result.Results || []).map(reply => reply.ID);
608
662
  }
609
663
  /**
610
664
  * The SQL predicate for one branch's path in a conversation, from