@genesislcap/ai-assistant 14.409.0-FUI-2495.2 → 14.409.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 (64) hide show
  1. package/dist/ai-assistant.api.json +1110 -2791
  2. package/dist/ai-assistant.d.ts +45 -267
  3. package/dist/dts/channel/ai-activity-channel.d.ts +0 -1
  4. package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
  5. package/dist/dts/components/chat-driver/chat-driver.d.ts +7 -25
  6. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  7. package/dist/dts/components/halo-overlay.d.ts +1 -13
  8. package/dist/dts/components/halo-overlay.d.ts.map +1 -1
  9. package/dist/dts/config/config.d.ts +15 -43
  10. package/dist/dts/config/config.d.ts.map +1 -1
  11. package/dist/dts/config/index.d.ts +0 -1
  12. package/dist/dts/config/index.d.ts.map +1 -1
  13. package/dist/dts/index.d.ts +0 -4
  14. package/dist/dts/index.d.ts.map +1 -1
  15. package/dist/dts/main/main.d.ts +7 -16
  16. package/dist/dts/main/main.d.ts.map +1 -1
  17. package/dist/dts/main/main.styles.d.ts.map +1 -1
  18. package/dist/dts/main/main.template.d.ts.map +1 -1
  19. package/dist/esm/components/chat-driver/chat-driver.js +31 -86
  20. package/dist/esm/components/halo-overlay.js +7 -53
  21. package/dist/esm/config/index.js +0 -1
  22. package/dist/esm/index.js +0 -4
  23. package/dist/esm/main/main.js +45 -103
  24. package/dist/esm/main/main.styles.js +4 -145
  25. package/dist/esm/main/main.template.js +61 -97
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +15 -15
  28. package/src/channel/ai-activity-channel.ts +0 -1
  29. package/src/components/chat-driver/chat-driver.ts +35 -116
  30. package/src/components/halo-overlay.ts +7 -45
  31. package/src/config/config.ts +15 -45
  32. package/src/config/index.ts +0 -1
  33. package/src/index.ts +0 -4
  34. package/src/main/main.styles.ts +4 -145
  35. package/src/main/main.template.ts +78 -116
  36. package/src/main/main.ts +50 -105
  37. package/dist/dts/components/ai-driver/ai-driver.d.ts +0 -38
  38. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +0 -1
  39. package/dist/dts/components/ai-driver/index.d.ts +0 -2
  40. package/dist/dts/components/ai-driver/index.d.ts.map +0 -1
  41. package/dist/dts/components/orchestrating-driver/index.d.ts +0 -2
  42. package/dist/dts/components/orchestrating-driver/index.d.ts.map +0 -1
  43. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +0 -36
  44. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +0 -1
  45. package/dist/dts/components/popout-manager/index.d.ts +0 -2
  46. package/dist/dts/components/popout-manager/index.d.ts.map +0 -1
  47. package/dist/dts/components/popout-manager/popout-manager.d.ts +0 -74
  48. package/dist/dts/components/popout-manager/popout-manager.d.ts.map +0 -1
  49. package/dist/dts/config/fallback-agents.d.ts +0 -20
  50. package/dist/dts/config/fallback-agents.d.ts.map +0 -1
  51. package/dist/esm/components/ai-driver/ai-driver.js +0 -1
  52. package/dist/esm/components/ai-driver/index.js +0 -1
  53. package/dist/esm/components/orchestrating-driver/index.js +0 -1
  54. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +0 -229
  55. package/dist/esm/components/popout-manager/index.js +0 -1
  56. package/dist/esm/components/popout-manager/popout-manager.js +0 -119
  57. package/dist/esm/config/fallback-agents.js +0 -26
  58. package/src/components/ai-driver/ai-driver.ts +0 -42
  59. package/src/components/ai-driver/index.ts +0 -1
  60. package/src/components/orchestrating-driver/index.ts +0 -1
  61. package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -300
  62. package/src/components/popout-manager/index.ts +0 -1
  63. package/src/components/popout-manager/popout-manager.ts +0 -144
  64. package/src/config/fallback-agents.ts +0 -29
@@ -1,300 +0,0 @@
1
- import type {
2
- AIProvider,
3
- ChatAttachment,
4
- ChatDriverResult,
5
- ChatMessage,
6
- ChatRequestOptions,
7
- } from '@genesislcap/foundation-ai';
8
- import type { AgentConfig, FallbackAgentConfig, SpecialistAgentConfig } from '../../config/config';
9
- import { logger } from '../../utils/logger';
10
- import type { AiDriver } from '../ai-driver/ai-driver';
11
- import { ChatDriver, REQUEST_CONTINUATION_TOOL } from '../chat-driver/chat-driver';
12
-
13
- const DEFAULT_MAX_HANDOFFS = 3;
14
- const DEFAULT_CLASSIFIER_HISTORY_LENGTH = 4;
15
- const DEFAULT_CLASSIFIER_RETRIES = 2;
16
-
17
- const REQUEST_CONTINUATION_DEFINITION = {
18
- name: REQUEST_CONTINUATION_TOOL,
19
- description:
20
- "Call this when you have completed your part of the task but fulfilling the user's full request requires capabilities outside your domain. Do not call this if you can complete the request yourself. Pass a plain-language description of what still needs to be done — another specialist will be selected automatically.",
21
- parameters: {
22
- type: 'object',
23
- required: ['summary', 'remaining_task'],
24
- properties: {
25
- summary: {
26
- type: 'string',
27
- description: 'What you found or did — passed as context to the next specialist.',
28
- },
29
- remaining_task: {
30
- type: 'string',
31
- description:
32
- 'What still needs to be done, in plain language. Used to route to the right specialist.',
33
- },
34
- },
35
- },
36
- };
37
-
38
- function isSpecialist(agent: AgentConfig): agent is SpecialistAgentConfig {
39
- return !agent.fallback;
40
- }
41
-
42
- function isFallback(agent: AgentConfig): agent is FallbackAgentConfig {
43
- return agent.fallback === true;
44
- }
45
-
46
- function buildFallbackSystemPrompt(
47
- fallback: FallbackAgentConfig,
48
- specialists: SpecialistAgentConfig[],
49
- ): string {
50
- const agentList = specialists.map((s) => `- ${s.name}: ${s.description}`).join('\n');
51
- if (fallback.systemPrompt) {
52
- return fallback.systemPrompt.replace('{{agents}}', agentList);
53
- }
54
- return `You are a helpful assistant. You cannot directly help with the user's request, but the following specialists are available:\n\n${agentList}\n\nPolitely let the user know what you can help with and invite them to rephrase their request.`;
55
- }
56
-
57
- /**
58
- * Prepares the history for a given agent by masking tool call args and results
59
- * produced by other agents — preserving conversation structure without leaking
60
- * irrelevant domain data into the active agent's context.
61
- */
62
- function transformHistoryForAgent(history: ChatMessage[], agentName: string): ChatMessage[] {
63
- return history.map((msg) => {
64
- if (!msg.agentName || msg.agentName === agentName) return msg;
65
- if (msg.toolCalls?.length) {
66
- return { ...msg, toolCalls: msg.toolCalls.map((tc) => ({ ...tc, args: {} })) };
67
- }
68
- if (msg.toolResult) {
69
- return { ...msg, content: "[other agent's tool result omitted]" };
70
- }
71
- return msg;
72
- });
73
- }
74
-
75
- /**
76
- * Orchestrates multiple specialist agents. Sits between `FoundationAiAssistant`
77
- * and `ChatDriver`, classifying each user message and routing it to the right
78
- * specialist — each with its own focused system prompt, tools, and primer.
79
- *
80
- * @beta
81
- */
82
- export class OrchestratingDriver extends EventTarget implements AiDriver {
83
- private readonly chatDriver: ChatDriver;
84
- private readonly specialists: SpecialistAgentConfig[];
85
- private readonly fallback?: FallbackAgentConfig;
86
- private readonly maxHandoffs: number;
87
- private readonly classifierHistoryLength: number;
88
- private readonly classifierRetries: number;
89
-
90
- activeAgent?: AgentConfig;
91
-
92
- constructor(
93
- private readonly aiProvider: AIProvider,
94
- private readonly agents: AgentConfig[],
95
- options: {
96
- maxHandoffs?: number;
97
- classifierHistoryLength?: number;
98
- classifierRetries?: number;
99
- maxToolIterations?: number;
100
- } = {},
101
- ) {
102
- super();
103
- this.maxHandoffs = options.maxHandoffs ?? DEFAULT_MAX_HANDOFFS;
104
- this.classifierHistoryLength =
105
- options.classifierHistoryLength ?? DEFAULT_CLASSIFIER_HISTORY_LENGTH;
106
- this.classifierRetries = options.classifierRetries ?? DEFAULT_CLASSIFIER_RETRIES;
107
-
108
- this.specialists = agents.filter(isSpecialist);
109
- const fallbacks = agents.filter(isFallback);
110
- if (fallbacks.length > 1) {
111
- logger.warn(
112
- 'OrchestratingDriver: multiple fallback agents found — only the first will be used.',
113
- );
114
- }
115
-
116
- const rawFallback = fallbacks[0];
117
- this.fallback = rawFallback
118
- ? { ...rawFallback, systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }
119
- : undefined;
120
-
121
- this.chatDriver = new ChatDriver(
122
- aiProvider,
123
- {},
124
- [],
125
- undefined,
126
- undefined,
127
- options.maxToolIterations,
128
- );
129
-
130
- // Proxy history-updated events from the shared driver
131
- this.chatDriver.addEventListener('history-updated', (e: Event) => {
132
- this.dispatchEvent(new CustomEvent('history-updated', { detail: (e as CustomEvent).detail }));
133
- });
134
- }
135
-
136
- resolveInteraction(interactionId: string, result: unknown): void {
137
- this.chatDriver.resolveInteraction(interactionId, result);
138
- }
139
-
140
- loadHistory(messages: ChatMessage[]): void {
141
- this.chatDriver.loadHistory(messages);
142
- }
143
-
144
- getRawHistory(): readonly ChatMessage[] {
145
- return this.chatDriver.getHistory();
146
- }
147
-
148
- async sendMessage(input: string, attachments?: ChatAttachment[]): Promise<ChatDriverResult> {
149
- const history = this.chatDriver.getHistory() as ChatMessage[];
150
- this.dispatchEvent(new CustomEvent('orchestrating-start'));
151
- let currentAgent = await this.classify(input, history);
152
- let isHandoff = false;
153
- let handoffs = 0;
154
- let handoffSummary = '';
155
- let remainingTask = '';
156
-
157
- while (true) {
158
- this.applyAgent(currentAgent);
159
-
160
- let result: ChatDriverResult;
161
- if (isHandoff) {
162
- const contextPrimer: ChatMessage[] = handoffSummary
163
- ? [{ role: 'user', content: `[Context from previous agent]: ${handoffSummary}` }]
164
- : [];
165
- // eslint-disable-next-line no-await-in-loop
166
- result = await this.chatDriver.continueFromHistory(contextPrimer);
167
- } else {
168
- // eslint-disable-next-line no-await-in-loop
169
- result = await this.chatDriver.sendMessage(input, attachments);
170
- }
171
-
172
- if (result.reason !== 'agent-handoff' || isFallback(currentAgent)) {
173
- break;
174
- }
175
-
176
- handoffs += 1;
177
- if (handoffs > this.maxHandoffs) {
178
- this.appendInlineMessage(
179
- `I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed (max: ${this.maxHandoffs}). Please try breaking your request into smaller steps.`,
180
- );
181
- break;
182
- }
183
-
184
- handoffSummary = result.summary;
185
- remainingTask = result.remainingTask;
186
- isHandoff = true;
187
-
188
- const updatedHistory = this.chatDriver.getHistory() as ChatMessage[];
189
- this.dispatchEvent(new CustomEvent('orchestrating-start'));
190
- // eslint-disable-next-line no-await-in-loop
191
- currentAgent = await this.classify(remainingTask, updatedHistory);
192
- }
193
-
194
- return { reason: 'done' };
195
- }
196
-
197
- async continueFromHistory(transientPrimer?: ChatMessage[]): Promise<ChatDriverResult> {
198
- return this.chatDriver.continueFromHistory(transientPrimer);
199
- }
200
-
201
- private applyAgent(agent: AgentConfig): void {
202
- // Fallback agents are terminal and should not hand off to other specialists
203
- const agentToApply = isFallback(agent)
204
- ? agent
205
- : {
206
- ...agent,
207
- toolDefinitions: [...(agent.toolDefinitions ?? []), REQUEST_CONTINUATION_DEFINITION],
208
- };
209
-
210
- // Mask history from other agents before handing context to this specialist
211
- const rawHistory = this.chatDriver.getHistory() as ChatMessage[];
212
- const maskedHistory = transformHistoryForAgent(rawHistory, agent.name);
213
-
214
- // Inject an agent switch indicator when the active specialist changes
215
- const previousAgent = this.activeAgent;
216
- if (previousAgent && previousAgent.name !== agent.name) {
217
- maskedHistory.push({ role: 'system-event', content: agent.name });
218
- }
219
-
220
- this.chatDriver.loadHistory(maskedHistory);
221
- this.chatDriver.applyAgent(agentToApply);
222
- this.activeAgent = agent;
223
- this.dispatchEvent(new CustomEvent('orchestrating-stop'));
224
- this.dispatchEvent(new CustomEvent('agent-changed', { detail: agent }));
225
- }
226
-
227
- private async classify(input: string, history: ChatMessage[]): Promise<AgentConfig> {
228
- if (this.specialists.length === 0) {
229
- return this.fallback ?? { name: 'Assistant', fallback: true };
230
- }
231
-
232
- const agentList = this.specialists
233
- .map((a, i) => `${i}: ${a.name} — ${a.description}`)
234
- .join('\n');
235
-
236
- const recentMessages = history
237
- .filter((m) => m.role === 'user' || m.role === 'assistant')
238
- .slice(-this.classifierHistoryLength)
239
- .map((m) => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
240
- .join('\n');
241
-
242
- const classifierPrompt = `You are a routing classifier. Call select_agent with the index of the best matching agent for the user message. Use -1 only if the request is entirely unrelated to every agent listed — prefer the closest match when in doubt. If the request spans multiple agents, pick the one that should act first; I will re-classify and route again once that agent is done.\n\nAgents:\n${agentList}${recentMessages ? `\n\nRecent conversation:\n${recentMessages}` : ''}`;
243
-
244
- const routingTool = {
245
- name: 'select_agent',
246
- description: 'Select the most appropriate agent for the user message.',
247
- parameters: {
248
- type: 'object',
249
- required: ['agent_index'],
250
- properties: {
251
- agent_index: {
252
- type: 'integer',
253
- description: 'Zero-based index of the matching agent, or -1 if no agent matches.',
254
- },
255
- },
256
- },
257
- };
258
-
259
- for (let attempt = 0; attempt <= this.classifierRetries; attempt += 1) {
260
- try {
261
- const options: ChatRequestOptions = {
262
- systemPrompt: classifierPrompt,
263
- tools: [routingTool],
264
- };
265
- // eslint-disable-next-line no-await-in-loop
266
- const response = await this.aiProvider.chat!([], input, options);
267
- const tc = response.toolCalls?.[0];
268
- const index = tc?.name === 'select_agent' ? (tc.args.agent_index as number) : -1;
269
-
270
- if (index >= 0 && index < this.specialists.length) {
271
- return this.specialists[index];
272
- }
273
- // index === -1 — fall through to fallback
274
- break;
275
- } catch (e) {
276
- logger.warn(`OrchestratingDriver: classifier attempt ${attempt + 1} failed:`, e);
277
- if (attempt === this.classifierRetries) {
278
- logger.error('OrchestratingDriver: classifier failed after all retries, using fallback');
279
- }
280
- }
281
- }
282
-
283
- if (this.fallback) return this.fallback;
284
-
285
- // No fallback configured — emit an inline response and return first specialist as no-op
286
- const specialistNames = this.specialists.map((s) => s.name).join(', ');
287
- this.appendInlineMessage(
288
- `I'm not sure how to help with that. I can assist with: ${specialistNames}.`,
289
- );
290
- return this.specialists[0];
291
- }
292
-
293
- private appendInlineMessage(content: string): void {
294
- const history = this.chatDriver.getHistory() as ChatMessage[];
295
- this.chatDriver.loadHistory([...history, { role: 'assistant', content }]);
296
- this.dispatchEvent(
297
- new CustomEvent('history-updated', { detail: this.chatDriver.getHistory() }),
298
- );
299
- }
300
- }
@@ -1 +0,0 @@
1
- export * from './popout-manager';
@@ -1,144 +0,0 @@
1
- import { customElement, GenesisElement, html, observable } from '@genesislcap/web-core';
2
- import { agenticActivityBus } from '../../channel/ai-activity-bus';
3
- import type { AiAssistantSerializedState } from '../../channel/ai-activity-channel';
4
-
5
- /**
6
- * Contract for pages that support docking the AI assistant.
7
- * Register an implementation with {@link getAiPopoutManager} on mount and deregister on unmount.
8
- *
9
- * @beta
10
- */
11
- export interface AiDockProvider {
12
- /**
13
- * Called when the user expands the bubble. Receives the assistant element and the
14
- * serialized state to restore. The provider is responsible for inserting the element
15
- * into its layout and calling `applyState` once the element is connected.
16
- * Resolves when the element is connected and state has been applied.
17
- */
18
- onDock(element: HTMLElement, state: AiAssistantSerializedState | undefined): Promise<void>;
19
- /** Called when the user collapses the panel or before navigation. */
20
- onUndock(): Promise<void>;
21
- /**
22
- * Called by `collapseIfDocked` before navigation. The provider is responsible for
23
- * finding the docked assistant element and calling `handlePopout()` on it, which
24
- * publishes `chat-popin` and returns the assistant to the bubble.
25
- */
26
- initiateCollapse(): void;
27
- }
28
-
29
- /**
30
- * App-shell component that owns the pop-out/pop-in lifecycle for the AI assistant bubble.
31
- *
32
- * @remarks
33
- * Place this in the persistent app shell, wrapping a `foundation-ai-chat-bubble` that
34
- * contains an AI assistant element in the `dialog-content` slot. The component will
35
- * auto-create a matching collapse-mode element for docking into pages, and will
36
- * control the expand button visibility based on whether a dock provider is registered.
37
- *
38
- * Pages that support docking call `aiPopoutManager.registerDockProvider()` on mount
39
- * and `aiPopoutManager.deregisterDockProvider()` on unmount.
40
- *
41
- * @example
42
- * ```html
43
- * <foundation-ai-popout-manager>
44
- * <foundation-ai-chat-bubble title="My Assistant">
45
- * <my-assistant slot="dialog-content"></my-assistant>
46
- * </foundation-ai-chat-bubble>
47
- * </foundation-ai-popout-manager>
48
- * ```
49
- *
50
- * @beta
51
- */
52
- @customElement({
53
- name: 'foundation-ai-popout-manager',
54
- template: html`
55
- <slot></slot>
56
- `,
57
- })
58
- export class FoundationAiPopoutManager extends GenesisElement {
59
- /** True when a dock provider is registered — controls expand button visibility. */
60
- @observable canDock = false;
61
-
62
- private collapseEl: HTMLElement | null = null;
63
- private dockProvider: AiDockProvider | null = null;
64
- private isDocked = false;
65
- private unsubBus?: () => void;
66
-
67
- connectedCallback() {
68
- super.connectedCallback();
69
- _aiPopoutManager = this;
70
-
71
- const bubble = this.querySelector('foundation-ai-chat-bubble');
72
- const assistantEl = bubble?.querySelector('[slot="dialog-content"]');
73
- if (assistantEl) {
74
- this.collapseEl = document.createElement(assistantEl.tagName.toLowerCase());
75
- this.collapseEl.setAttribute('popout-mode', 'collapse');
76
- }
77
-
78
- const unsubPopout = agenticActivityBus.subscribe('chat-popout', async ({ state }) => {
79
- if (this.isDocked || !this.dockProvider || !this.collapseEl) return;
80
- this.isDocked = true;
81
- await this.dockProvider.onDock(this.collapseEl, state);
82
- });
83
-
84
- const unsubPopin = agenticActivityBus.subscribe('chat-popin', () => {
85
- this.isDocked = false;
86
- this.dockProvider?.onUndock();
87
- });
88
-
89
- this.unsubBus = () => {
90
- unsubPopout();
91
- unsubPopin();
92
- };
93
- }
94
-
95
- disconnectedCallback() {
96
- super.disconnectedCallback();
97
- this.unsubBus?.();
98
- this.unsubBus = undefined;
99
- _aiPopoutManager = undefined;
100
- }
101
-
102
- canDockChanged() {
103
- const bubble = this.querySelector('foundation-ai-chat-bubble');
104
- const assistantEl = bubble?.querySelector('[slot="dialog-content"]');
105
- if (this.canDock) {
106
- assistantEl?.setAttribute('popout-mode', 'expand');
107
- } else {
108
- assistantEl?.removeAttribute('popout-mode');
109
- }
110
- }
111
-
112
- registerDockProvider(provider: AiDockProvider): void {
113
- this.dockProvider = provider;
114
- this.canDock = true;
115
- }
116
-
117
- deregisterDockProvider(): void {
118
- this.isDocked = false;
119
- this.dockProvider = null;
120
- this.canDock = false;
121
- }
122
-
123
- /**
124
- * If the assistant is currently docked, collapses it back into the bubble.
125
- * Await this in `onBeforeNavButtonClick` to ensure cleanup before navigation.
126
- */
127
- collapseIfDocked(): Promise<void> {
128
- if (!this.isDocked || !this.dockProvider) return Promise.resolve();
129
- this.dockProvider.initiateCollapse();
130
- return Promise.resolve();
131
- }
132
- }
133
-
134
- let _aiPopoutManager: FoundationAiPopoutManager | undefined;
135
-
136
- /**
137
- * Returns the active `FoundationAiPopoutManager` instance, or `undefined` if none is mounted.
138
- * Import this in pages to call `registerDockProvider` / `deregisterDockProvider`.
139
- *
140
- * @beta
141
- */
142
- export function getAiPopoutManager(): FoundationAiPopoutManager | undefined {
143
- return _aiPopoutManager;
144
- }
@@ -1,29 +0,0 @@
1
- import type { FallbackAgentConfig } from './config';
2
-
3
- /**
4
- * A warm, conversational fallback agent. Acknowledges it can't help directly,
5
- * explains what the available specialists can do, and invites the user to retry.
6
- *
7
- * The system prompt is generated at runtime by `OrchestratingDriver` from the
8
- * specialist list — no manual authoring required.
9
- *
10
- * @beta
11
- */
12
- export const friendlyFallbackAgent: FallbackAgentConfig = {
13
- name: 'Assistant',
14
- fallback: true,
15
- systemPrompt: `You are a warm, friendly assistant. For general conversation like "Hi" or "How are you", you can have a friendly chat. For anything more complex, you cannot directly help, but you can explain what specialists are available. The available specialists are:\n\n{{agents}}\n\nPolitely let the user know what you can help with and invite them to rephrase their request if it is outside your scope.`,
16
- };
17
-
18
- /**
19
- * A brief, professional fallback agent. States what is available without
20
- * elaboration. The `{{agents}}` placeholder is replaced at runtime by
21
- * `OrchestratingDriver` with the generated specialist list.
22
- *
23
- * @beta
24
- */
25
- export const strictFallbackAgent: FallbackAgentConfig = {
26
- name: 'Assistant',
27
- fallback: true,
28
- systemPrompt: `You are a concise assistant. The user's request is outside your scope. Respond with a single short sentence stating what you can assist with. Do not elaborate. The available specialists are: {{agents}}.`,
29
- };