@genesislcap/ai-assistant 14.420.0 → 14.421.1

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 (105) hide show
  1. package/dist/ai-assistant.api.json +4061 -1416
  2. package/dist/ai-assistant.d.ts +594 -81
  3. package/dist/dts/channel/ai-activity-channel.d.ts +4 -22
  4. package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
  5. package/dist/dts/components/ai-driver/ai-driver.d.ts +52 -0
  6. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -0
  7. package/dist/dts/components/ai-driver/index.d.ts +2 -0
  8. package/dist/dts/components/ai-driver/index.d.ts.map +1 -0
  9. package/dist/dts/components/chat-driver/chat-driver.d.ts +63 -8
  10. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  11. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +3 -3
  12. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
  13. package/dist/dts/components/chat-markdown/chat-markdown.d.ts +1 -1
  14. package/dist/dts/components/chat-markdown/chat-markdown.d.ts.map +1 -1
  15. package/dist/dts/components/halo-overlay.d.ts +13 -1
  16. package/dist/dts/components/halo-overlay.d.ts.map +1 -1
  17. package/dist/dts/components/orchestrating-driver/index.d.ts +2 -0
  18. package/dist/dts/components/orchestrating-driver/index.d.ts.map +1 -0
  19. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +39 -0
  20. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -0
  21. package/dist/dts/components/popout-manager/index.d.ts +2 -0
  22. package/dist/dts/components/popout-manager/index.d.ts.map +1 -0
  23. package/dist/dts/components/popout-manager/popout-manager.d.ts +72 -0
  24. package/dist/dts/components/popout-manager/popout-manager.d.ts.map +1 -0
  25. package/dist/dts/config/config.d.ts +43 -15
  26. package/dist/dts/config/config.d.ts.map +1 -1
  27. package/dist/dts/config/fallback-agents.d.ts +20 -0
  28. package/dist/dts/config/fallback-agents.d.ts.map +1 -0
  29. package/dist/dts/config/index.d.ts +1 -0
  30. package/dist/dts/config/index.d.ts.map +1 -1
  31. package/dist/dts/index.d.ts +6 -0
  32. package/dist/dts/index.d.ts.map +1 -1
  33. package/dist/dts/main/main.d.ts +122 -21
  34. package/dist/dts/main/main.d.ts.map +1 -1
  35. package/dist/dts/main/main.styles.d.ts.map +1 -1
  36. package/dist/dts/main/main.template.d.ts.map +1 -1
  37. package/dist/dts/main/main.types.d.ts +16 -0
  38. package/dist/dts/main/main.types.d.ts.map +1 -1
  39. package/dist/dts/state/ai-assistant-slice.d.ts +38 -0
  40. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -0
  41. package/dist/dts/state/driver-registry.d.ts +22 -0
  42. package/dist/dts/state/driver-registry.d.ts.map +1 -0
  43. package/dist/dts/state/session-store.d.ts +37 -0
  44. package/dist/dts/state/session-store.d.ts.map +1 -0
  45. package/dist/dts/suggestions/chat-suggestions.d.ts +7 -0
  46. package/dist/dts/suggestions/chat-suggestions.d.ts.map +1 -0
  47. package/dist/dts/types/ai-chat-widget.d.ts +3 -2
  48. package/dist/dts/types/ai-chat-widget.d.ts.map +1 -1
  49. package/dist/dts/utils/index.d.ts +1 -0
  50. package/dist/dts/utils/index.d.ts.map +1 -1
  51. package/dist/dts/utils/tool-fold.d.ts +133 -0
  52. package/dist/dts/utils/tool-fold.d.ts.map +1 -0
  53. package/dist/esm/components/ai-driver/ai-driver.js +1 -0
  54. package/dist/esm/components/ai-driver/index.js +1 -0
  55. package/dist/esm/components/chat-driver/chat-driver.js +499 -67
  56. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +2 -2
  57. package/dist/esm/components/chat-markdown/chat-markdown.js +1 -1
  58. package/dist/esm/components/halo-overlay.js +53 -7
  59. package/dist/esm/components/orchestrating-driver/index.js +1 -0
  60. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +247 -0
  61. package/dist/esm/components/popout-manager/index.js +1 -0
  62. package/dist/esm/components/popout-manager/popout-manager.js +126 -0
  63. package/dist/esm/config/fallback-agents.js +26 -0
  64. package/dist/esm/config/index.js +1 -0
  65. package/dist/esm/index.js +6 -0
  66. package/dist/esm/main/main.js +546 -112
  67. package/dist/esm/main/main.styles.js +200 -4
  68. package/dist/esm/main/main.template.js +163 -63
  69. package/dist/esm/state/ai-assistant-slice.js +54 -0
  70. package/dist/esm/state/driver-registry.js +46 -0
  71. package/dist/esm/state/session-store.js +39 -0
  72. package/dist/esm/suggestions/chat-suggestions.js +147 -0
  73. package/dist/esm/utils/index.js +1 -0
  74. package/dist/esm/utils/tool-fold.js +92 -0
  75. package/dist/tsconfig.tsbuildinfo +1 -1
  76. package/docs/migration-FUI-2495.md +339 -0
  77. package/docs/sub_agent.md +310 -0
  78. package/package.json +16 -15
  79. package/src/channel/ai-activity-channel.ts +4 -20
  80. package/src/components/ai-driver/ai-driver.ts +69 -0
  81. package/src/components/ai-driver/index.ts +1 -0
  82. package/src/components/chat-driver/chat-driver.ts +600 -73
  83. package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +3 -3
  84. package/src/components/chat-markdown/chat-markdown.ts +1 -1
  85. package/src/components/halo-overlay.ts +45 -7
  86. package/src/components/orchestrating-driver/index.ts +1 -0
  87. package/src/components/orchestrating-driver/orchestrating-driver.ts +328 -0
  88. package/src/components/popout-manager/index.ts +1 -0
  89. package/src/components/popout-manager/popout-manager.ts +147 -0
  90. package/src/config/config.ts +45 -15
  91. package/src/config/fallback-agents.ts +29 -0
  92. package/src/config/index.ts +1 -0
  93. package/src/index.ts +6 -0
  94. package/src/main/main.styles.ts +200 -4
  95. package/src/main/main.template.ts +200 -80
  96. package/src/main/main.ts +567 -94
  97. package/src/main/main.types.ts +11 -0
  98. package/src/state/ai-assistant-slice.ts +80 -0
  99. package/src/state/driver-registry.ts +51 -0
  100. package/src/state/session-store.ts +56 -0
  101. package/src/suggestions/chat-suggestions.ts +158 -0
  102. package/src/types/ai-chat-widget.ts +4 -2
  103. package/src/utils/index.ts +1 -0
  104. package/src/utils/tool-fold.ts +181 -0
  105. package/docs/multi-agent-architecture.md +0 -198
@@ -1,5 +1,27 @@
1
+ // =============================================================================
2
+ // ARCHITECTURAL RULES — read before modifying this file
3
+ // =============================================================================
4
+ //
5
+ // 1. STORE INIT MUST PRECEDE super.connectedCallback()
6
+ // `_sessionRef` must be assigned before calling `super.connectedCallback()`.
7
+ // FAST triggers its first render inside `super.connectedCallback()`. The store
8
+ // Proxy calls Observable.track() when a slice is read, which wires up FAST's
9
+ // reactivity. If `_sessionRef` is null during that first render, no tracking
10
+ // is registered and subsequent Redux dispatches will not trigger re-renders.
11
+ // Do not reorder these lines.
12
+ //
13
+ // 2. MARK @volatile ON GETTERS WITH CONDITIONAL OBSERVABLE ACCESS
14
+ // FAST only tracks observables that are actually accessed during a getter's
15
+ // last evaluation. If a getter has branches that access different observables
16
+ // depending on runtime state (e.g. filtering over messages of varying types,
17
+ // or a ternary that reads one observable OR another), FAST may not know to
18
+ // re-evaluate when an unvisited branch's observable changes. Mark such
19
+ // getters @volatile so FAST always re-evaluates them when any dependency
20
+ // changes, rather than relying on cached dependency tracking.
21
+ //
22
+ // =============================================================================
1
23
  var FoundationAiAssistant_1;
2
- import { __awaiter, __decorate } from "tslib";
24
+ import { __awaiter, __decorate, __rest } from "tslib";
3
25
  import { AIProvider } from '@genesislcap/foundation-ai';
4
26
  import { avoidTreeShaking } from '@genesislcap/foundation-utils';
5
27
  import { customElement, html, GenesisElement, observable, volatile, attr, } from '@genesislcap/web-core';
@@ -10,18 +32,32 @@ import { ChatDriver } from '../components/chat-driver/chat-driver';
10
32
  import { AiChatInteractionWrapper } from '../components/chat-interaction-wrapper/chat-interaction-wrapper';
11
33
  import { AiChatMarkdown } from '../components/chat-markdown/chat-markdown';
12
34
  import { AiHaloOverlay } from '../components/halo-overlay';
35
+ import { OrchestratingDriver } from '../components/orchestrating-driver/orchestrating-driver';
36
+ import { getOrCreateDriver, deleteDriver } from '../state/driver-registry';
37
+ import { getSessionStore, hasSessionStore } from '../state/session-store';
38
+ import { ChatSuggestions } from '../suggestions/chat-suggestions';
13
39
  import { logger } from '../utils/logger';
40
+ import { expandToolTree } from '../utils/tool-fold';
14
41
  import { styles } from './main.styles';
15
42
  import { FoundationAiAssistantTemplate } from './main.template';
16
43
  import { ALL_ANIMATIONS } from './main.types';
44
+ /** Context window sizes (in tokens) for known models. */
45
+ const MODEL_CONTEXT_LIMITS = {
46
+ 'gemini-2.5-flash': 1048576,
47
+ 'gemini-2.5-flash-lite': 1048576,
48
+ 'gpt-4o': 128000,
49
+ 'gpt-4o-mini': 128000,
50
+ 'gpt-4-turbo': 128000,
51
+ };
17
52
  // Register supporting components when the main component module is imported.
18
- avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiChatBubble, AiActivityHalo);
53
+ avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiChatBubble, AiActivityHalo, ChatSuggestions);
19
54
  /**
20
55
  * Foundation AI Assistant component.
21
56
  *
22
57
  * @remarks
23
58
  * Inject an `AIProvider` through the DI container. Pass agent configuration via the `agents`
24
- * property. The component creates a `ChatDriver` to manage the conversation loop.
59
+ * property. The component creates a `ChatDriver` (single agent) or `OrchestratingDriver`
60
+ * (multiple agents) to manage the conversation loop.
25
61
  *
26
62
  * Popout/collapse coordination uses `agenticActivityBus` topics `chat-popout` and `chat-popin` — not DOM `CustomEvent`s on this element.
27
63
  *
@@ -33,37 +69,143 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
33
69
  constructor() {
34
70
  super(...arguments);
35
71
  this.designSystemPrefix = 'rapid';
72
+ this.headerTitle = 'Genesis Assistant';
36
73
  this.placeholder = 'Message assistant...';
37
74
  this.chatConfig = {};
38
- this.messages = [];
39
- this.state = 'idle';
40
- this.inputValue = '';
75
+ /** When set, enables Redux DevTools for this instance's session store. */
76
+ this.debugRedux = false;
77
+ // ---- Transient UI state (stays as @observable on the component) ----
78
+ this._suggestionsGeneration = 0;
41
79
  this.attachments = [];
42
80
  this.attachmentErrors = [];
43
- /** Current user-facing toggle state for tool call visibility. */
44
- this.showToolCalls = false;
45
- /** Current user-facing toggle state for thinking step visibility. */
46
- this.showThinkingSteps = false;
47
- /** Currently enabled animations. */
48
- this.enabledAnimations = [];
49
81
  /** Whether the loading spinner is currently visible. Controlled by the loading delay timer. */
50
82
  this.showLoadingIndicator = false;
51
83
  /** Whether the settings panel is open. */
52
84
  this.settingsOpen = false;
85
+ /** Whether the splash overlay is currently showing (no messages and showSplash is enabled). Reflected as a boolean attribute on the host. */
86
+ this.showingSplash = false;
53
87
  this.haloStartPublished = false;
54
- /**
55
- * Whether the halo animation should be shown.
56
- * True when the AI is actively computing (loading state, no pending interaction).
57
- */
58
- this.showHalo = false;
88
+ /** True when the user has intentionally scrolled away from the bottom — suppresses auto-scroll. */
89
+ this._userScrolledAway = false;
90
+ this.showHalo = 'no';
91
+ }
92
+ get messages() {
93
+ var _a, _b;
94
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.messages) !== null && _b !== void 0 ? _b : [];
95
+ }
96
+ set messages(value) {
97
+ var _a;
98
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setMessages(value);
99
+ this.handleMessagesUpdate();
100
+ }
101
+ get state() {
102
+ var _a, _b;
103
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.state) !== null && _b !== void 0 ? _b : 'idle';
104
+ }
105
+ set state(value) {
106
+ var _a;
107
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setState(value);
108
+ this.syncShowHalo();
109
+ }
110
+ get activeAgent() {
111
+ var _a;
112
+ return (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.activeAgent;
113
+ }
114
+ set activeAgent(value) {
115
+ var _a, _b;
116
+ // Strip toolHandlers before storing — functions are non-serializable and Redux
117
+ // serializable-state middleware will warn. toolHandlers are never read back from
118
+ // the store; they are always sourced from this.agents when the driver is built.
119
+ if (value) {
120
+ const { toolHandlers: _ } = value, serializable = __rest(value, ["toolHandlers"]);
121
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setActiveAgent(serializable);
122
+ }
123
+ else {
124
+ (_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setActiveAgent(undefined);
125
+ }
126
+ }
127
+ get suggestionsState() {
128
+ var _a, _b;
129
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.suggestionsState) !== null && _b !== void 0 ? _b : { status: 'idle' };
130
+ }
131
+ set suggestionsState(value) {
132
+ var _a;
133
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setSuggestionsState(value);
134
+ }
135
+ /** Current user-facing toggle state for tool call visibility. */
136
+ get showToolCalls() {
137
+ var _a, _b;
138
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.showToolCalls) !== null && _b !== void 0 ? _b : false;
139
+ }
140
+ set showToolCalls(value) {
141
+ var _a;
142
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setShowToolCalls(value);
143
+ }
144
+ /** Current user-facing toggle state for thinking step visibility. */
145
+ get showThinkingSteps() {
146
+ var _a, _b;
147
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.showThinkingSteps) !== null && _b !== void 0 ? _b : false;
148
+ }
149
+ set showThinkingSteps(value) {
150
+ var _a;
151
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setShowThinkingSteps(value);
152
+ }
153
+ /** Current user-facing toggle state for agent switch indicator visibility. */
154
+ get showAgentSwitchIndicator() {
155
+ var _a, _b;
156
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.showAgentSwitchIndicator) !== null && _b !== void 0 ? _b : false;
157
+ }
158
+ set showAgentSwitchIndicator(value) {
159
+ var _a;
160
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setShowAgentSwitchIndicator(value);
161
+ }
162
+ /** Currently enabled animations. */
163
+ get enabledAnimations() {
164
+ var _a, _b;
165
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.enabledAnimations) !== null && _b !== void 0 ? _b : [];
166
+ }
167
+ set enabledAnimations(value) {
168
+ var _a;
169
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setEnabledAnimations(value);
170
+ }
171
+ /** Most recent prompt token count from the AI provider, if available. */
172
+ get contextTokens() {
173
+ var _a;
174
+ return (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.contextTokens;
175
+ }
176
+ set contextTokens(value) {
177
+ var _a;
178
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setContextTokens(value);
179
+ }
180
+ /** Context window size for the active model, if known. */
181
+ get contextLimit() {
182
+ var _a;
183
+ return (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.contextLimit;
184
+ }
185
+ set contextLimit(value) {
186
+ var _a;
187
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setContextLimit(value);
188
+ }
189
+ get inputValue() {
190
+ var _a, _b;
191
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.inputValue) !== null && _b !== void 0 ? _b : '';
192
+ }
193
+ set inputValue(value) {
194
+ var _a;
195
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setInputValue(value);
59
196
  }
60
197
  syncShowHalo() {
61
198
  if (this.state !== 'loading') {
62
- this.showHalo = false;
199
+ this.showHalo = 'no';
63
200
  return;
64
201
  }
65
202
  const last = this.messages[this.messages.length - 1];
66
- this.showHalo = !(last === null || last === void 0 ? void 0 : last.interaction);
203
+ if (last === null || last === void 0 ? void 0 : last.interaction) {
204
+ this.showHalo = 'no';
205
+ }
206
+ else if (this.showHalo !== 'orchestrating') {
207
+ this.showHalo = 'agent';
208
+ }
67
209
  }
68
210
  /** True when there is a pending (unresolved) interaction — disables the popout button. */
69
211
  get hasActivePendingInteraction() {
@@ -76,7 +218,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
76
218
  var _a;
77
219
  if (!((_a = this.enabledAnimations) === null || _a === void 0 ? void 0 : _a.includes('halo')))
78
220
  return;
79
- if (!this.showHalo) {
221
+ if (this.showHalo === 'no') {
80
222
  agenticActivityBus.publish('halo-stop', undefined);
81
223
  this.haloStartPublished = false;
82
224
  }
@@ -98,10 +240,24 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
98
240
  * Messages filtered by the current toggle state.
99
241
  * Tool-related messages (those with toolCalls or toolResult) are hidden when
100
242
  * `showToolCalls` is false.
243
+ *
244
+ * Marked `@volatile` because the filter branches conditionally access
245
+ * `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator`
246
+ * depending on message content. Without it, FAST would only track the
247
+ * observables accessed on the last evaluation and miss toggle changes that
248
+ * happen to hit an untracked branch.
101
249
  */
102
250
  get visibleMessages() {
251
+ var _a;
252
+ const showAgentSwitchIndicator = ((_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showAgentSwitchIndicator) != null
253
+ ? this.showAgentSwitchIndicator
254
+ : this.showToolCalls;
103
255
  return this.messages.filter((m) => {
104
256
  var _a, _b, _c;
257
+ // Agent switch indicators are shown when the toggle is on (or showToolCalls implies it)
258
+ if (m.role === 'system-event') {
259
+ return showAgentSwitchIndicator;
260
+ }
105
261
  // Never show tool messages to the user
106
262
  if (m.role === 'tool') {
107
263
  return false;
@@ -118,45 +274,247 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
118
274
  return !isToolRelated || this.showToolCalls;
119
275
  });
120
276
  }
277
+ agentsChanged() {
278
+ var _a, _b, _c;
279
+ // Guard: driver doesn't exist yet during connectedCallback — createDriver/wireDriver handle that.
280
+ if (!this.driver)
281
+ return;
282
+ const key = this.getStateKey();
283
+ if (!key)
284
+ return;
285
+ // Skip rebuild if the agent configuration hasn't meaningfully changed.
286
+ // This prevents spurious driver teardowns when the collapse-mode element
287
+ // connects and its parent re-assigns the same agents array, which would
288
+ // destroy pending interactions in the existing driver.
289
+ const newKey = this.getAgentsKey(this.agents);
290
+ if (newKey === this._driverAgentsKey)
291
+ return;
292
+ this._driverAgentsKey = newKey;
293
+ // Don't rebuild while the driver is busy — it was created with the correct agents
294
+ // and has live pending interactions. This prevents the collapse element (which
295
+ // initially has no agents, then gets them assigned by its wrapper) from tearing
296
+ // down a mid-flight driver and creating a fresh idle one.
297
+ if (this.driver.isBusy())
298
+ return;
299
+ const history = (_c = (_b = (_a = this.driver).getRawHistory) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : [];
300
+ this.unwireDriver();
301
+ deleteDriver(key);
302
+ this.driver = getOrCreateDriver(key, () => this.createDriver());
303
+ this.wireDriver();
304
+ if (history.length)
305
+ this.driver.loadHistory([...history]);
306
+ }
307
+ /** Returns a stable fingerprint for an agents array based on agent names and tool handler keys. */
308
+ getAgentsKey(agents) {
309
+ if (!(agents === null || agents === void 0 ? void 0 : agents.length))
310
+ return '';
311
+ return agents
312
+ .map((a) => {
313
+ var _a;
314
+ const toolNames = Object.keys((_a = a.toolHandlers) !== null && _a !== void 0 ? _a : {})
315
+ .sort()
316
+ .join('+');
317
+ return `${a.name}[${toolNames}]`;
318
+ })
319
+ .join(',');
320
+ }
321
+ /**
322
+ * Pure factory — creates a ChatDriver or OrchestratingDriver based on the
323
+ * current agent configuration. Does not wire event listeners or register in
324
+ * the driver registry.
325
+ */
326
+ createDriver() {
327
+ var _a, _b, _c;
328
+ const agent = (_a = this.chatConfig.agent) !== null && _a !== void 0 ? _a : {};
329
+ const { agents } = this;
330
+ if (agents && agents.length > 1) {
331
+ return new OrchestratingDriver(this.aiProvider, agents, {
332
+ maxHandoffs: agent.maxHandoffs,
333
+ classifierHistoryLength: agent.classifierHistoryLength,
334
+ classifierRetries: agent.classifierRetries,
335
+ maxToolIterations: agent.maxToolIterations,
336
+ maxFoldOperations: agent.maxFoldOperations,
337
+ });
338
+ }
339
+ const singleAgent = agents === null || agents === void 0 ? void 0 : agents[0];
340
+ this.activeAgent = singleAgent;
341
+ return new ChatDriver(this.aiProvider, (_b = singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.toolHandlers) !== null && _b !== void 0 ? _b : {}, (_c = singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.toolDefinitions) !== null && _c !== void 0 ? _c : [], singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.systemPrompt, singleAgent === null || singleAgent === void 0 ? void 0 : singleAgent.primerHistory, agent.maxToolIterations, agent.maxFoldOperations);
342
+ }
343
+ /**
344
+ * Attaches event listeners to the current driver. Stores a cleanup function
345
+ * so `unwireDriver` can remove them precisely. Safe to call multiple
346
+ * times — unwires any existing listeners first.
347
+ */
348
+ wireDriver() {
349
+ const { driver } = this;
350
+ if (!driver)
351
+ return;
352
+ // Idempotent — unwire previous listeners on this component before re-wiring.
353
+ this.unwireDriver();
354
+ const onHistoryUpdated = (e) => {
355
+ this.messages = [...e.detail];
356
+ };
357
+ driver.addEventListener('history-updated', onHistoryUpdated);
358
+ const cleanups = [
359
+ () => driver.removeEventListener('history-updated', onHistoryUpdated),
360
+ ];
361
+ if (driver instanceof OrchestratingDriver) {
362
+ const onOrchStart = () => {
363
+ this.showHalo = 'orchestrating';
364
+ };
365
+ const onOrchStop = () => {
366
+ if (this.showHalo === 'orchestrating')
367
+ this.showHalo = 'agent';
368
+ };
369
+ const onAgentChanged = (e) => {
370
+ this.activeAgent = e.detail;
371
+ };
372
+ driver.addEventListener('orchestrating-start', onOrchStart);
373
+ driver.addEventListener('orchestrating-stop', onOrchStop);
374
+ driver.addEventListener('agent-changed', onAgentChanged);
375
+ cleanups.push(() => driver.removeEventListener('orchestrating-start', onOrchStart), () => driver.removeEventListener('orchestrating-stop', onOrchStop), () => driver.removeEventListener('agent-changed', onAgentChanged));
376
+ }
377
+ this.driverCleanup = () => {
378
+ for (const fn of cleanups)
379
+ fn();
380
+ };
381
+ }
382
+ /**
383
+ * Removes event listeners attached by `wireDriver`. Does not destroy
384
+ * the driver or remove it from the registry.
385
+ */
386
+ unwireDriver() {
387
+ var _a;
388
+ (_a = this.driverCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
389
+ this.driverCleanup = undefined;
390
+ }
121
391
  connectedCallback() {
122
- var _a, _b, _c, _d;
392
+ var _a, _b, _c, _d, _e;
393
+ // Initialise the store reference BEFORE super.connectedCallback() so that
394
+ // the first FAST render has access to the store. The store Proxy calls
395
+ // Observable.track(observableStore, sliceName) whenever a slice is read,
396
+ // and actions call Observable.notify — so FAST's reactivity wires up
397
+ // correctly only if _sessionRef is set during that first render pass.
398
+ const key = this.getStateKey();
399
+ const isNewStore = !hasSessionStore(key);
400
+ this._sessionRef = getSessionStore(key, this.debugRedux || undefined);
123
401
  super.connectedCallback();
124
- const { showToolCalls, showThinkingSteps, animations } = this.chatConfig;
125
- this.showToolCalls = showToolCalls === true;
126
- this.showThinkingSteps = showThinkingSteps === true;
127
- this.enabledAnimations =
128
- (_a = animations === null || animations === void 0 ? void 0 : animations.enabled) !== null && _a !== void 0 ? _a : (animations ? [...ALL_ANIMATIONS] : []);
129
- // TODO: multi-agent orchestration — classify intent and route to the correct agent
130
- if (this.agents && this.agents.length > 1) {
131
- logger.warn('FoundationAiAssistant: multiple agents configured but orchestration is not yet implemented. Only the first agent will be used.');
402
+ // Only apply chatConfig UI defaults to a freshly created store.
403
+ // Existing stores already hold the correct state from a prior session.
404
+ if (isNewStore) {
405
+ const ui = (_a = this.chatConfig.ui) !== null && _a !== void 0 ? _a : {};
406
+ this.showToolCalls = ui.showToolCalls === true;
407
+ this.showThinkingSteps = ui.showThinkingSteps === true;
408
+ this.showAgentSwitchIndicator = ui.showAgentSwitchIndicator === true;
409
+ this.enabledAnimations =
410
+ (_c = (_b = ui.animations) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : (ui.animations ? [...ALL_ANIMATIONS] : []);
132
411
  }
133
- const agent = (_b = this.agents) === null || _b === void 0 ? void 0 : _b[0];
134
- this.driver = new ChatDriver(this.aiProvider, (_c = agent === null || agent === void 0 ? void 0 : agent.toolHandlers) !== null && _c !== void 0 ? _c : {}, (_d = agent === null || agent === void 0 ? void 0 : agent.toolDefinitions) !== null && _d !== void 0 ? _d : [], agent === null || agent === void 0 ? void 0 : agent.systemPrompt, agent === null || agent === void 0 ? void 0 : agent.primerHistory, this.chatConfig.maxToolIterations);
135
- this.driver.addEventListener('history-updated', (e) => {
136
- this.messages = [...e.detail];
137
- });
138
- // When embedded in a bubble (expand mode), listen for chat-popin to restore state
139
- // when the layout panel collapses back.
412
+ this.driver = getOrCreateDriver(key, () => this.createDriver());
413
+ this._driverAgentsKey = this.getAgentsKey(this.agents);
414
+ this.wireDriver();
140
415
  if (this.popoutMode === 'expand') {
141
- this.unsubBus = agenticActivityBus.subscribe('chat-popin', ({ state }) => {
142
- this.applyState(state);
416
+ // Dual-listener guard: when the assistant is popped out into the layout
417
+ // panel, a collapse-mode element connects and wires to the same shared
418
+ // driver. Unwire this (hidden) instance on popout; re-wire on popin.
419
+ const unsubPopout = agenticActivityBus.subscribe('chat-popout', () => {
420
+ this.unwireDriver();
143
421
  });
422
+ const unsubPopin = agenticActivityBus.subscribe('chat-popin', () => {
423
+ this.wireDriver();
424
+ });
425
+ this.unsubBus = () => {
426
+ unsubPopout();
427
+ unsubPopin();
428
+ };
429
+ }
430
+ this.syncShowingSplash();
431
+ // Restore loading state if the driver is still executing mid-lifecycle.
432
+ // disconnectedCallback resets state to 'idle', so we must check real driver state here.
433
+ if ((_d = this.driver) === null || _d === void 0 ? void 0 : _d.isBusy()) {
434
+ this.state = 'loading';
435
+ this.startLoadingTimer();
436
+ // Subscribe once so that when the originating send() completes (possibly on a
437
+ // different element instance that has since disconnected), this element cleans up
438
+ // its own timer, syncs the halo, and triggers the post-response suggestion fetch.
439
+ this._executionCompletionUnsub = (_e = this._sessionRef) === null || _e === void 0 ? void 0 : _e.subscribeKey((s) => s.aiAssistant.state, () => {
440
+ var _a, _b;
441
+ if (((_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.state) === 'idle') {
442
+ this.stopLoadingTimer();
443
+ this.syncShowHalo();
444
+ this.fetchSuggestions();
445
+ (_b = this._executionCompletionUnsub) === null || _b === void 0 ? void 0 : _b.call(this);
446
+ this._executionCompletionUnsub = undefined;
447
+ }
448
+ });
449
+ }
450
+ this.fetchSuggestions();
451
+ void this.resolveContextLimit();
452
+ if (this.messagesEl) {
453
+ this._scrollListener = () => {
454
+ this._userScrolledAway =
455
+ this.messagesEl.scrollTop + this.messagesEl.clientHeight <
456
+ this.messagesEl.scrollHeight - FoundationAiAssistant_1.SCROLL_BOTTOM_THRESHOLD_PX;
457
+ };
458
+ this.messagesEl.addEventListener('scroll', this._scrollListener);
144
459
  }
145
460
  logger.debug('FoundationAiAssistant connected');
146
461
  }
147
462
  disconnectedCallback() {
148
- var _a;
463
+ var _a, _b;
149
464
  super.disconnectedCallback();
150
465
  this.stopLoadingTimer();
151
466
  this.state = 'idle';
467
+ this.unwireDriver();
152
468
  (_a = this.unsubBus) === null || _a === void 0 ? void 0 : _a.call(this);
153
469
  this.unsubBus = undefined;
470
+ (_b = this._executionCompletionUnsub) === null || _b === void 0 ? void 0 : _b.call(this);
471
+ this._executionCompletionUnsub = undefined;
472
+ if (this.messagesEl && this._scrollListener) {
473
+ this.messagesEl.removeEventListener('scroll', this._scrollListener);
474
+ }
475
+ this._scrollListener = undefined;
476
+ this._userScrolledAway = false;
477
+ // Clear local references only — driver and store stay in their registries.
154
478
  this.driver = undefined;
479
+ this._sessionRef = undefined;
155
480
  }
156
- stateChanged() {
157
- this.syncShowHalo();
481
+ resolveContextLimit() {
482
+ return __awaiter(this, void 0, void 0, function* () {
483
+ var _a, _b;
484
+ try {
485
+ const status = yield ((_b = (_a = this.aiProvider).getStatus) === null || _b === void 0 ? void 0 : _b.call(_a));
486
+ if (status === null || status === void 0 ? void 0 : status.model) {
487
+ this.contextLimit = MODEL_CONTEXT_LIMITS[status.model];
488
+ }
489
+ }
490
+ catch (_c) {
491
+ // Non-fatal — context limit display simply won't show
492
+ }
493
+ });
494
+ }
495
+ chatConfigChanged() {
496
+ this.syncShowingSplash();
497
+ }
498
+ showingSplashChanged() {
499
+ if (this.showingSplash) {
500
+ this.setAttribute('showing-splash', '');
501
+ }
502
+ else {
503
+ this.removeAttribute('showing-splash');
504
+ }
505
+ }
506
+ // Sets the observable which reflects as a boolean attribute on the host via showingSplashChanged.
507
+ // Must be an attribute on the host (not a shadow DOM class) so consumer CSS can target slotted
508
+ // light DOM content — shadow DOM classes are not visible to external stylesheets.
509
+ syncShowingSplash() {
510
+ var _a;
511
+ this.showingSplash = !!((_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showSplash) && this.messages.length === 0;
158
512
  }
159
- messagesChanged() {
513
+ /**
514
+ * Runs side effects that were previously in `messagesChanged()`.
515
+ * Called from the `messages` setter after dispatching to the store.
516
+ */
517
+ handleMessagesUpdate() {
160
518
  var _a, _b;
161
519
  // Reset the loading timer when an assistant message arrives mid-loop so each
162
520
  // individual step gets a fresh window before the spinner appears.
@@ -172,11 +530,16 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
172
530
  }
173
531
  }
174
532
  this.syncShowHalo();
533
+ this.syncShowingSplash();
534
+ // Update context token count from the most recent message that carries usage data.
535
+ for (let i = this.messages.length - 1; i >= 0; i -= 1) {
536
+ if (this.messages[i].inputTokens != null) {
537
+ this.contextTokens = this.messages[i].inputTokens;
538
+ break;
539
+ }
540
+ }
175
541
  // Publish halo-start whenever a new toolCalls message arrives.
176
- // If we've already published one this turn, send halo-stop first so grids
177
- // not relevant to the new tools begin deactivating (fix #2).
178
- // Never publish with empty toolNames (fix #3).
179
- if (this.showHalo && ((_a = this.enabledAnimations) === null || _a === void 0 ? void 0 : _a.includes('halo'))) {
542
+ if (this.showHalo !== 'no' && ((_a = this.enabledAnimations) === null || _a === void 0 ? void 0 : _a.includes('halo'))) {
180
543
  const last = this.messages[this.messages.length - 1];
181
544
  if ((_b = last === null || last === void 0 ? void 0 : last.toolCalls) === null || _b === void 0 ? void 0 : _b.length) {
182
545
  const toolNames = this.getActiveToolNames();
@@ -192,7 +555,6 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
192
555
  this.scrollToBottom();
193
556
  }
194
557
  showLoadingIndicatorChanged() {
195
- // Scroll to bottom when the spinner appears so it is visible.
196
558
  if (this.showLoadingIndicator) {
197
559
  this.scrollToBottom();
198
560
  }
@@ -200,19 +562,19 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
200
562
  // Double rAF: first frame lets the framework flush the DOM update,
201
563
  // second frame reads the correct scrollHeight after layout.
202
564
  scrollToBottom() {
565
+ if (this._userScrolledAway)
566
+ return;
203
567
  requestAnimationFrame(() => {
204
568
  requestAnimationFrame(() => {
205
- var _a;
206
- const el = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.messages');
207
- if (el)
208
- el.scrollTop = el.scrollHeight;
569
+ if (this.messagesEl)
570
+ this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
209
571
  });
210
572
  });
211
573
  }
212
574
  startLoadingTimer() {
213
- var _a;
575
+ var _a, _b;
214
576
  this.clearLoadingTimer();
215
- const delay = (_a = this.chatConfig.loadingDelay) !== null && _a !== void 0 ? _a : FoundationAiAssistant_1.DEFAULT_LOADING_DELAY_S;
577
+ const delay = (_b = (_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.loadingDelay) !== null && _b !== void 0 ? _b : FoundationAiAssistant_1.DEFAULT_LOADING_DELAY_S;
216
578
  if (delay === 0) {
217
579
  this.showLoadingIndicator = true;
218
580
  }
@@ -235,31 +597,35 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
235
597
  /** Called when the user clicks the popout button. */
236
598
  handlePopout() {
237
599
  if (this.popoutMode === 'expand') {
238
- agenticActivityBus.publish('chat-popout', { state: this.serializeState() });
600
+ agenticActivityBus.publish('chat-popout', undefined);
239
601
  }
240
602
  else if (this.popoutMode === 'collapse') {
241
- agenticActivityBus.publish('chat-popin', { state: this.serializeState() });
603
+ agenticActivityBus.publish('chat-popin', undefined);
242
604
  }
243
605
  }
244
- /** Applies a serialized state snapshot to this live instance (used on collapse path). */
245
- applyState(state) {
246
- var _a;
247
- this.messages = [...state.messages];
248
- this.showToolCalls = state.showToolCalls;
249
- this.showThinkingSteps = state.showThinkingSteps;
250
- this.enabledAnimations = [...state.enabledAnimations];
251
- (_a = this.driver) === null || _a === void 0 ? void 0 : _a.loadHistory(state.messages);
252
- }
253
- serializeState() {
254
- return {
255
- messages: [...this.messages],
256
- showToolCalls: this.showToolCalls,
257
- showThinkingSteps: this.showThinkingSteps,
258
- enabledAnimations: [...this.enabledAnimations],
259
- };
606
+ /** Returns a cache key for this instance, or undefined if the component has no identity. */
607
+ getStateKey() {
608
+ const parts = [this.id, this.headerTitle].filter(Boolean);
609
+ return parts.length ? parts.join('::') : undefined;
260
610
  }
261
611
  toggleSettings() {
262
- this.settingsOpen = !this.settingsOpen;
612
+ var _a;
613
+ if (this.settingsOpen) {
614
+ const panel = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.settings-panel');
615
+ if (panel) {
616
+ panel.classList.add('closing');
617
+ panel.addEventListener('animationend', () => {
618
+ panel.classList.remove('closing');
619
+ this.settingsOpen = false;
620
+ }, { once: true });
621
+ }
622
+ else {
623
+ this.settingsOpen = false;
624
+ }
625
+ }
626
+ else {
627
+ this.settingsOpen = true;
628
+ }
263
629
  }
264
630
  toggleShowToolCalls() {
265
631
  this.showToolCalls = !this.showToolCalls;
@@ -267,23 +633,38 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
267
633
  toggleShowThinkingSteps() {
268
634
  this.showThinkingSteps = !this.showThinkingSteps;
269
635
  }
636
+ toggleShowAgentSwitchIndicator() {
637
+ this.showAgentSwitchIndicator = !this.showAgentSwitchIndicator;
638
+ }
270
639
  setEnabledAnimations(animations) {
271
640
  this.enabledAnimations = animations;
272
641
  }
273
- downloadHistory() {
274
- var _a, _b, _c, _d;
642
+ getDebugLog() {
643
+ var _a, _b, _c, _d, _e, _f, _g;
644
+ const timestamp = new Date().toISOString().replace(/:/g, '-');
645
+ return {
646
+ messages: (_c = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getRawHistory) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : this.messages,
647
+ meta: {
648
+ timestamp,
649
+ host: window.location.host,
650
+ agentSummary: (_d = this.agents) === null || _d === void 0 ? void 0 : _d.map((a) => {
651
+ var _a, _b;
652
+ return (Object.assign(Object.assign({}, a), { toolDefinitions: expandToolTree((_a = a.toolDefinitions) !== null && _a !== void 0 ? _a : [], (_b = a.toolHandlers) !== null && _b !== void 0 ? _b : {}), toolHandlers: undefined }));
653
+ }),
654
+ activeSystemPrompt: (_e = this.activeAgent) === null || _e === void 0 ? void 0 : _e.systemPrompt,
655
+ activePrimerHistory: (_f = this.activeAgent) === null || _f === void 0 ? void 0 : _f.primerHistory,
656
+ activeFoldStack: this.driver instanceof ChatDriver ? this.driver.getActiveFoldNames() : undefined,
657
+ debug: (_g = this.debugStateFactory) === null || _g === void 0 ? void 0 : _g.call(this),
658
+ },
659
+ };
660
+ }
661
+ downloadDebugLog() {
662
+ var _a, _b, _c;
275
663
  const timestamp = new Date().toISOString().replace(/:/g, '-');
276
- // TODO: multi-agent orchestration use the active agent rather than always agents[0]
277
- const agent = (_a = this.agents) === null || _a === void 0 ? void 0 : _a[0];
278
- const agentName = ((_c = (_b = agent === null || agent === void 0 ? void 0 : agent.name) !== null && _b !== void 0 ? _b : this.headerTitle) !== null && _c !== void 0 ? _c : 'chat')
664
+ const agentName = ((_c = (_b = (_a = this.activeAgent) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : this.headerTitle) !== null && _c !== void 0 ? _c : 'chat')
279
665
  .toLowerCase()
280
666
  .replace(/\s+/g, '-');
281
- const payload = {
282
- messages: this.messages,
283
- systemPrompt: agent === null || agent === void 0 ? void 0 : agent.systemPrompt,
284
- primerHistory: agent === null || agent === void 0 ? void 0 : agent.primerHistory,
285
- debug: (_d = this.debugStateFactory) === null || _d === void 0 ? void 0 : _d.call(this),
286
- };
667
+ const payload = this.getDebugLog();
287
668
  const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
288
669
  const url = URL.createObjectURL(blob);
289
670
  const a = document.createElement('a');
@@ -312,12 +693,12 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
312
693
  }
313
694
  }
314
695
  isAcceptedFile(file) {
315
- var _a, _b;
316
- const accepted = this.chatConfig.acceptedFiles;
696
+ var _a, _b, _c;
697
+ const accepted = (_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.acceptedFiles;
317
698
  if (!accepted)
318
699
  return false;
319
700
  const acceptedList = accepted.split(',').map((s) => s.trim().toLowerCase());
320
- const ext = `.${(_b = (_a = file.name.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : ''}`;
701
+ const ext = `.${(_c = (_b = file.name.split('.').pop()) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== null && _c !== void 0 ? _c : ''}`;
321
702
  return acceptedList.some((a) => a === ext ||
322
703
  a === file.type ||
323
704
  (a.endsWith('/*') && file.type.startsWith(a.replace('/*', '/'))));
@@ -367,16 +748,79 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
367
748
  handleSendClick() {
368
749
  this.send();
369
750
  }
751
+ handleSuggestionClick(suggestion) {
752
+ this.inputValue = suggestion;
753
+ this.send();
754
+ }
755
+ fetchSuggestions() {
756
+ return __awaiter(this, void 0, void 0, function* () {
757
+ var _a, _b;
758
+ const suggestionsConfig = this.chatConfig.suggestions;
759
+ if (!this.driver || !suggestionsConfig || suggestionsConfig.behavior === 'never') {
760
+ return;
761
+ }
762
+ if (suggestionsConfig.behavior === 'initial' && this.messages.length > 0) {
763
+ return;
764
+ }
765
+ // Skip if a tool flow is currently executing — suggestions would be generated
766
+ // from partial history and would be replaced by the post-response fetch anyway.
767
+ if ((_a = this.driver) === null || _a === void 0 ? void 0 : _a.isBusy()) {
768
+ return;
769
+ }
770
+ // Skip if suggestions were already fetched or are in-flight — avoids redundant
771
+ // AI calls when the component reconnects during lifecycle events (pop-out/pop-in,
772
+ // docking) since suggestionsState is persisted in the store.
773
+ if (this.suggestionsState.status === 'loaded' || this.suggestionsState.status === 'loading') {
774
+ return;
775
+ }
776
+ this._suggestionsGeneration += 1;
777
+ const generation = this._suggestionsGeneration;
778
+ this.suggestionsState = { status: 'loading' };
779
+ // For single-agent (ChatDriver used directly), activeAgent is set but its name is never
780
+ // passed to the driver — build agentInfo here so getSuggestions has context.
781
+ // OrchestratingDriver ignores this parameter and builds its own from specialists.
782
+ const agentInfo = this.activeAgent
783
+ ? [
784
+ {
785
+ name: this.activeAgent.name,
786
+ description: 'description' in this.activeAgent ? this.activeAgent.description : '',
787
+ tools: (_b = this.activeAgent.toolDefinitions) !== null && _b !== void 0 ? _b : [],
788
+ },
789
+ ]
790
+ : undefined;
791
+ try {
792
+ const suggestions = yield this.driver.getSuggestions(this.messages, suggestionsConfig.prompt || '', suggestionsConfig.count || FoundationAiAssistant_1.DEFAULT_SUGGESTION_COUNT, agentInfo);
793
+ if (generation !== this._suggestionsGeneration)
794
+ return;
795
+ this.suggestionsState = { status: 'loaded', suggestions };
796
+ this.scrollToBottom();
797
+ }
798
+ catch (e) {
799
+ if (generation !== this._suggestionsGeneration)
800
+ return;
801
+ this.suggestionsState = { status: 'error', message: e.message };
802
+ logger.error('Failed to fetch suggestions:', e);
803
+ }
804
+ });
805
+ }
370
806
  send() {
371
807
  return __awaiter(this, void 0, void 0, function* () {
372
808
  var _a;
373
809
  const input = this.inputValue.trim();
374
810
  if ((!input && !this.attachments.length) || this.state === 'loading')
375
811
  return;
812
+ // Capture the session ref before any await. If a lifecycle event occurs during
813
+ // execution, disconnectedCallback clears this._sessionRef — but the captured
814
+ // reference lets the finally block still write 'idle' back to the shared store,
815
+ // which in turn triggers the _executionCompletionUnsub subscription on whichever
816
+ // element is currently active (e.g. the collapse-mode element after docking).
817
+ const capturedSessionRef = this._sessionRef;
376
818
  const pendingAttachments = this.attachments.length ? [...this.attachments] : undefined;
377
819
  this.inputValue = '';
378
820
  this.attachments = [];
379
821
  this.attachmentErrors = [];
822
+ this.suggestionsState = { status: 'idle' };
823
+ this._userScrolledAway = false;
380
824
  this.state = 'loading';
381
825
  this.startLoadingTimer();
382
826
  const displayInput = (pendingAttachments === null || pendingAttachments === void 0 ? void 0 : pendingAttachments.length)
@@ -387,15 +831,17 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
387
831
  }
388
832
  finally {
389
833
  this.stopLoadingTimer();
390
- this.state = 'idle';
834
+ // Write directly via captured ref — this._sessionRef may be null if the element
835
+ // disconnected mid-execution. The state setter also calls syncShowHalo(); replicate
836
+ // that here for the case where this element is still connected.
837
+ capturedSessionRef === null || capturedSessionRef === void 0 ? void 0 : capturedSessionRef.actions.aiAssistant.setState('idle');
838
+ this.syncShowHalo();
839
+ this.fetchSuggestions();
391
840
  this.restoreFocusIfAppropriate();
392
841
  }
393
842
  });
394
843
  }
395
844
  restoreFocusIfAppropriate() {
396
- // If focus is still within this component (document.activeElement === this, since shadow DOM
397
- // reports the host) or nothing specific has focus (body), return focus to the input.
398
- // If the user has navigated to another element in the app, leave them there.
399
845
  const active = document.activeElement;
400
846
  if (active !== document.body && active !== this)
401
847
  return;
@@ -405,7 +851,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
405
851
  });
406
852
  }
407
853
  onChatHeaderMouseDown(e) {
408
- if (this.popoutMode !== 'expand')
854
+ if (this.popoutMode === 'collapse')
409
855
  return;
410
856
  e.preventDefault();
411
857
  this.dispatchEvent(new CustomEvent('chat-header-mousedown', {
@@ -419,11 +865,14 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
419
865
  const detail = e.detail;
420
866
  if (detail && detail.interactionId) {
421
867
  this.startLoadingTimer();
422
- (_a = this.driver) === null || _a === void 0 ? void 0 : _a.resolveInteraction(detail.interactionId, detail);
868
+ const { interactionId } = detail, result = __rest(detail, ["interactionId"]);
869
+ (_a = this.driver) === null || _a === void 0 ? void 0 : _a.resolveInteraction(interactionId, result);
423
870
  }
424
871
  }
425
872
  };
873
+ FoundationAiAssistant.SCROLL_BOTTOM_THRESHOLD_PX = 50;
426
874
  FoundationAiAssistant.DEFAULT_LOADING_DELAY_S = 5;
875
+ FoundationAiAssistant.DEFAULT_SUGGESTION_COUNT = 3;
427
876
  FoundationAiAssistant.MS_PER_SECOND = 1000;
428
877
  __decorate([
429
878
  AIProvider
@@ -453,29 +902,14 @@ __decorate([
453
902
  observable
454
903
  ], FoundationAiAssistant.prototype, "debugStateFactory", void 0);
455
904
  __decorate([
456
- observable
457
- ], FoundationAiAssistant.prototype, "messages", void 0);
458
- __decorate([
459
- observable
460
- ], FoundationAiAssistant.prototype, "state", void 0);
461
- __decorate([
462
- observable
463
- ], FoundationAiAssistant.prototype, "inputValue", void 0);
905
+ attr({ attribute: 'debug-redux', mode: 'boolean' })
906
+ ], FoundationAiAssistant.prototype, "debugRedux", void 0);
464
907
  __decorate([
465
908
  observable
466
909
  ], FoundationAiAssistant.prototype, "attachments", void 0);
467
910
  __decorate([
468
911
  observable
469
912
  ], FoundationAiAssistant.prototype, "attachmentErrors", void 0);
470
- __decorate([
471
- observable
472
- ], FoundationAiAssistant.prototype, "showToolCalls", void 0);
473
- __decorate([
474
- observable
475
- ], FoundationAiAssistant.prototype, "showThinkingSteps", void 0);
476
- __decorate([
477
- observable
478
- ], FoundationAiAssistant.prototype, "enabledAnimations", void 0);
479
913
  __decorate([
480
914
  observable
481
915
  ], FoundationAiAssistant.prototype, "showLoadingIndicator", void 0);
@@ -484,10 +918,10 @@ __decorate([
484
918
  ], FoundationAiAssistant.prototype, "settingsOpen", void 0);
485
919
  __decorate([
486
920
  observable
487
- ], FoundationAiAssistant.prototype, "showHalo", void 0);
921
+ ], FoundationAiAssistant.prototype, "showingSplash", void 0);
488
922
  __decorate([
489
- volatile
490
- ], FoundationAiAssistant.prototype, "hasActivePendingInteraction", null);
923
+ observable
924
+ ], FoundationAiAssistant.prototype, "showHalo", void 0);
491
925
  __decorate([
492
926
  volatile
493
927
  ], FoundationAiAssistant.prototype, "visibleMessages", null);