@genesislcap/ai-assistant 14.479.0 → 14.480.0

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 (63) hide show
  1. package/dist/ai-assistant.api.json +546 -2
  2. package/dist/ai-assistant.d.ts +73 -1
  3. package/dist/dts/components/ai-driver/ai-driver.d.ts +8 -0
  4. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  5. package/dist/dts/components/chat-driver/chat-driver.d.ts +32 -0
  6. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  7. package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +1 -1
  8. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +7 -0
  9. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
  10. package/dist/dts/components/flowing-waves-indicator.d.ts +1 -1
  11. package/dist/dts/components/flowing-waves-indicator.d.ts.map +1 -1
  12. package/dist/dts/components/halo-overlay.d.ts +1 -0
  13. package/dist/dts/components/halo-overlay.d.ts.map +1 -1
  14. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +4 -0
  15. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  16. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  17. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  18. package/dist/dts/components/waves-indicator.d.ts +1 -1
  19. package/dist/dts/components/waves-indicator.d.ts.map +1 -1
  20. package/dist/dts/main/main.d.ts +29 -1
  21. package/dist/dts/main/main.d.ts.map +1 -1
  22. package/dist/dts/main/main.styles.d.ts.map +1 -1
  23. package/dist/dts/main/main.template.d.ts.map +1 -1
  24. package/dist/dts/state/ai-assistant-slice.d.ts +2 -0
  25. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  26. package/dist/dts/state/session-store.d.ts +1 -0
  27. package/dist/dts/state/session-store.d.ts.map +1 -1
  28. package/dist/dts/utils/message-partition.d.ts +1 -0
  29. package/dist/dts/utils/message-partition.d.ts.map +1 -1
  30. package/dist/esm/components/chat-driver/chat-driver.js +88 -8
  31. package/dist/esm/components/chat-driver/chat-driver.test.js +104 -0
  32. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +48 -6
  33. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +62 -0
  34. package/dist/esm/components/flowing-waves-indicator.js +22 -11
  35. package/dist/esm/components/halo-overlay.js +30 -6
  36. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +14 -0
  37. package/dist/esm/components/settings-modal/settings-modal.styles.js +7 -0
  38. package/dist/esm/components/settings-modal/settings-modal.template.js +18 -8
  39. package/dist/esm/components/waves-indicator.js +22 -8
  40. package/dist/esm/main/main.js +73 -6
  41. package/dist/esm/main/main.styles.js +6 -0
  42. package/dist/esm/main/main.template.js +21 -2
  43. package/dist/esm/state/ai-assistant-slice.js +4 -0
  44. package/dist/esm/utils/message-partition.js +10 -1
  45. package/dist/esm/utils/message-partition.test.js +2 -0
  46. package/package.json +17 -17
  47. package/src/components/ai-driver/ai-driver.ts +17 -0
  48. package/src/components/chat-driver/chat-driver.test.ts +143 -0
  49. package/src/components/chat-driver/chat-driver.ts +105 -4
  50. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +81 -0
  51. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +51 -5
  52. package/src/components/flowing-waves-indicator.ts +26 -6
  53. package/src/components/halo-overlay.ts +31 -6
  54. package/src/components/orchestrating-driver/orchestrating-driver.ts +18 -0
  55. package/src/components/settings-modal/settings-modal.styles.ts +7 -0
  56. package/src/components/settings-modal/settings-modal.template.ts +36 -16
  57. package/src/components/waves-indicator.ts +25 -5
  58. package/src/main/main.styles.ts +6 -0
  59. package/src/main/main.template.ts +25 -2
  60. package/src/main/main.ts +65 -1
  61. package/src/state/ai-assistant-slice.ts +5 -0
  62. package/src/utils/message-partition.test.ts +2 -0
  63. package/src/utils/message-partition.ts +11 -1
@@ -89,6 +89,12 @@ const messageType = (m: ChatMessage): string => {
89
89
  // outcome; render it identically to a real user message.
90
90
  case m.role === 'synthetic-user':
91
91
  return 'user';
92
+ // Reasoning (chain-of-thought summary) and narration (interstitial prose the model emits
93
+ // alongside a tool call) both render as the "Thinking" bubble — visually set apart from a real
94
+ // final agent reply, even though they stay separate categories (own log entry + own toggle:
95
+ // `showThinkingSteps` vs `showNarration`). `thinking` is the legacy flag for pre-split sessions.
96
+ case m.category === 'reasoning':
97
+ case m.category === 'narration':
92
98
  case !!m.thinking:
93
99
  return 'ai-thinking';
94
100
  case !!m.toolCalls:
@@ -108,6 +114,14 @@ const senderLabel: Record<string, string> = {
108
114
  ai: 'Assistant',
109
115
  };
110
116
 
117
+ /**
118
+ * Sender label for a message. Reasoning and narration share the "Thinking" bubble, so the label is
119
+ * the only thing that tells them apart: narration reads "Working" (the action it narrates alongside
120
+ * its tool call), reasoning stays "Thinking". Everything else uses the {@link messageType} map.
121
+ */
122
+ const senderLabelFor = (m: ChatMessage): string =>
123
+ m.category === 'narration' ? 'Working' : senderLabel[messageType(m)];
124
+
111
125
  /**
112
126
  * Resolves how the host frames an interaction widget, defaulting to `'label'`
113
127
  * (the historical "Assistant" label, no bubble). Callers guard on
@@ -293,7 +307,10 @@ ${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<stro
293
307
  userAvatarTemplate,
294
308
  )}${when((m) => messageType(m) !== 'user', assistantAvatarTemplate)}
295
309
  </div>
296
- <div class="message ${(m) => messageType(m)}">
310
+ <div
311
+ class="message ${(m) => messageType(m)} ${(m) =>
312
+ classNames(['narration', m.category === 'narration'])}"
313
+ >
297
314
  ${when(
298
315
  // A 'bare' interaction owns its full presentation — suppress the
299
316
  // host sender label. Every other message keeps it.
@@ -305,7 +322,7 @@ ${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<stro
305
322
  m.agentName &&
306
323
  (c.parent as FoundationAiAssistant).showAgentSwitchIndicator
307
324
  ? `Tool Call · ${m.agentLabel ?? m.agentName}`
308
- : senderLabel[messageType(m)]}
325
+ : senderLabelFor(m)}
309
326
  </div>
310
327
  `,
311
328
  )}
@@ -326,6 +343,12 @@ ${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<stro
326
343
  (m) => m.interaction,
327
344
  html<ChatMessage, FoundationAiAssistant>`
328
345
  <ai-chat-interaction-wrapper
346
+ ${
347
+ /* GENC-1410: bind ownerHostId BEFORE componentName/interactionId — bindings
348
+ apply in template order and both trigger renderComponent, so the ownership
349
+ gate prop must be set by the time the first render runs. */ ''
350
+ }
351
+ :ownerHostId=${(m) => m.interaction!.ownerHostId ?? ''}
329
352
  :componentName=${(m) => m.interaction!.componentName}
330
353
  :data=${(m) => m.interaction!.data}
331
354
  :interactionId=${(m) => m.interaction!.interactionId}
package/src/main/main.ts CHANGED
@@ -409,6 +409,14 @@ export class FoundationAiAssistant extends GenesisElement {
409
409
  this._sessionRef?.actions.aiAssistant.setShowThinkingSteps(value);
410
410
  }
411
411
 
412
+ /** Current user-facing toggle state for narration (interstitial prose) visibility. */
413
+ get showNarration(): boolean {
414
+ return this._sessionRef?.store.aiAssistant.showNarration ?? false;
415
+ }
416
+ set showNarration(value: boolean) {
417
+ this._sessionRef?.actions.aiAssistant.setShowNarration(value);
418
+ }
419
+
412
420
  /** Current user-facing toggle state for agent switch indicator visibility. */
413
421
  get showAgentSwitchIndicator(): boolean {
414
422
  return this._sessionRef?.store.aiAssistant.showAgentSwitchIndicator ?? false;
@@ -786,8 +794,18 @@ export class FoundationAiAssistant extends GenesisElement {
786
794
  * to avoid stealing focus from elsewhere on the page.
787
795
  */
788
796
  @observable isEngaged: boolean = true;
797
+ /**
798
+ * GENC-1410 (Error 3): stable id for this assistant view. Reported to the shared driver so it
799
+ * can elect a single owner to render (and run) each interaction's widget when more than one view
800
+ * (e.g. a bubble and a docked panel) is wired to the same driver — so it fires once, not per view.
801
+ */
802
+ private readonly hostId = crypto.randomUUID();
789
803
  private _handleGlobalInteraction = (e: Event) => {
790
804
  const next = e.composedPath().includes(this);
805
+ // GENC-1410: sticky "last active host" — whenever the user interacts inside THIS view, claim
806
+ // ownership on the shared driver. Unlike `isEngaged` this does NOT clear when the user later
807
+ // clicks elsewhere in the layout; it only moves when they use a *different* assistant view.
808
+ if (next) this.driver?.setActiveHost?.(this.hostId);
791
809
  if (next === this.isEngaged) return;
792
810
  this.isEngaged = next;
793
811
  };
@@ -837,7 +855,7 @@ export class FoundationAiAssistant extends GenesisElement {
837
855
  }
838
856
  }
839
857
 
840
- /** True when there is a pending (unresolved) interaction — disables the popout button. */
858
+ /** True when there is a pending (unresolved) interaction. */
841
859
  get hasActivePendingInteraction(): boolean {
842
860
  if (!this.busy) return false;
843
861
  const last = this.messages[this.messages.length - 1];
@@ -892,6 +910,7 @@ export class FoundationAiAssistant extends GenesisElement {
892
910
  return filterVisibleMessages(listed, {
893
911
  showToolCalls: this.showToolCalls,
894
912
  showThinkingSteps: this.showThinkingSteps,
913
+ showNarration: this.showNarration,
895
914
  showAgentSwitchIndicator,
896
915
  });
897
916
  }
@@ -1082,6 +1101,10 @@ export class FoundationAiAssistant extends GenesisElement {
1082
1101
  // Idempotent — unwire previous listeners on this component before re-wiring.
1083
1102
  this.unwireDriver();
1084
1103
 
1104
+ // GENC-1410 (Error 3): announce this view to the (shared) driver so it can elect a single
1105
+ // owner for side-effecting interactions when more than one view is wired to it.
1106
+ driver.registerHost?.(this.hostId);
1107
+
1085
1108
  const onHistoryUpdated = (e: Event) => {
1086
1109
  this.messages = [...(e as CustomEvent<ChatMessage[]>).detail];
1087
1110
  };
@@ -1166,6 +1189,7 @@ export class FoundationAiAssistant extends GenesisElement {
1166
1189
  driver.addEventListener('provider-changed', onProviderChanged);
1167
1190
 
1168
1191
  const cleanups: (() => void)[] = [
1192
+ () => driver.unregisterHost?.(this.hostId),
1169
1193
  () => driver.removeEventListener('history-updated', onHistoryUpdated),
1170
1194
  () => driver.removeEventListener('sub-agent-history-updated', onSubAgentHistoryUpdated),
1171
1195
  () => driver.removeEventListener('sub-agent-start', onSubAgentStart),
@@ -1257,6 +1281,7 @@ export class FoundationAiAssistant extends GenesisElement {
1257
1281
  const ui = this.chatConfig.ui ?? {};
1258
1282
  this.showToolCalls = ui.showToolCalls === true;
1259
1283
  this.showThinkingSteps = ui.showThinkingSteps === true;
1284
+ this.showNarration = ui.showNarration === true;
1260
1285
  this.showAgentSwitchIndicator = ui.showAgentSwitchIndicator === true;
1261
1286
  this.enabledAnimations = resolveExclusiveLoadingStyle(
1262
1287
  (ui.animations?.enabled as AiAssistantAnimation[]) ??
@@ -1288,6 +1313,11 @@ export class FoundationAiAssistant extends GenesisElement {
1288
1313
  this.logMeta('driver.wired', { reason: 'popin' });
1289
1314
  this.wireDriver();
1290
1315
  this._finalizeCostSessionOnPageHide = true;
1316
+ // GENC-1410: popin makes THIS (bubble) view the visible one. Take interaction ownership on
1317
+ // the shared driver so it elects this view to render widgets — not the now-hidden docked
1318
+ // panel. Ownership transfers as part of the dock lifecycle, deterministically, rather than
1319
+ // being inferred from wherever the user last happened to click.
1320
+ this.driver?.setActiveHost?.(this.hostId);
1291
1321
  });
1292
1322
  this.unsubBus = () => {
1293
1323
  unsubPopout();
@@ -1295,6 +1325,11 @@ export class FoundationAiAssistant extends GenesisElement {
1295
1325
  };
1296
1326
  } else if (this.popoutMode === 'collapse') {
1297
1327
  this._finalizeCostSessionOnPageHide = true;
1328
+ // GENC-1410: a collapse-mode view exists only while docked, and it connects (running this
1329
+ // setup) as part of `onDock` — the moment it becomes the visible view. Take interaction
1330
+ // ownership now so the shared driver renders widgets here, not in the hidden bubble. This is
1331
+ // the dock counterpart to the bubble's ownership claim on `chat-popin` above.
1332
+ this.driver?.setActiveHost?.(this.hostId);
1298
1333
  const unsubPopout = agenticActivityBus.subscribe('chat-popout', () => {
1299
1334
  this._finalizeCostSessionOnPageHide = true;
1300
1335
  });
@@ -1595,6 +1630,30 @@ export class FoundationAiAssistant extends GenesisElement {
1595
1630
  }
1596
1631
 
1597
1632
  /** Called when the user clicks the popout button. */
1633
+ /**
1634
+ * GENC-1410 (Error 3): claim the right to run a side-effecting interaction's server work for
1635
+ * THIS view, via the shared driver's first-writer-wins latch. Returns `true` when this view may
1636
+ * run it (first/owning claimant, or a re-render of the same view), `false` when another view
1637
+ * already holds it. Fail-open when the driver has no latch. Called by `ai-chat-interaction-wrapper`.
1638
+ */
1639
+ claimSideEffect(interactionId: string): boolean {
1640
+ return this.driver?.claimSideEffect?.(interactionId, this.hostId) ?? true;
1641
+ }
1642
+
1643
+ /**
1644
+ * GENC-1410 (Error 3): mark THIS view as the interaction owner on the shared driver, so it (not a
1645
+ * sibling view sharing the same driver) is elected to render side-effecting widgets. The bubble
1646
+ * and docked panel normally claim this automatically on the pop-in / dock lifecycle, but that
1647
+ * lifecycle only runs once `popout-mode` is active. A host that establishes a view outside that
1648
+ * lifecycle — e.g. a page that restores a docked assistant from a persisted layout before the
1649
+ * pop-out affordance is enabled — calls this to seize ownership deterministically rather than
1650
+ * leaving it to the last-interaction / first-registered fallback. Idempotent; safe to call on
1651
+ * every layout (re)establish. No-op if the driver was never wired.
1652
+ */
1653
+ claimInteractionOwnership(): void {
1654
+ this.driver?.setActiveHost?.(this.hostId);
1655
+ }
1656
+
1598
1657
  handlePopout(): void {
1599
1658
  if (this.popoutMode === 'expand') {
1600
1659
  this.logMeta('assistant.popout');
@@ -1787,6 +1846,7 @@ export class FoundationAiAssistant extends GenesisElement {
1787
1846
  !!this.headerTitle ||
1788
1847
  ui?.showToolCalls != null ||
1789
1848
  ui?.showThinkingSteps != null ||
1849
+ ui?.showNarration != null ||
1790
1850
  ui?.showAgentSwitchIndicator != null ||
1791
1851
  ui?.allowDebugDownload === true ||
1792
1852
  ui?.animations != null ||
@@ -1985,6 +2045,10 @@ export class FoundationAiAssistant extends GenesisElement {
1985
2045
  this.showThinkingSteps = !this.showThinkingSteps;
1986
2046
  }
1987
2047
 
2048
+ toggleShowNarration() {
2049
+ this.showNarration = !this.showNarration;
2050
+ }
2051
+
1988
2052
  toggleShowAgentSwitchIndicator() {
1989
2053
  this.showAgentSwitchIndicator = !this.showAgentSwitchIndicator;
1990
2054
  }
@@ -34,6 +34,7 @@ export interface AiAssistantSessionState {
34
34
  state: AiAssistantState;
35
35
  showToolCalls: boolean;
36
36
  showThinkingSteps: boolean;
37
+ showNarration: boolean;
37
38
  showAgentSwitchIndicator: boolean;
38
39
  enabledAnimations: AiAssistantAnimation[];
39
40
  suggestionsState: SuggestionsState;
@@ -100,6 +101,7 @@ export const defaultSessionState: AiAssistantSessionState = {
100
101
  state: 'idle',
101
102
  showToolCalls: false,
102
103
  showThinkingSteps: false,
104
+ showNarration: false,
103
105
  showAgentSwitchIndicator: false,
104
106
  enabledAnimations: [],
105
107
  suggestionsState: { status: 'idle' },
@@ -136,6 +138,9 @@ export const aiAssistantSlice = createSlice({
136
138
  setShowThinkingSteps(state, action: PayloadAction<boolean>) {
137
139
  state.showThinkingSteps = action.payload;
138
140
  },
141
+ setShowNarration(state, action: PayloadAction<boolean>) {
142
+ state.showNarration = action.payload;
143
+ },
139
144
  setShowAgentSwitchIndicator(state, action: PayloadAction<boolean>) {
140
145
  state.showAgentSwitchIndicator = action.payload;
141
146
  },
@@ -22,11 +22,13 @@ const interaction = (interactionId: string, resolved = false): ChatInteraction =
22
22
  const ALL_ON: MessageFilterState = {
23
23
  showToolCalls: true,
24
24
  showThinkingSteps: true,
25
+ showNarration: true,
25
26
  showAgentSwitchIndicator: true,
26
27
  };
27
28
  const ALL_OFF: MessageFilterState = {
28
29
  showToolCalls: false,
29
30
  showThinkingSteps: false,
31
+ showNarration: false,
30
32
  showAgentSwitchIndicator: false,
31
33
  };
32
34
 
@@ -9,6 +9,7 @@ import type { ChatMessage } from '@genesislcap/foundation-ai';
9
9
  export interface MessageFilterState {
10
10
  showToolCalls: boolean;
11
11
  showThinkingSteps: boolean;
12
+ showNarration: boolean;
12
13
  showAgentSwitchIndicator: boolean;
13
14
  }
14
15
 
@@ -52,7 +53,16 @@ export function filterVisibleMessages(
52
53
  if (m.role === 'tool') {
53
54
  return false;
54
55
  }
55
- // Thinking messages follow their own toggle.
56
+ // Reasoning (chain-of-thought summary) follows the thinking toggle.
57
+ if (m.category === 'reasoning') {
58
+ return toggles.showThinkingSteps;
59
+ }
60
+ // Narration (interstitial prose alongside a tool call) follows its own toggle.
61
+ if (m.category === 'narration') {
62
+ return toggles.showNarration;
63
+ }
64
+ // Legacy: sessions persisted before the reasoning/narration split tagged reasoning-ish
65
+ // content with `thinking`; keep honouring the flag so their history still hides it.
56
66
  if (m.thinking && !toggles.showThinkingSteps) {
57
67
  return false;
58
68
  }