@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
@@ -16,8 +16,8 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
16
16
  this.componentName = '';
17
17
  /** @internal */
18
18
  this.interactionId = '';
19
- /** When true, the interaction has already been resolved. Forwarded to the rendered component. @internal */
20
- this.resolved = false;
19
+ /** The resolved result once the interaction has completed. Forwarded to the rendered component. @internal */
20
+ this.resolved = undefined;
21
21
  }
22
22
  connectedCallback() {
23
23
  super.connectedCallback();
@@ -71,7 +71,7 @@ const styles = css `
71
71
  let AiChatMarkdown = class AiChatMarkdown extends GenesisElement {
72
72
  constructor() {
73
73
  super(...arguments);
74
- /** @internal */
74
+ /** The markdown string to render. */
75
75
  this.content = '';
76
76
  }
77
77
  /** @internal */
@@ -2,6 +2,10 @@ var AiHaloOverlay_1;
2
2
  import { __decorate } from "tslib";
3
3
  import { attr, css, customElement, GenesisElement, html } from '@genesislcap/web-core';
4
4
  import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
5
+ const HALO_DEFAULT_SPEED = 1.5;
6
+ const HALO_DEFAULT_BORDER_SIZE = 3;
7
+ const HALO_DEFAULT_GLOW_OPACITY = 0.35;
8
+ const HALO_DEFAULT_GLOW_SPREAD = 70;
5
9
  /**
6
10
  * Animated halo overlay — rotating conic-gradient border with an inward glow.
7
11
  *
@@ -19,8 +23,27 @@ let AiHaloOverlay = AiHaloOverlay_1 = class AiHaloOverlay extends GenesisElement
19
23
  constructor() {
20
24
  super(...arguments);
21
25
  this.active = false;
26
+ /** Rotation speed in degrees per frame. Default: 1.5 (≈ 4 s per full revolution at 60 fps). */
27
+ this.speed = HALO_DEFAULT_SPEED;
28
+ /** Rotation direction. Default: 'cw' (clockwise). */
29
+ this.direction = 'cw';
30
+ /** Border thickness in px. Default: 3. */
31
+ this.borderSize = HALO_DEFAULT_BORDER_SIZE;
32
+ /** Glow layer opacity (0–1). Default: 0.35. */
33
+ this.glowOpacity = HALO_DEFAULT_GLOW_OPACITY;
34
+ /** Transparent stop of the radial glow mask as a percentage (0–100). Higher = less spread. Default: 70. */
35
+ this.glowSpread = HALO_DEFAULT_GLOW_SPREAD;
22
36
  this.angle = 0;
23
37
  }
38
+ borderSizeChanged() {
39
+ this.style.setProperty('--halo-border-size', `${this.borderSize}px`);
40
+ }
41
+ glowOpacityChanged() {
42
+ this.style.setProperty('--halo-glow-opacity', String(this.glowOpacity));
43
+ }
44
+ glowSpreadChanged() {
45
+ this.style.setProperty('--halo-glow-spread', `${this.glowSpread}%`);
46
+ }
24
47
  connectedCallback() {
25
48
  super.connectedCallback();
26
49
  this.tick();
@@ -32,7 +55,9 @@ let AiHaloOverlay = AiHaloOverlay_1 = class AiHaloOverlay extends GenesisElement
32
55
  }
33
56
  }
34
57
  tick() {
35
- this.angle = (this.angle + AiHaloOverlay_1.DEG_PER_FRAME) % AiHaloOverlay_1.FULL_ROTATION_DEG;
58
+ const step = this.direction === 'ccw' ? -this.speed : this.speed;
59
+ this.angle =
60
+ (this.angle + step + AiHaloOverlay_1.FULL_ROTATION_DEG) % AiHaloOverlay_1.FULL_ROTATION_DEG;
36
61
  this.style.setProperty('--halo-angle', `${this.angle}deg`);
37
62
  this.animFrame = requestAnimationFrame(() => this.tick());
38
63
  }
@@ -42,12 +67,25 @@ let AiHaloOverlay = AiHaloOverlay_1 = class AiHaloOverlay extends GenesisElement
42
67
  // 2. The loop ticks even when `active` is false (idle cost is negligible but non-zero).
43
68
  // Once @property inside Shadow DOM has solid cross-browser support, consider switching back
44
69
  // to a CSS @keyframes approach and removing connectedCallback/disconnectedCallback/tick().
45
- // 1.5° per frame @ 60 fps ≈ 4 s per full revolution
46
- AiHaloOverlay.DEG_PER_FRAME = 1.5;
47
70
  AiHaloOverlay.FULL_ROTATION_DEG = 360;
48
71
  __decorate([
49
72
  attr({ mode: 'boolean' })
50
73
  ], AiHaloOverlay.prototype, "active", void 0);
74
+ __decorate([
75
+ attr({ converter: { fromView: Number, toView: String } })
76
+ ], AiHaloOverlay.prototype, "speed", void 0);
77
+ __decorate([
78
+ attr
79
+ ], AiHaloOverlay.prototype, "direction", void 0);
80
+ __decorate([
81
+ attr({ attribute: 'border-size', converter: { fromView: Number, toView: String } })
82
+ ], AiHaloOverlay.prototype, "borderSize", void 0);
83
+ __decorate([
84
+ attr({ attribute: 'glow-opacity', converter: { fromView: Number, toView: String } })
85
+ ], AiHaloOverlay.prototype, "glowOpacity", void 0);
86
+ __decorate([
87
+ attr({ attribute: 'glow-spread', converter: { fromView: Number, toView: String } })
88
+ ], AiHaloOverlay.prototype, "glowSpread", void 0);
51
89
  AiHaloOverlay = AiHaloOverlay_1 = __decorate([
52
90
  customElement({
53
91
  name: 'ai-halo-overlay',
@@ -76,7 +114,7 @@ AiHaloOverlay = AiHaloOverlay_1 = __decorate([
76
114
  position: absolute;
77
115
  inset: 0;
78
116
  border-radius: inherit;
79
- padding: 3px;
117
+ padding: var(--halo-border-size, 3px);
80
118
  background: repeating-conic-gradient(
81
119
  from var(--halo-angle, 0deg),
82
120
  ${AI_COLOUR_AMBER},
@@ -109,9 +147,17 @@ AiHaloOverlay = AiHaloOverlay_1 = __decorate([
109
147
  ${AI_COLOUR_VIOLET},
110
148
  ${AI_COLOUR_AMBER}
111
149
  );
112
- opacity: 0.35;
113
- -webkit-mask-image: radial-gradient(ellipse at center, transparent 70%, black 100%);
114
- mask-image: radial-gradient(ellipse at center, transparent 70%, black 100%);
150
+ opacity: var(--halo-glow-opacity, 0.35);
151
+ -webkit-mask-image: radial-gradient(
152
+ ellipse at center,
153
+ transparent var(--halo-glow-spread, 70%),
154
+ black 100%
155
+ );
156
+ mask-image: radial-gradient(
157
+ ellipse at center,
158
+ transparent var(--halo-glow-spread, 70%),
159
+ black 100%
160
+ );
115
161
  }
116
162
  `,
117
163
  })
@@ -0,0 +1 @@
1
+ export { OrchestratingDriver } from './orchestrating-driver';
@@ -0,0 +1,247 @@
1
+ import { __awaiter } from "tslib";
2
+ import { logger } from '../../utils/logger';
3
+ import { ChatDriver, REQUEST_CONTINUATION_TOOL } from '../chat-driver/chat-driver';
4
+ const DEFAULT_MAX_HANDOFFS = 3;
5
+ const DEFAULT_CLASSIFIER_HISTORY_LENGTH = 4;
6
+ const DEFAULT_CLASSIFIER_RETRIES = 2;
7
+ const REQUEST_CONTINUATION_DEFINITION = {
8
+ name: REQUEST_CONTINUATION_TOOL,
9
+ description: "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.",
10
+ parameters: {
11
+ type: 'object',
12
+ required: ['summary', 'remaining_task'],
13
+ properties: {
14
+ summary: {
15
+ type: 'string',
16
+ description: 'What you found or did — passed as context to the next specialist.',
17
+ },
18
+ remaining_task: {
19
+ type: 'string',
20
+ description: 'What still needs to be done, in plain language. Used to route to the right specialist.',
21
+ },
22
+ },
23
+ },
24
+ };
25
+ function isSpecialist(agent) {
26
+ return !agent.fallback;
27
+ }
28
+ function isFallback(agent) {
29
+ return agent.fallback === true;
30
+ }
31
+ function buildFallbackSystemPrompt(fallback, specialists) {
32
+ const agentList = specialists.map((s) => `- ${s.name}: ${s.description}`).join('\n');
33
+ if (fallback.systemPrompt) {
34
+ return fallback.systemPrompt.replace('{{agents}}', agentList);
35
+ }
36
+ 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.`;
37
+ }
38
+ /**
39
+ * Prepares history for the LLM only: masks tool call args and results from other
40
+ * agents so the active specialist is not confused by tools it does not have.
41
+ * Canonical history in `ChatDriver` stays unmasked for UI and logging.
42
+ */
43
+ function transformHistoryForAgent(history, agentName) {
44
+ return history.map((msg) => {
45
+ var _a;
46
+ if (!msg.agentName || msg.agentName === agentName)
47
+ return msg;
48
+ if ((_a = msg.toolCalls) === null || _a === void 0 ? void 0 : _a.length) {
49
+ return Object.assign(Object.assign({}, msg), { toolCalls: msg.toolCalls.map((tc) => (Object.assign(Object.assign({}, tc), { args: {} }))) });
50
+ }
51
+ if (msg.toolResult) {
52
+ return Object.assign(Object.assign({}, msg), { toolResult: Object.assign(Object.assign({}, msg.toolResult), { content: "[other agent's tool result omitted]" }) });
53
+ }
54
+ return msg;
55
+ });
56
+ }
57
+ /**
58
+ * Orchestrates multiple specialist agents. Sits between `FoundationAiAssistant`
59
+ * and `ChatDriver`, classifying each user message and routing it to the right
60
+ * specialist — each with its own focused system prompt, tools, and primer.
61
+ *
62
+ * @beta
63
+ */
64
+ export class OrchestratingDriver extends EventTarget {
65
+ constructor(aiProvider, agents, options = {}) {
66
+ var _a, _b, _c;
67
+ super();
68
+ this.aiProvider = aiProvider;
69
+ this.agents = agents;
70
+ this.maxHandoffs = (_a = options.maxHandoffs) !== null && _a !== void 0 ? _a : DEFAULT_MAX_HANDOFFS;
71
+ this.classifierHistoryLength =
72
+ (_b = options.classifierHistoryLength) !== null && _b !== void 0 ? _b : DEFAULT_CLASSIFIER_HISTORY_LENGTH;
73
+ this.classifierRetries = (_c = options.classifierRetries) !== null && _c !== void 0 ? _c : DEFAULT_CLASSIFIER_RETRIES;
74
+ this.specialists = agents.filter(isSpecialist);
75
+ const fallbacks = agents.filter(isFallback);
76
+ if (fallbacks.length > 1) {
77
+ logger.warn('OrchestratingDriver: multiple fallback agents found — only the first will be used.');
78
+ }
79
+ const rawFallback = fallbacks[0];
80
+ this.fallback = rawFallback
81
+ ? Object.assign(Object.assign({}, rawFallback), { systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }) : undefined;
82
+ this.chatDriver = new ChatDriver(aiProvider, {}, [], undefined, undefined, options.maxToolIterations, options.maxFoldOperations);
83
+ // Proxy history-updated events from the shared driver
84
+ this.chatDriver.addEventListener('history-updated', (e) => {
85
+ this.dispatchEvent(new CustomEvent('history-updated', { detail: e.detail }));
86
+ });
87
+ }
88
+ resolveInteraction(interactionId, result) {
89
+ this.chatDriver.resolveInteraction(interactionId, result);
90
+ }
91
+ isBusy() {
92
+ return this.chatDriver.isBusy();
93
+ }
94
+ loadHistory(messages) {
95
+ this.chatDriver.loadHistory(messages);
96
+ }
97
+ getRawHistory() {
98
+ return this.chatDriver.getHistory();
99
+ }
100
+ getSuggestions(history, prompt, count, allAgentInfo) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ const agentInfo = this.specialists.map((s) => {
103
+ var _a;
104
+ return ({
105
+ name: s.name,
106
+ description: s.description,
107
+ tools: (_a = s.toolDefinitions) !== null && _a !== void 0 ? _a : [],
108
+ });
109
+ });
110
+ return this.chatDriver.getSuggestions(history, prompt, count, agentInfo);
111
+ });
112
+ }
113
+ sendMessage(input, attachments) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ const history = this.chatDriver.getHistory();
116
+ // Emit the user message immediately so the UI reflects it during the classify
117
+ // round-trip — without this the chat appears frozen until classify returns.
118
+ this.dispatchEvent(new CustomEvent('history-updated', {
119
+ detail: [...history, { role: 'user', content: input, attachments }],
120
+ }));
121
+ this.dispatchEvent(new CustomEvent('orchestrating-start'));
122
+ let currentAgent = yield this.classify(input, history);
123
+ let isHandoff = false;
124
+ let handoffs = 0;
125
+ let handoffSummary = '';
126
+ let remainingTask = '';
127
+ while (true) {
128
+ this.applyAgent(currentAgent);
129
+ let result;
130
+ if (isHandoff) {
131
+ const contextPrimer = handoffSummary
132
+ ? [{ role: 'user', content: `[Context from previous agent]: ${handoffSummary}` }]
133
+ : [];
134
+ // eslint-disable-next-line no-await-in-loop
135
+ result = yield this.chatDriver.continueFromHistory(contextPrimer);
136
+ }
137
+ else {
138
+ // eslint-disable-next-line no-await-in-loop
139
+ result = yield this.chatDriver.sendMessage(input, attachments);
140
+ }
141
+ if (result.reason !== 'agent-handoff' || isFallback(currentAgent)) {
142
+ break;
143
+ }
144
+ handoffs += 1;
145
+ if (handoffs > this.maxHandoffs) {
146
+ this.appendInlineMessage(`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.`);
147
+ break;
148
+ }
149
+ handoffSummary = result.summary;
150
+ remainingTask = result.remainingTask;
151
+ isHandoff = true;
152
+ const updatedHistory = this.chatDriver.getHistory();
153
+ this.dispatchEvent(new CustomEvent('orchestrating-start'));
154
+ // eslint-disable-next-line no-await-in-loop
155
+ currentAgent = yield this.classify(remainingTask, updatedHistory);
156
+ }
157
+ return { reason: 'done' };
158
+ });
159
+ }
160
+ continueFromHistory(transientPrimer) {
161
+ return __awaiter(this, void 0, void 0, function* () {
162
+ return this.chatDriver.continueFromHistory(transientPrimer);
163
+ });
164
+ }
165
+ applyAgent(agent) {
166
+ var _a;
167
+ // Fallback agents are terminal and should not hand off to other specialists
168
+ const agentToApply = isFallback(agent)
169
+ ? agent
170
+ : Object.assign(Object.assign({}, agent), { toolDefinitions: [...((_a = agent.toolDefinitions) !== null && _a !== void 0 ? _a : []), REQUEST_CONTINUATION_DEFINITION] });
171
+ const previousAgent = this.activeAgent;
172
+ if (previousAgent && previousAgent.name !== agent.name) {
173
+ const rawHistory = this.chatDriver.getHistory();
174
+ this.chatDriver.loadHistory([...rawHistory, { role: 'system-event', content: agent.name }]);
175
+ }
176
+ this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
177
+ this.chatDriver.applyAgent(agentToApply);
178
+ this.activeAgent = agent;
179
+ this.dispatchEvent(new CustomEvent('orchestrating-stop'));
180
+ this.dispatchEvent(new CustomEvent('agent-changed', { detail: agent }));
181
+ }
182
+ classify(input, history) {
183
+ return __awaiter(this, void 0, void 0, function* () {
184
+ var _a, _b;
185
+ if (this.specialists.length === 0) {
186
+ return (_a = this.fallback) !== null && _a !== void 0 ? _a : { name: 'Assistant', fallback: true };
187
+ }
188
+ const agentList = this.specialists
189
+ .map((a, i) => `${i}: ${a.name} — ${a.description}`)
190
+ .join('\n');
191
+ const recentMessages = history
192
+ .filter((m) => m.role === 'user' || m.role === 'assistant')
193
+ .slice(-this.classifierHistoryLength)
194
+ .map((m) => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
195
+ .join('\n');
196
+ 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}` : ''}`;
197
+ const routingTool = {
198
+ name: 'select_agent',
199
+ description: 'Select the most appropriate agent for the user message.',
200
+ parameters: {
201
+ type: 'object',
202
+ required: ['agent_index'],
203
+ properties: {
204
+ agent_index: {
205
+ type: 'integer',
206
+ description: 'Zero-based index of the matching agent, or -1 if no agent matches.',
207
+ },
208
+ },
209
+ },
210
+ };
211
+ for (let attempt = 0; attempt <= this.classifierRetries; attempt += 1) {
212
+ try {
213
+ const options = {
214
+ systemPrompt: classifierPrompt,
215
+ tools: [routingTool],
216
+ };
217
+ // eslint-disable-next-line no-await-in-loop
218
+ const response = yield this.aiProvider.chat([], input, options);
219
+ const tc = (_b = response.toolCalls) === null || _b === void 0 ? void 0 : _b[0];
220
+ const index = (tc === null || tc === void 0 ? void 0 : tc.name) === 'select_agent' ? tc.args.agent_index : -1;
221
+ if (index >= 0 && index < this.specialists.length) {
222
+ return this.specialists[index];
223
+ }
224
+ // index === -1 — fall through to fallback
225
+ break;
226
+ }
227
+ catch (e) {
228
+ logger.warn(`OrchestratingDriver: classifier attempt ${attempt + 1} failed:`, e);
229
+ if (attempt === this.classifierRetries) {
230
+ logger.error('OrchestratingDriver: classifier failed after all retries, using fallback');
231
+ }
232
+ }
233
+ }
234
+ if (this.fallback)
235
+ return this.fallback;
236
+ // No fallback configured — emit an inline response and return first specialist as no-op
237
+ const specialistNames = this.specialists.map((s) => s.name).join(', ');
238
+ this.appendInlineMessage(`I'm not sure how to help with that. I can assist with: ${specialistNames}.`);
239
+ return this.specialists[0];
240
+ });
241
+ }
242
+ appendInlineMessage(content) {
243
+ const history = this.chatDriver.getHistory();
244
+ this.chatDriver.loadHistory([...history, { role: 'assistant', content }]);
245
+ this.dispatchEvent(new CustomEvent('history-updated', { detail: this.chatDriver.getHistory() }));
246
+ }
247
+ }
@@ -0,0 +1 @@
1
+ export * from './popout-manager';
@@ -0,0 +1,126 @@
1
+ import { __awaiter, __decorate } from "tslib";
2
+ import { customElement, GenesisElement, html, observable } from '@genesislcap/web-core';
3
+ import { agenticActivityBus } from '../../channel/ai-activity-bus';
4
+ /**
5
+ * App-shell component that owns the pop-out/pop-in lifecycle for the AI assistant bubble.
6
+ *
7
+ * @remarks
8
+ * Place this in the persistent app shell, wrapping a `foundation-ai-chat-bubble` that
9
+ * contains an AI assistant element in the `dialog-content` slot. The component will
10
+ * auto-create a matching collapse-mode element for docking into pages, and will
11
+ * control the expand button visibility based on whether a dock provider is registered.
12
+ *
13
+ * Pages that support docking call `aiPopoutManager.registerDockProvider()` on mount
14
+ * and `aiPopoutManager.deregisterDockProvider()` on unmount.
15
+ *
16
+ * @example
17
+ * ```html
18
+ * <foundation-ai-popout-manager>
19
+ * <foundation-ai-chat-bubble title="My Assistant">
20
+ * <my-assistant slot="dialog-content"></my-assistant>
21
+ * </foundation-ai-chat-bubble>
22
+ * </foundation-ai-popout-manager>
23
+ * ```
24
+ *
25
+ * @beta
26
+ */
27
+ let FoundationAiPopoutManager = class FoundationAiPopoutManager extends GenesisElement {
28
+ constructor() {
29
+ super(...arguments);
30
+ /** True when a dock provider is registered — controls expand button visibility. */
31
+ this.canDock = false;
32
+ this.collapseEl = null;
33
+ this.dockProvider = null;
34
+ this.isDocked = false;
35
+ }
36
+ connectedCallback() {
37
+ super.connectedCallback();
38
+ _aiPopoutManager = this;
39
+ const bubble = this.querySelector('foundation-ai-chat-bubble');
40
+ const assistantEl = bubble === null || bubble === void 0 ? void 0 : bubble.querySelector('[slot="dialog-content"]');
41
+ if (assistantEl) {
42
+ this.collapseEl = document.createElement(assistantEl.tagName.toLowerCase());
43
+ this.collapseEl.setAttribute('popout-mode', 'collapse');
44
+ // Copy identity attributes so the collapse element shares the same session
45
+ // store and driver registry key as the expand-mode element in the bubble.
46
+ const headerTitle = assistantEl.getAttribute('header-title');
47
+ if (headerTitle)
48
+ this.collapseEl.setAttribute('header-title', headerTitle);
49
+ if (assistantEl.id)
50
+ this.collapseEl.id = assistantEl.id;
51
+ }
52
+ const unsubPopout = agenticActivityBus.subscribe('chat-popout', () => __awaiter(this, void 0, void 0, function* () {
53
+ if (this.isDocked || !this.dockProvider || !this.collapseEl)
54
+ return;
55
+ this.isDocked = true;
56
+ yield this.dockProvider.onDock(this.collapseEl);
57
+ }));
58
+ const unsubPopin = agenticActivityBus.subscribe('chat-popin', () => {
59
+ var _a;
60
+ this.isDocked = false;
61
+ (_a = this.dockProvider) === null || _a === void 0 ? void 0 : _a.onUndock();
62
+ });
63
+ this.unsubBus = () => {
64
+ unsubPopout();
65
+ unsubPopin();
66
+ };
67
+ }
68
+ disconnectedCallback() {
69
+ var _a;
70
+ super.disconnectedCallback();
71
+ (_a = this.unsubBus) === null || _a === void 0 ? void 0 : _a.call(this);
72
+ this.unsubBus = undefined;
73
+ _aiPopoutManager = undefined;
74
+ }
75
+ canDockChanged() {
76
+ const bubble = this.querySelector('foundation-ai-chat-bubble');
77
+ const assistantEl = bubble === null || bubble === void 0 ? void 0 : bubble.querySelector('[slot="dialog-content"]');
78
+ if (this.canDock) {
79
+ assistantEl === null || assistantEl === void 0 ? void 0 : assistantEl.setAttribute('popout-mode', 'expand');
80
+ }
81
+ else {
82
+ assistantEl === null || assistantEl === void 0 ? void 0 : assistantEl.removeAttribute('popout-mode');
83
+ }
84
+ }
85
+ registerDockProvider(provider) {
86
+ this.dockProvider = provider;
87
+ this.canDock = true;
88
+ }
89
+ deregisterDockProvider() {
90
+ this.isDocked = false;
91
+ this.dockProvider = null;
92
+ this.canDock = false;
93
+ }
94
+ /**
95
+ * If the assistant is currently docked, collapses it back into the bubble.
96
+ * Await this in `onBeforeNavButtonClick` to ensure cleanup before navigation.
97
+ */
98
+ collapseIfDocked() {
99
+ if (!this.isDocked || !this.dockProvider)
100
+ return Promise.resolve();
101
+ this.dockProvider.initiateCollapse();
102
+ return Promise.resolve();
103
+ }
104
+ };
105
+ __decorate([
106
+ observable
107
+ ], FoundationAiPopoutManager.prototype, "canDock", void 0);
108
+ FoundationAiPopoutManager = __decorate([
109
+ customElement({
110
+ name: 'foundation-ai-popout-manager',
111
+ template: html `
112
+ <slot></slot>
113
+ `,
114
+ })
115
+ ], FoundationAiPopoutManager);
116
+ export { FoundationAiPopoutManager };
117
+ let _aiPopoutManager;
118
+ /**
119
+ * Returns the active `FoundationAiPopoutManager` instance, or `undefined` if none is mounted.
120
+ * Import this in pages to call `registerDockProvider` / `deregisterDockProvider`.
121
+ *
122
+ * @beta
123
+ */
124
+ export function getAiPopoutManager() {
125
+ return _aiPopoutManager;
126
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A warm, conversational fallback agent. Acknowledges it can't help directly,
3
+ * explains what the available specialists can do, and invites the user to retry.
4
+ *
5
+ * The system prompt is generated at runtime by `OrchestratingDriver` from the
6
+ * specialist list — no manual authoring required.
7
+ *
8
+ * @beta
9
+ */
10
+ export const friendlyFallbackAgent = {
11
+ name: 'Assistant',
12
+ fallback: true,
13
+ 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.`,
14
+ };
15
+ /**
16
+ * A brief, professional fallback agent. States what is available without
17
+ * elaboration. The `{{agents}}` placeholder is replaced at runtime by
18
+ * `OrchestratingDriver` with the generated specialist list.
19
+ *
20
+ * @beta
21
+ */
22
+ export const strictFallbackAgent = {
23
+ name: 'Assistant',
24
+ fallback: true,
25
+ 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}}.`,
26
+ };
@@ -1 +1,2 @@
1
1
  export * from './config';
2
+ export * from './fallback-agents';
package/dist/esm/index.js CHANGED
@@ -1,7 +1,13 @@
1
1
  export * from './main/main';
2
2
  export * from './main/main.types';
3
3
  export * from './main/main.template';
4
+ export * from './components/ai-driver';
4
5
  export * from './components/chat-driver';
6
+ export * from './components/orchestrating-driver';
7
+ export * from './components/popout-manager';
5
8
  export * from './channel/ai-activity-channel';
6
9
  export * from './channel/ai-activity-bus';
7
10
  export * from './config/config';
11
+ export * from './config/fallback-agents';
12
+ export * from './utils/tool-fold';
13
+ export { AiChatMarkdown } from './components/chat-markdown/chat-markdown';