@genesislcap/ai-assistant 14.421.1 → 14.422.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +191 -1
- package/dist/ai-assistant.d.ts +60 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts +33 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +20 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +6 -0
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts +16 -0
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +6 -0
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/session-store.d.ts +2 -0
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/history-transform.d.ts +13 -0
- package/dist/dts/utils/history-transform.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +127 -16
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +8 -20
- package/dist/esm/config/config.js +18 -1
- package/dist/esm/main/main.js +43 -11
- package/dist/esm/main/main.styles.js +62 -0
- package/dist/esm/main/main.template.js +122 -71
- package/dist/esm/state/ai-assistant-slice.js +8 -0
- package/dist/esm/utils/history-transform.js +35 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/sub_agent.md +149 -211
- package/package.json +16 -16
- package/src/components/chat-driver/chat-driver.ts +169 -15
- package/src/components/orchestrating-driver/orchestrating-driver.ts +10 -22
- package/src/config/config.ts +24 -0
- package/src/main/main.styles.ts +62 -0
- package/src/main/main.template.ts +189 -117
- package/src/main/main.ts +43 -9
- package/src/state/ai-assistant-slice.ts +12 -0
- package/src/utils/history-transform.ts +40 -0
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whitespace in FAST templates
|
|
3
|
+
* ────────────────────────────
|
|
4
|
+
* The formatter adds newlines/indentation to static template string parts,
|
|
5
|
+
* which FAST renders as real text nodes. With `white-space: pre-wrap` these
|
|
6
|
+
* are visible as blank lines. To avoid this:
|
|
7
|
+
*
|
|
8
|
+
* - Apply `white-space: pre-wrap` only on leaf elements whose *content* needs
|
|
9
|
+
* it, never on containers.
|
|
10
|
+
* - Inside pre-wrap elements keep content on the same line as the opening tag:
|
|
11
|
+
* GOOD `<div>${expr}</div>` / BAD `<div>\n ${expr}\n</div>`
|
|
12
|
+
* - Extract shared or deeply-nested templates as named module-level consts to
|
|
13
|
+
* keep indentation shallow.
|
|
14
|
+
* - Chain adjacent `when()` calls as `${when(a,tA)}${when(b,tB)}` — separate
|
|
15
|
+
* lines let the formatter inject whitespace between them.
|
|
16
|
+
*/
|
|
17
|
+
|
|
1
18
|
import { isChatToolCallUnknown } from '@genesislcap/foundation-ai';
|
|
2
19
|
import type { ChatAttachment, ChatMessage, ChatToolCall } from '@genesislcap/foundation-ai';
|
|
3
20
|
import { html, ref, repeat, when, ViewTemplate } from '@genesislcap/web-core';
|
|
@@ -72,6 +89,64 @@ const senderLabel: Record<string, string> = {
|
|
|
72
89
|
ai: 'Assistant',
|
|
73
90
|
};
|
|
74
91
|
|
|
92
|
+
// ─── Sub-agent trace fragments ────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
const subAgentAssistantTemplate = html<ChatMessage>`
|
|
95
|
+
<div class="sub-agent-message sub-agent-assistant">${(m) => m.content}</div>
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const subAgentToolCallTemplate = html<ChatMessage>`
|
|
99
|
+
<div class="sub-agent-message sub-agent-tool-call">
|
|
100
|
+
${repeat(
|
|
101
|
+
(m) => m.toolCalls!,
|
|
102
|
+
html<ChatToolCall>`
|
|
103
|
+
<span class="sub-agent-tool-name">${(tc) => tc.name}</span>
|
|
104
|
+
`,
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
const subAgentToolResultTemplate = html<ChatMessage>`
|
|
110
|
+
<div class="sub-agent-message sub-agent-tool-result">${(m) => m.toolResult!.content}</div>
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
/** Renders one row of a sub-agent trace (assistant text, tool call, or tool result). */
|
|
114
|
+
const subAgentMessageRowTemplate = html<ChatMessage>`
|
|
115
|
+
${when(
|
|
116
|
+
(m) => m.role === 'assistant' && !m.toolCalls?.length && !!m.content,
|
|
117
|
+
subAgentAssistantTemplate,
|
|
118
|
+
)}${when((m) => !!m.toolCalls?.length, subAgentToolCallTemplate)}${when(
|
|
119
|
+
(m) => m.role === 'tool' && !!m.toolResult?.content,
|
|
120
|
+
subAgentToolResultTemplate,
|
|
121
|
+
)}
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
/** Collapsed <details> trace shown inside a tool-call card once the sub-agent finishes. */
|
|
125
|
+
const subAgentTraceTemplate = html<ChatToolCall>`
|
|
126
|
+
<details class="sub-agent-trace">
|
|
127
|
+
<summary class="sub-agent-trace-summary">
|
|
128
|
+
${(tc) => tc.subAgentTrace![0]?.agentName ?? 'Sub-agent'} trace
|
|
129
|
+
</summary>
|
|
130
|
+
${repeat(
|
|
131
|
+
(tc) => tc.subAgentTrace!.filter((m) => m.role !== 'user'),
|
|
132
|
+
subAgentMessageRowTemplate,
|
|
133
|
+
)}
|
|
134
|
+
</details>
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
/** Live streaming trace panel shown while a sub-agent is actively running. */
|
|
138
|
+
const liveSubAgentTraceTemplate = html<FoundationAiAssistant>`
|
|
139
|
+
<div class="live-sub-agent-trace" part="live-sub-agent-trace">
|
|
140
|
+
<span class="live-sub-agent-name">${(x) => x.liveSubAgentName ?? 'Sub-agent'}</span>
|
|
141
|
+
${repeat(
|
|
142
|
+
(x) => x.liveSubAgentTrace.filter((m) => m.role !== 'user'),
|
|
143
|
+
subAgentMessageRowTemplate,
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
// ─── Public factory ───────────────────────────────────────────────────────────
|
|
149
|
+
|
|
75
150
|
/** @internal */
|
|
76
151
|
export const FoundationAiAssistantTemplate = (
|
|
77
152
|
designSystemPrefix: string,
|
|
@@ -83,6 +158,118 @@ export const FoundationAiAssistantTemplate = (
|
|
|
83
158
|
const iconTag = `${designSystemPrefix}-icon`;
|
|
84
159
|
const progressTag = `${designSystemPrefix}-progress`;
|
|
85
160
|
|
|
161
|
+
// ── Tool call item ──────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
const toolCallItemTemplate = html<ChatToolCall>`
|
|
164
|
+
${when(
|
|
165
|
+
(tc) => tc.foldEvent === 'open',
|
|
166
|
+
html<ChatToolCall>`
|
|
167
|
+
<pre class="payload fold-event fold-open">► ${(tc) => tc.name}</pre>
|
|
168
|
+
`,
|
|
169
|
+
)}
|
|
170
|
+
${when(
|
|
171
|
+
(tc) => tc.foldEvent === 'close',
|
|
172
|
+
html<ChatToolCall>`
|
|
173
|
+
<pre class="payload fold-event fold-close">◄ ${(tc) => tc.name}</pre>
|
|
174
|
+
`,
|
|
175
|
+
)}
|
|
176
|
+
${when(
|
|
177
|
+
(tc) => !tc.foldEvent && !isChatToolCallUnknown(tc),
|
|
178
|
+
html<ChatToolCall>`
|
|
179
|
+
<pre class="payload">
|
|
180
|
+
${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<strong>${(tc) =>
|
|
181
|
+
tc.name}</strong>(${(tc) => JSON.stringify(tc.args, null, 2)})</pre
|
|
182
|
+
>
|
|
183
|
+
`,
|
|
184
|
+
)}
|
|
185
|
+
${when(
|
|
186
|
+
isChatToolCallUnknown,
|
|
187
|
+
html<ChatToolCall>`
|
|
188
|
+
<pre class="payload unknown-tool">${(tc) => unknownToolPayload(tc)}</pre>
|
|
189
|
+
`,
|
|
190
|
+
)}
|
|
191
|
+
${when((tc) => !!tc.subAgentTrace?.length, subAgentTraceTemplate)}
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
// ── Message row ─────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
const messageRowTemplate = html<ChatMessage, FoundationAiAssistant>`
|
|
197
|
+
${when(
|
|
198
|
+
(m) => m.role === 'system-event',
|
|
199
|
+
html<ChatMessage>`
|
|
200
|
+
<div class="agent-switch-indicator" part="agent-switch-indicator">
|
|
201
|
+
<span class="agent-switch-label">${(m) => m.content}</span>
|
|
202
|
+
</div>
|
|
203
|
+
`,
|
|
204
|
+
)}
|
|
205
|
+
${when(
|
|
206
|
+
(m) => m.role !== 'system-event',
|
|
207
|
+
html<ChatMessage, FoundationAiAssistant>`
|
|
208
|
+
<div class="message-row ${(m) => messageType(m)}">
|
|
209
|
+
${when(
|
|
210
|
+
(m) => m.role !== 'user',
|
|
211
|
+
html<ChatMessage, FoundationAiAssistant>`
|
|
212
|
+
<div class="avatar ${(m) => messageType(m)}">
|
|
213
|
+
${when(
|
|
214
|
+
(m, c) => !!(c.parent as FoundationAiAssistant).imageSrc,
|
|
215
|
+
html<ChatMessage, FoundationAiAssistant>`
|
|
216
|
+
<img
|
|
217
|
+
src="${(m, c) => (c.parent as FoundationAiAssistant).imageSrc}"
|
|
218
|
+
alt="Assistant"
|
|
219
|
+
class="avatar-img"
|
|
220
|
+
/>
|
|
221
|
+
`,
|
|
222
|
+
)}
|
|
223
|
+
${when(
|
|
224
|
+
(m, c) => !(c.parent as FoundationAiAssistant).imageSrc,
|
|
225
|
+
genesisIconTemplate,
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
`,
|
|
229
|
+
)}
|
|
230
|
+
<div class="message ${(m) => messageType(m)}">
|
|
231
|
+
<div class="sender">
|
|
232
|
+
${(m, c) =>
|
|
233
|
+
messageType(m) === 'ai-function' &&
|
|
234
|
+
m.agentName &&
|
|
235
|
+
(c.parent as FoundationAiAssistant).showAgentSwitchIndicator
|
|
236
|
+
? `Tool Call · ${m.agentName}`
|
|
237
|
+
: senderLabel[messageType(m)]}
|
|
238
|
+
</div>
|
|
239
|
+
<div class="content">
|
|
240
|
+
${when(
|
|
241
|
+
(m) => m.content,
|
|
242
|
+
html<ChatMessage>`
|
|
243
|
+
<ai-chat-markdown :content="${(m) => m.content}"></ai-chat-markdown>
|
|
244
|
+
`,
|
|
245
|
+
)}
|
|
246
|
+
${when(
|
|
247
|
+
(m) => m.toolCalls,
|
|
248
|
+
html<ChatMessage>`
|
|
249
|
+
${repeat((m) => m.toolCalls ?? [], toolCallItemTemplate)}
|
|
250
|
+
`,
|
|
251
|
+
)}
|
|
252
|
+
${when(
|
|
253
|
+
(m) => m.interaction,
|
|
254
|
+
html<ChatMessage, FoundationAiAssistant>`
|
|
255
|
+
<ai-chat-interaction-wrapper
|
|
256
|
+
:componentName=${(m) => m.interaction!.componentName}
|
|
257
|
+
:data=${(m) => m.interaction!.data}
|
|
258
|
+
:interactionId=${(m) => m.interaction!.interactionId}
|
|
259
|
+
:resolved=${(m) => m.interaction!.resolved}
|
|
260
|
+
@interaction-completed=${(m, c) => c.parent.handleInteractionCompleted(c.event)}
|
|
261
|
+
></ai-chat-interaction-wrapper>
|
|
262
|
+
`,
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
`,
|
|
268
|
+
)}
|
|
269
|
+
`;
|
|
270
|
+
|
|
271
|
+
// ── Root template ───────────────────────────────────────────────────────────
|
|
272
|
+
|
|
86
273
|
return html<FoundationAiAssistant>`
|
|
87
274
|
<div class="chat-wrapper" part="chat-wrapper">
|
|
88
275
|
${when(
|
|
@@ -247,123 +434,8 @@ export const FoundationAiAssistantTemplate = (
|
|
|
247
434
|
</div>
|
|
248
435
|
|
|
249
436
|
<div class="messages" part="messages" ${ref('messagesEl')}>
|
|
250
|
-
|
|
251
|
-
${
|
|
252
|
-
(x) => x.visibleMessages,
|
|
253
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
254
|
-
${when(
|
|
255
|
-
(m) => m.role === 'system-event',
|
|
256
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
257
|
-
<div class="agent-switch-indicator" part="agent-switch-indicator">
|
|
258
|
-
<span class="agent-switch-label">${(m) => m.content}</span>
|
|
259
|
-
</div>
|
|
260
|
-
`,
|
|
261
|
-
)}
|
|
262
|
-
${when(
|
|
263
|
-
(m) => m.role !== 'system-event',
|
|
264
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
265
|
-
<div class="message-row ${(m) => messageType(m)}">
|
|
266
|
-
${when(
|
|
267
|
-
(m) => m.role !== 'user',
|
|
268
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
269
|
-
<div class="avatar ${(m) => messageType(m)}">
|
|
270
|
-
${when(
|
|
271
|
-
(m, c) => !!(c.parent as FoundationAiAssistant).imageSrc,
|
|
272
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
273
|
-
<img
|
|
274
|
-
src="${(m, c) => (c.parent as FoundationAiAssistant).imageSrc}"
|
|
275
|
-
alt="Assistant"
|
|
276
|
-
class="avatar-img"
|
|
277
|
-
/>
|
|
278
|
-
`,
|
|
279
|
-
)}
|
|
280
|
-
${when(
|
|
281
|
-
(m, c) => !(c.parent as FoundationAiAssistant).imageSrc,
|
|
282
|
-
genesisIconTemplate,
|
|
283
|
-
)}
|
|
284
|
-
</div>
|
|
285
|
-
`,
|
|
286
|
-
)}
|
|
287
|
-
<div class="message ${(m) => messageType(m)}">
|
|
288
|
-
<div class="sender">
|
|
289
|
-
${(m, c) =>
|
|
290
|
-
messageType(m) === 'ai-function' &&
|
|
291
|
-
m.agentName &&
|
|
292
|
-
(c.parent as FoundationAiAssistant).showAgentSwitchIndicator
|
|
293
|
-
? `Tool Call · ${m.agentName}`
|
|
294
|
-
: senderLabel[messageType(m)]}
|
|
295
|
-
</div>
|
|
296
|
-
<div class="content">
|
|
297
|
-
${when(
|
|
298
|
-
(m) => m.content,
|
|
299
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
300
|
-
<ai-chat-markdown :content="${(m) => m.content}"></ai-chat-markdown>
|
|
301
|
-
`,
|
|
302
|
-
)}
|
|
303
|
-
${when(
|
|
304
|
-
(m) => m.toolCalls,
|
|
305
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
306
|
-
${repeat(
|
|
307
|
-
(m) => m.toolCalls ?? [],
|
|
308
|
-
html<ChatToolCall>`
|
|
309
|
-
${when(
|
|
310
|
-
(tc) => tc.foldEvent === 'open',
|
|
311
|
-
html<ChatToolCall>`
|
|
312
|
-
<pre class="payload fold-event fold-open">
|
|
313
|
-
► ${(tc) => tc.name}</pre
|
|
314
|
-
>
|
|
315
|
-
`,
|
|
316
|
-
)}
|
|
317
|
-
${when(
|
|
318
|
-
(tc) => tc.foldEvent === 'close',
|
|
319
|
-
html<ChatToolCall>`
|
|
320
|
-
<pre class="payload fold-event fold-close">
|
|
321
|
-
◄ ${(tc) => tc.name}</pre
|
|
322
|
-
>
|
|
323
|
-
`,
|
|
324
|
-
)}
|
|
325
|
-
${when(
|
|
326
|
-
(tc) => !tc.foldEvent && !isChatToolCallUnknown(tc),
|
|
327
|
-
html<ChatToolCall>`
|
|
328
|
-
<pre class="payload">
|
|
329
|
-
${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<strong>${(tc) =>
|
|
330
|
-
tc.name}</strong>(${(tc) =>
|
|
331
|
-
JSON.stringify(tc.args, null, 2)})</pre
|
|
332
|
-
>
|
|
333
|
-
`,
|
|
334
|
-
)}
|
|
335
|
-
${when(
|
|
336
|
-
isChatToolCallUnknown,
|
|
337
|
-
html<ChatToolCall>`
|
|
338
|
-
<pre class="payload unknown-tool">
|
|
339
|
-
${(tc) => unknownToolPayload(tc)}</pre
|
|
340
|
-
>
|
|
341
|
-
`,
|
|
342
|
-
)}
|
|
343
|
-
`,
|
|
344
|
-
)}
|
|
345
|
-
`,
|
|
346
|
-
)}
|
|
347
|
-
${when(
|
|
348
|
-
(m) => m.interaction,
|
|
349
|
-
html<ChatMessage, FoundationAiAssistant>`
|
|
350
|
-
<ai-chat-interaction-wrapper
|
|
351
|
-
:componentName=${(m) => m.interaction!.componentName}
|
|
352
|
-
:data=${(m) => m.interaction!.data}
|
|
353
|
-
:interactionId=${(m) => m.interaction!.interactionId}
|
|
354
|
-
:resolved=${(m) => m.interaction!.resolved}
|
|
355
|
-
@interaction-completed=${(m, c) =>
|
|
356
|
-
c.parent.handleInteractionCompleted(c.event)}
|
|
357
|
-
></ai-chat-interaction-wrapper>
|
|
358
|
-
`,
|
|
359
|
-
)}
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
362
|
-
</div>
|
|
363
|
-
`,
|
|
364
|
-
)}
|
|
365
|
-
`,
|
|
366
|
-
)}
|
|
437
|
+
${repeat((x) => x.visibleMessages, messageRowTemplate)}
|
|
438
|
+
${when((x) => x.liveSubAgentTrace.length > 0 && x.showToolCalls, liveSubAgentTraceTemplate)}
|
|
367
439
|
${when(
|
|
368
440
|
(x) =>
|
|
369
441
|
x.showLoadingIndicator &&
|
package/src/main/main.ts
CHANGED
|
@@ -76,6 +76,14 @@ avoidTreeShaking(
|
|
|
76
76
|
ChatSuggestions,
|
|
77
77
|
);
|
|
78
78
|
|
|
79
|
+
/** Recursively strips `toolHandlers` from an agent and all its sub-agents. */
|
|
80
|
+
function stripHandlers(agent: AgentConfig): Omit<AgentConfig, 'toolHandlers'> {
|
|
81
|
+
const { toolHandlers: _, subAgents, ...rest } = agent;
|
|
82
|
+
return subAgents?.length
|
|
83
|
+
? { ...rest, subAgents: subAgents.map(stripHandlers) as AgentConfig[] }
|
|
84
|
+
: rest;
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
/**
|
|
80
88
|
* Foundation AI Assistant component.
|
|
81
89
|
*
|
|
@@ -145,15 +153,11 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
145
153
|
return this._sessionRef?.store.aiAssistant.activeAgent;
|
|
146
154
|
}
|
|
147
155
|
set activeAgent(value: AgentConfig | undefined) {
|
|
148
|
-
// Strip toolHandlers before storing — functions are non-serializable
|
|
149
|
-
// serializable-state middleware will warn. toolHandlers are never read
|
|
150
|
-
// the store; they are always sourced from this.agents when the driver
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this._sessionRef?.actions.aiAssistant.setActiveAgent(serializable);
|
|
154
|
-
} else {
|
|
155
|
-
this._sessionRef?.actions.aiAssistant.setActiveAgent(undefined);
|
|
156
|
-
}
|
|
156
|
+
// Strip toolHandlers recursively before storing — functions are non-serializable
|
|
157
|
+
// and Redux serializable-state middleware will warn. toolHandlers are never read
|
|
158
|
+
// back from the store; they are always sourced from this.agents when the driver
|
|
159
|
+
// is built.
|
|
160
|
+
this._sessionRef?.actions.aiAssistant.setActiveAgent(value ? stripHandlers(value) : undefined);
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
get suggestionsState(): SuggestionsState {
|
|
@@ -195,6 +199,20 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
195
199
|
this._sessionRef?.actions.aiAssistant.setEnabledAnimations(value);
|
|
196
200
|
}
|
|
197
201
|
|
|
202
|
+
get liveSubAgentTrace(): ChatMessage[] {
|
|
203
|
+
return this._sessionRef?.store.aiAssistant.liveSubAgentTrace ?? [];
|
|
204
|
+
}
|
|
205
|
+
set liveSubAgentTrace(value: ChatMessage[]) {
|
|
206
|
+
this._sessionRef?.actions.aiAssistant.setLiveSubAgentTrace(value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
get liveSubAgentName(): string | null {
|
|
210
|
+
return this._sessionRef?.store.aiAssistant.liveSubAgentName ?? null;
|
|
211
|
+
}
|
|
212
|
+
set liveSubAgentName(value: string | null) {
|
|
213
|
+
this._sessionRef?.actions.aiAssistant.setLiveSubAgentName(value);
|
|
214
|
+
}
|
|
215
|
+
|
|
198
216
|
/** Most recent prompt token count from the AI provider, if available. */
|
|
199
217
|
get contextTokens(): number | undefined {
|
|
200
218
|
return this._sessionRef?.store.aiAssistant.contextTokens;
|
|
@@ -421,8 +439,24 @@ export class FoundationAiAssistant extends GenesisElement {
|
|
|
421
439
|
};
|
|
422
440
|
driver.addEventListener('history-updated', onHistoryUpdated);
|
|
423
441
|
|
|
442
|
+
const onSubAgentHistoryUpdated = (e: Event) => {
|
|
443
|
+
const { agentName, history } = (e as CustomEvent).detail;
|
|
444
|
+
this.liveSubAgentName = agentName;
|
|
445
|
+
// structuredClone so Immer freezes an independent copy, not the child
|
|
446
|
+
// driver's own history array (which is still being mutated by the tool loop).
|
|
447
|
+
this.liveSubAgentTrace = structuredClone(history);
|
|
448
|
+
};
|
|
449
|
+
const onSubAgentStop = () => {
|
|
450
|
+
this.liveSubAgentTrace = [];
|
|
451
|
+
this.liveSubAgentName = null;
|
|
452
|
+
};
|
|
453
|
+
driver.addEventListener('sub-agent-history-updated', onSubAgentHistoryUpdated);
|
|
454
|
+
driver.addEventListener('sub-agent-stop', onSubAgentStop);
|
|
455
|
+
|
|
424
456
|
const cleanups: (() => void)[] = [
|
|
425
457
|
() => driver.removeEventListener('history-updated', onHistoryUpdated),
|
|
458
|
+
() => driver.removeEventListener('sub-agent-history-updated', onSubAgentHistoryUpdated),
|
|
459
|
+
() => driver.removeEventListener('sub-agent-stop', onSubAgentStop),
|
|
426
460
|
];
|
|
427
461
|
|
|
428
462
|
if (driver instanceof OrchestratingDriver) {
|
|
@@ -22,6 +22,10 @@ export interface AiAssistantSessionState {
|
|
|
22
22
|
activeAgent: Omit<AgentConfig, 'toolHandlers'> | undefined;
|
|
23
23
|
/** Draft text in the input box — preserved across pop-in/pop-out cycles. */
|
|
24
24
|
inputValue: string;
|
|
25
|
+
/** Live trace from a currently-executing sub-agent. Cleared on sub-agent-stop. */
|
|
26
|
+
liveSubAgentTrace: ChatMessage[];
|
|
27
|
+
/** Name of the currently-executing sub-agent, or null when idle. */
|
|
28
|
+
liveSubAgentName: string | null;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export const defaultSessionState: AiAssistantSessionState = {
|
|
@@ -36,6 +40,8 @@ export const defaultSessionState: AiAssistantSessionState = {
|
|
|
36
40
|
contextLimit: undefined,
|
|
37
41
|
activeAgent: undefined,
|
|
38
42
|
inputValue: '',
|
|
43
|
+
liveSubAgentTrace: [],
|
|
44
|
+
liveSubAgentName: null,
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
export const aiAssistantSlice = createSlice({
|
|
@@ -75,6 +81,12 @@ export const aiAssistantSlice = createSlice({
|
|
|
75
81
|
setInputValue(state, action: PayloadAction<string>) {
|
|
76
82
|
state.inputValue = action.payload;
|
|
77
83
|
},
|
|
84
|
+
setLiveSubAgentTrace(state, action: PayloadAction<ChatMessage[]>) {
|
|
85
|
+
state.liveSubAgentTrace = action.payload;
|
|
86
|
+
},
|
|
87
|
+
setLiveSubAgentName(state, action: PayloadAction<string | null>) {
|
|
88
|
+
state.liveSubAgentName = action.payload;
|
|
89
|
+
},
|
|
78
90
|
},
|
|
79
91
|
selectors: {},
|
|
80
92
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Masks the tool-specific payload of a single message — clears tool call args
|
|
5
|
+
* and replaces tool result content with a placeholder. Used when passing history
|
|
6
|
+
* to an agent that should not see another agent's implementation detail.
|
|
7
|
+
*/
|
|
8
|
+
function maskToolPayload(msg: ChatMessage): ChatMessage {
|
|
9
|
+
if (msg.toolCalls?.length) {
|
|
10
|
+
return { ...msg, toolCalls: msg.toolCalls.map((tc) => ({ ...tc, args: {} })) };
|
|
11
|
+
}
|
|
12
|
+
if (msg.toolResult) {
|
|
13
|
+
return {
|
|
14
|
+
...msg,
|
|
15
|
+
toolResult: { ...msg.toolResult, content: "[other agent's tool result omitted]" },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return msg;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Prepares history for the LLM only: masks tool call args and results from other
|
|
23
|
+
* agents so the active specialist is not confused by tools it does not have.
|
|
24
|
+
* Canonical history in `ChatDriver` stays unmasked for UI and logging.
|
|
25
|
+
*/
|
|
26
|
+
export function transformHistoryForAgent(history: ChatMessage[], agentName: string): ChatMessage[] {
|
|
27
|
+
return history.map((msg) => {
|
|
28
|
+
if (!msg.agentName || msg.agentName === agentName) return msg;
|
|
29
|
+
return maskToolPayload(msg);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Applies a history cap for sub-agent context: the last `cap` messages are passed
|
|
35
|
+
* verbatim; older messages have their tool payloads masked.
|
|
36
|
+
*/
|
|
37
|
+
export function applyHistoryCap(history: readonly ChatMessage[], cap: number): ChatMessage[] {
|
|
38
|
+
const cutoff = history.length - cap;
|
|
39
|
+
return history.map((msg, i) => (i < cutoff ? maskToolPayload(msg) : msg));
|
|
40
|
+
}
|