@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.
Files changed (37) hide show
  1. package/dist/ai-assistant.api.json +191 -1
  2. package/dist/ai-assistant.d.ts +60 -0
  3. package/dist/dts/components/chat-driver/chat-driver.d.ts +33 -0
  4. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  5. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  6. package/dist/dts/config/config.d.ts +20 -0
  7. package/dist/dts/config/config.d.ts.map +1 -1
  8. package/dist/dts/main/main.d.ts +6 -0
  9. package/dist/dts/main/main.d.ts.map +1 -1
  10. package/dist/dts/main/main.styles.d.ts.map +1 -1
  11. package/dist/dts/main/main.template.d.ts +16 -0
  12. package/dist/dts/main/main.template.d.ts.map +1 -1
  13. package/dist/dts/state/ai-assistant-slice.d.ts +6 -0
  14. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  15. package/dist/dts/state/session-store.d.ts +2 -0
  16. package/dist/dts/state/session-store.d.ts.map +1 -1
  17. package/dist/dts/utils/history-transform.d.ts +13 -0
  18. package/dist/dts/utils/history-transform.d.ts.map +1 -0
  19. package/dist/esm/components/chat-driver/chat-driver.js +127 -16
  20. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +8 -20
  21. package/dist/esm/config/config.js +18 -1
  22. package/dist/esm/main/main.js +43 -11
  23. package/dist/esm/main/main.styles.js +62 -0
  24. package/dist/esm/main/main.template.js +122 -71
  25. package/dist/esm/state/ai-assistant-slice.js +8 -0
  26. package/dist/esm/utils/history-transform.js +35 -0
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/docs/sub_agent.md +149 -211
  29. package/package.json +16 -16
  30. package/src/components/chat-driver/chat-driver.ts +169 -15
  31. package/src/components/orchestrating-driver/orchestrating-driver.ts +10 -22
  32. package/src/config/config.ts +24 -0
  33. package/src/main/main.styles.ts +62 -0
  34. package/src/main/main.template.ts +189 -117
  35. package/src/main/main.ts +43 -9
  36. package/src/state/ai-assistant-slice.ts +12 -0
  37. package/src/utils/history-transform.ts +40 -0
@@ -1,11 +1,13 @@
1
1
  import { __awaiter } from "tslib";
2
2
  import { MalformedFunctionCallError } from '@genesislcap/foundation-ai';
3
+ import { applyHistoryCap } from '../../utils/history-transform';
3
4
  import { logger } from '../../utils/logger';
4
5
  import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
5
6
  const DEFAULT_MAX_TOOL_ITERATIONS = 50;
6
7
  const DEFAULT_MAX_FOLD_OPERATIONS = 5;
7
8
  const DEFAULT_MAX_UNKNOWN_TOOL_CALLS = 5;
8
9
  const MAX_MALFORMED_RETRIES = 2;
10
+ const MAX_EMPTY_RESPONSE_RETRIES = 3;
9
11
  const SUGGESTIONS_HISTORY_WINDOW = 8;
10
12
  /** Name reserved for the cross-agent handoff tool — injected by OrchestratingDriver. */
11
13
  export const REQUEST_CONTINUATION_TOOL = 'request_continuation';
@@ -35,6 +37,8 @@ export class ChatDriver extends EventTarget {
35
37
  this.consecutiveFoldOps = 0;
36
38
  /** Consecutive unknown-tool calls without a real tool call. Reset on real tool execution. */
37
39
  this.consecutiveUnknownToolCalls = 0;
40
+ /** Sub-agents declared on the active agent config, keyed by name. */
41
+ this.subAgentsMap = new Map();
38
42
  this.toolHandlers = toolHandlers;
39
43
  this.toolDefinitions = toolDefinitions;
40
44
  this.systemPrompt = systemPrompt;
@@ -46,16 +50,24 @@ export class ChatDriver extends EventTarget {
46
50
  * each specialist turn so the shared driver runs with the right tools and prompt.
47
51
  */
48
52
  applyAgent(config) {
49
- var _a, _b;
53
+ var _a, _b, _c;
50
54
  this.systemPrompt = config.systemPrompt;
51
55
  this.toolDefinitions = (_a = config.toolDefinitions) !== null && _a !== void 0 ? _a : [];
52
56
  this.toolHandlers = (_b = config.toolHandlers) !== null && _b !== void 0 ? _b : {};
53
57
  this.primerHistory = config.primerHistory;
54
58
  this.activeAgentName = config.name;
59
+ this.subAgentsMap = new Map(((_c = config.subAgents) !== null && _c !== void 0 ? _c : []).map((s) => [s.name, s]));
55
60
  // Reset fold state when agent changes — each specialist starts fresh
56
61
  this.foldStack = [];
57
62
  this.consecutiveFoldOps = 0;
58
63
  }
64
+ /**
65
+ * Returns the early-stop result set by `completeSubAgent`, if any.
66
+ * Called by a parent `ChatDriver` after running this instance as a sub-agent.
67
+ */
68
+ getSubAgentCompletion() {
69
+ return this.subAgentCompletion;
70
+ }
59
71
  /**
60
72
  * Optional transform applied to conversation history immediately before each LLM request.
61
73
  * Cleared when `undefined`. Does not alter stored history.
@@ -212,6 +224,7 @@ export class ChatDriver extends EventTarget {
212
224
  return { reason: 'done' };
213
225
  }
214
226
  this.busy = true;
227
+ this.subAgentCompletion = undefined;
215
228
  this.appendToHistory({ role: 'user', content: userInput, attachments });
216
229
  try {
217
230
  return yield this.runToolLoop(userInput, attachments);
@@ -226,6 +239,89 @@ export class ChatDriver extends EventTarget {
226
239
  }
227
240
  });
228
241
  }
242
+ /**
243
+ * Builds the context object passed to every tool handler call.
244
+ * Centralised here so fold shortcut dispatch and the main tool loop use the
245
+ * same context without duplication.
246
+ *
247
+ * @param traceCapture - Optional per-invocation slot. When provided, the trace
248
+ * from any sub-agent call is written here rather than to shared instance state,
249
+ * so parallel tool calls each capture their own trace independently.
250
+ */
251
+ buildHandlerContext(traceCapture) {
252
+ return Object.assign(Object.assign({ requestInteraction: (componentName, data) => this.requestInteraction(componentName, data) }, (this.subAgentsMap.size > 0 && {
253
+ requestSubAgent: (name, options) => this.invokeSubAgent(name, options).then(({ result, trace }) => {
254
+ if (traceCapture)
255
+ traceCapture.trace = trace;
256
+ return result;
257
+ }),
258
+ })), { completeSubAgent: (result) => {
259
+ var _a;
260
+ if (this.subAgentCompletion) {
261
+ logger.warn(`ChatDriver(${(_a = this.activeAgentName) !== null && _a !== void 0 ? _a : 'unknown'}): completeSubAgent called more than once — ignoring`);
262
+ return;
263
+ }
264
+ this.subAgentCompletion = { result };
265
+ } });
266
+ }
267
+ /**
268
+ * Creates a child `ChatDriver` for the named sub-agent, runs it to completion,
269
+ * and returns its structured result (or final text fallback) together with the
270
+ * full child conversation trace. Callers receive both values so each parallel
271
+ * invocation can capture its own trace without touching shared instance state.
272
+ */
273
+ invokeSubAgent(name, options) {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ var _a, _b, _c;
276
+ const subConfig = this.subAgentsMap.get(name);
277
+ if (!subConfig) {
278
+ const available = [...this.subAgentsMap.keys()].join(', ') || '(none)';
279
+ throw new Error(`Sub-agent "${name}" not found on agent "${this.activeAgentName}". Available: ${available}`);
280
+ }
281
+ const { task, historyCap, context } = options !== null && options !== void 0 ? options : {};
282
+ // Exclude the current in-flight assistant message (the one with tool calls that
283
+ // triggered this invocation) — it has unresolved tool calls that would confuse
284
+ // the sub-agent into thinking it needs to handle tools it doesn't own.
285
+ const lastMsg = this.history[this.history.length - 1];
286
+ const baseHistory = (lastMsg === null || lastMsg === void 0 ? void 0 : lastMsg.role) === 'assistant' && ((_a = lastMsg.toolCalls) === null || _a === void 0 ? void 0 : _a.length)
287
+ ? this.history.slice(0, -1)
288
+ : this.history;
289
+ const snapshotHistory = historyCap != null ? applyHistoryCap(baseHistory, historyCap) : [...baseHistory];
290
+ const contextMessages = context
291
+ ? [{ role: 'user', content: `[Sub-agent context]: ${JSON.stringify(context)}` }]
292
+ : [];
293
+ const effectivePrimer = [
294
+ ...snapshotHistory,
295
+ ...contextMessages,
296
+ ...((_b = subConfig.primerHistory) !== null && _b !== void 0 ? _b : []),
297
+ ];
298
+ const child = new ChatDriver(this.aiProvider);
299
+ child.applyAgent(Object.assign(Object.assign({}, subConfig), { primerHistory: effectivePrimer }));
300
+ const forwardTrace = (e) => {
301
+ this.dispatchEvent(new CustomEvent('sub-agent-history-updated', {
302
+ detail: { agentName: subConfig.name, history: e.detail },
303
+ }));
304
+ };
305
+ child.addEventListener('history-updated', forwardTrace);
306
+ this.dispatchEvent(new CustomEvent('sub-agent-start', { detail: { name } }));
307
+ try {
308
+ yield child.sendMessage(task !== null && task !== void 0 ? task : '');
309
+ }
310
+ finally {
311
+ child.removeEventListener('history-updated', forwardTrace);
312
+ this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: { name } }));
313
+ }
314
+ const trace = child.getHistory();
315
+ const completion = child.getSubAgentCompletion();
316
+ if (completion) {
317
+ return { result: completion.result, trace };
318
+ }
319
+ const finalMsg = [...trace]
320
+ .reverse()
321
+ .find((m) => { var _a, _b; return m.role === 'assistant' && !((_a = m.toolCalls) === null || _a === void 0 ? void 0 : _a.length) && ((_b = m.content) === null || _b === void 0 ? void 0 : _b.trim()); });
322
+ return { result: ((_c = finalMsg === null || finalMsg === void 0 ? void 0 : finalMsg.content) !== null && _c !== void 0 ? _c : ''), trace };
323
+ });
324
+ }
229
325
  /**
230
326
  * Continue the tool loop from current history without appending a new user message.
231
327
  * Used by OrchestratingDriver after an agent handoff.
@@ -239,6 +335,7 @@ export class ChatDriver extends EventTarget {
239
335
  return { reason: 'done' };
240
336
  }
241
337
  this.busy = true;
338
+ this.subAgentCompletion = undefined;
242
339
  try {
243
340
  return yield this.runToolLoop('', undefined, transientPrimer);
244
341
  }
@@ -327,9 +424,7 @@ export class ChatDriver extends EventTarget {
327
424
  const innerArgs = typeof args[key] === 'object' && args[key] !== null
328
425
  ? args[key]
329
426
  : {};
330
- return innerHandler(innerArgs, {
331
- requestInteraction: (c, d) => this.requestInteraction(c, d),
332
- }).then((r) => (typeof r === 'string' ? r : JSON.stringify(r)));
427
+ return innerHandler(innerArgs, this.buildHandlerContext()).then((r) => typeof r === 'string' ? r : JSON.stringify(r));
333
428
  }
334
429
  }
335
430
  // Normal two-step open
@@ -395,7 +490,12 @@ export class ChatDriver extends EventTarget {
395
490
  let currentAttachments = attachments;
396
491
  let iterations = 0;
397
492
  let malformedAttempts = 0;
398
- const startIteration = currentInput ? 1 : 0;
493
+ let emptyResponseAttempts = 0;
494
+ // True only for the very first LLM call. Used to exclude the pending user message
495
+ // from history (it is passed separately as currentInput). Must not be derived from
496
+ // `iterations` because fold operations decrement iterations, which would incorrectly
497
+ // re-trigger the slice on subsequent calls after a fold open/close.
498
+ let firstLlmCall = !!currentInput;
399
499
  while (iterations < this.maxToolIterations) {
400
500
  iterations += 1;
401
501
  const foldSuffix = this.buildFoldSystemPromptSuffix();
@@ -403,14 +503,17 @@ export class ChatDriver extends EventTarget {
403
503
  ? `${this.systemPrompt}${foldSuffix}`
404
504
  : foldSuffix || undefined;
405
505
  const primer = [...((_a = this.primerHistory) !== null && _a !== void 0 ? _a : []), ...(transientPrimer !== null && transientPrimer !== void 0 ? transientPrimer : [])];
406
- const baseHistory = iterations === startIteration ? this.history.slice(0, -1) : this.history;
506
+ const baseHistory = firstLlmCall ? this.history.slice(0, -1) : this.history;
507
+ firstLlmCall = false;
407
508
  const historyForProvider = this.providerHistoryTransform
408
509
  ? this.providerHistoryTransform([...baseHistory])
409
510
  : baseHistory;
410
511
  const historyForCall = [...primer, ...historyForProvider];
411
512
  const systemPrompt = malformedAttempts > 0
412
513
  ? `${baseSystemPrompt !== null && baseSystemPrompt !== void 0 ? baseSystemPrompt : ''}\n\nIMPORTANT: Use only the structured function-call API to invoke tools. Do not write Python code or use Python-style syntax to call tools.`
413
- : baseSystemPrompt;
514
+ : emptyResponseAttempts > 0
515
+ ? `${baseSystemPrompt !== null && baseSystemPrompt !== void 0 ? baseSystemPrompt : ''}\n\nIMPORTANT: You must respond to the user's message. Call the appropriate tool or provide a text response — do not return an empty response.`
516
+ : baseSystemPrompt;
414
517
  const options = {
415
518
  systemPrompt,
416
519
  // Strip fold-only properties (foldEvent, foldPath) before sending to provider
@@ -443,9 +546,9 @@ export class ChatDriver extends EventTarget {
443
546
  const isThinkingStep = response.content && ((_b = response.toolCalls) === null || _b === void 0 ? void 0 : _b.length);
444
547
  const isEmptyResponse = !((_c = response.content) === null || _c === void 0 ? void 0 : _c.trim()) && !((_d = response.toolCalls) === null || _d === void 0 ? void 0 : _d.length);
445
548
  if (isEmptyResponse) {
446
- malformedAttempts += 1;
447
- if (malformedAttempts < MAX_MALFORMED_RETRIES) {
448
- logger.warn(`ChatDriver: empty model response, retrying (${malformedAttempts}/${MAX_MALFORMED_RETRIES})`);
549
+ emptyResponseAttempts += 1;
550
+ if (emptyResponseAttempts < MAX_EMPTY_RESPONSE_RETRIES) {
551
+ logger.warn(`ChatDriver: empty model response, retrying (${emptyResponseAttempts}/${MAX_EMPTY_RESPONSE_RETRIES})`);
449
552
  iterations -= 1;
450
553
  continue;
451
554
  }
@@ -542,11 +645,14 @@ export class ChatDriver extends EventTarget {
542
645
  }
543
646
  // Real tool execution
544
647
  try {
545
- const result = yield handler(tc.args, {
546
- requestInteraction: (componentName, data) => this.requestInteraction(componentName, data),
547
- });
648
+ const traceCapture = {};
649
+ const result = yield handler(tc.args, this.buildHandlerContext(traceCapture));
548
650
  const content = typeof result === 'string' ? result : JSON.stringify(result);
549
- executedById.set(tc.id, { toolCallId: tc.id, content });
651
+ executedById.set(tc.id, {
652
+ toolCallId: tc.id,
653
+ content,
654
+ subAgentTrace: traceCapture.trace,
655
+ });
550
656
  anyRealToolExecuted = true;
551
657
  }
552
658
  catch (e) {
@@ -594,7 +700,7 @@ export class ChatDriver extends EventTarget {
594
700
  const tcMsg = this.history[tcMsgIdx];
595
701
  const availableToolNames = Object.keys(this.toolHandlers);
596
702
  const annotatedCalls = tcMsg.toolCalls.map((tc) => {
597
- var _a, _b, _c, _d;
703
+ var _a, _b, _c, _d, _e;
598
704
  const isFoldOpen = !!this.getFold(tc.name) ||
599
705
  (
600
706
  // Was a fold facade at time of the call (now the tool set has changed)
@@ -608,7 +714,7 @@ export class ChatDriver extends EventTarget {
608
714
  ? 'close'
609
715
  : undefined,
610
716
  // Use the fold path that was active at the START of this iteration (before any opens/closes)
611
- foldPath: !isFoldOpen && !isFoldClose && foldPath.length > 0 ? foldPath : undefined, unknown: isUnknown || undefined, availableTools: isUnknown ? availableToolNames : undefined });
717
+ foldPath: !isFoldOpen && !isFoldClose && foldPath.length > 0 ? foldPath : undefined, unknown: isUnknown || undefined, availableTools: isUnknown ? availableToolNames : undefined, subAgentTrace: (_e = executedById.get(tc.id)) === null || _e === void 0 ? void 0 : _e.subAgentTrace });
612
718
  });
613
719
  this.history[tcMsgIdx] = Object.assign(Object.assign({}, tcMsg), { toolCalls: annotatedCalls });
614
720
  this.dispatchEvent(new CustomEvent('history-updated', {
@@ -628,6 +734,11 @@ export class ChatDriver extends EventTarget {
628
734
  const { summary, remaining_task: remainingTask } = firstContinuation.args;
629
735
  return { reason: 'agent-handoff', summary, remainingTask };
630
736
  }
737
+ // Sub-agent early exit — checked here so the exit point mirrors the
738
+ // system-call pattern above. Set by completeSubAgent() in a tool handler.
739
+ if (this.subAgentCompletion) {
740
+ return { reason: 'done' };
741
+ }
631
742
  currentInput = '';
632
743
  }
633
744
  if (iterations >= this.maxToolIterations) {
@@ -1,4 +1,5 @@
1
1
  import { __awaiter } from "tslib";
2
+ import { transformHistoryForAgent } from '../../utils/history-transform';
2
3
  import { logger } from '../../utils/logger';
3
4
  import { ChatDriver, REQUEST_CONTINUATION_TOOL } from '../chat-driver/chat-driver';
4
5
  const DEFAULT_MAX_HANDOFFS = 3;
@@ -35,25 +36,6 @@ function buildFallbackSystemPrompt(fallback, specialists) {
35
36
  }
36
37
  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
  }
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
39
  /**
58
40
  * Orchestrates multiple specialist agents. Sits between `FoundationAiAssistant`
59
41
  * and `ChatDriver`, classifying each user message and routing it to the right
@@ -80,10 +62,16 @@ export class OrchestratingDriver extends EventTarget {
80
62
  this.fallback = rawFallback
81
63
  ? Object.assign(Object.assign({}, rawFallback), { systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }) : undefined;
82
64
  this.chatDriver = new ChatDriver(aiProvider, {}, [], undefined, undefined, options.maxToolIterations, options.maxFoldOperations);
83
- // Proxy history-updated events from the shared driver
65
+ // Proxy events from the shared driver
84
66
  this.chatDriver.addEventListener('history-updated', (e) => {
85
67
  this.dispatchEvent(new CustomEvent('history-updated', { detail: e.detail }));
86
68
  });
69
+ this.chatDriver.addEventListener('sub-agent-history-updated', (e) => {
70
+ this.dispatchEvent(new CustomEvent('sub-agent-history-updated', { detail: e.detail }));
71
+ });
72
+ this.chatDriver.addEventListener('sub-agent-stop', (e) => {
73
+ this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: e.detail }));
74
+ });
87
75
  }
88
76
  resolveInteraction(interactionId, result) {
89
77
  this.chatDriver.resolveInteraction(interactionId, result);
@@ -1 +1,18 @@
1
- export {};
1
+ /**
2
+ * Identity helper that infers the narrowest possible type for an agent config,
3
+ * preserving string literal types (including `name`) without requiring `as const`.
4
+ *
5
+ * Use this when you need `typeof myAgent` to carry the literal `name` type —
6
+ * for example, when wiring `ChatToolHandlers<typeof myAgent>`.
7
+ *
8
+ * ```ts
9
+ * const myAgent = defineAgent({ name: 'my_agent', ... });
10
+ * type Handlers = ChatToolHandlers<typeof myAgent>;
11
+ * // requestSubAgent name param is now typed as 'my_agent'
12
+ * ```
13
+ *
14
+ * @beta
15
+ */
16
+ export function defineAgent(config) {
17
+ return config;
18
+ }
@@ -51,6 +51,12 @@ const MODEL_CONTEXT_LIMITS = {
51
51
  };
52
52
  // Register supporting components when the main component module is imported.
53
53
  avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiChatBubble, AiActivityHalo, ChatSuggestions);
54
+ /** Recursively strips `toolHandlers` from an agent and all its sub-agents. */
55
+ function stripHandlers(agent) {
56
+ const { toolHandlers: _, subAgents } = agent, rest = __rest(agent, ["toolHandlers", "subAgents"]);
57
+ return (subAgents === null || subAgents === void 0 ? void 0 : subAgents.length)
58
+ ? Object.assign(Object.assign({}, rest), { subAgents: subAgents.map(stripHandlers) }) : rest;
59
+ }
54
60
  /**
55
61
  * Foundation AI Assistant component.
56
62
  *
@@ -112,17 +118,12 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
112
118
  return (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.activeAgent;
113
119
  }
114
120
  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
- }
121
+ var _a;
122
+ // Strip toolHandlers recursively before storing — functions are non-serializable
123
+ // and Redux serializable-state middleware will warn. toolHandlers are never read
124
+ // back from the store; they are always sourced from this.agents when the driver
125
+ // is built.
126
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setActiveAgent(value ? stripHandlers(value) : undefined);
126
127
  }
127
128
  get suggestionsState() {
128
129
  var _a, _b;
@@ -168,6 +169,22 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
168
169
  var _a;
169
170
  (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setEnabledAnimations(value);
170
171
  }
172
+ get liveSubAgentTrace() {
173
+ var _a, _b;
174
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.liveSubAgentTrace) !== null && _b !== void 0 ? _b : [];
175
+ }
176
+ set liveSubAgentTrace(value) {
177
+ var _a;
178
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setLiveSubAgentTrace(value);
179
+ }
180
+ get liveSubAgentName() {
181
+ var _a, _b;
182
+ return (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.liveSubAgentName) !== null && _b !== void 0 ? _b : null;
183
+ }
184
+ set liveSubAgentName(value) {
185
+ var _a;
186
+ (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setLiveSubAgentName(value);
187
+ }
171
188
  /** Most recent prompt token count from the AI provider, if available. */
172
189
  get contextTokens() {
173
190
  var _a;
@@ -355,8 +372,23 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
355
372
  this.messages = [...e.detail];
356
373
  };
357
374
  driver.addEventListener('history-updated', onHistoryUpdated);
375
+ const onSubAgentHistoryUpdated = (e) => {
376
+ const { agentName, history } = e.detail;
377
+ this.liveSubAgentName = agentName;
378
+ // structuredClone so Immer freezes an independent copy, not the child
379
+ // driver's own history array (which is still being mutated by the tool loop).
380
+ this.liveSubAgentTrace = structuredClone(history);
381
+ };
382
+ const onSubAgentStop = () => {
383
+ this.liveSubAgentTrace = [];
384
+ this.liveSubAgentName = null;
385
+ };
386
+ driver.addEventListener('sub-agent-history-updated', onSubAgentHistoryUpdated);
387
+ driver.addEventListener('sub-agent-stop', onSubAgentStop);
358
388
  const cleanups = [
359
389
  () => driver.removeEventListener('history-updated', onHistoryUpdated),
390
+ () => driver.removeEventListener('sub-agent-history-updated', onSubAgentHistoryUpdated),
391
+ () => driver.removeEventListener('sub-agent-stop', onSubAgentStop),
360
392
  ];
361
393
  if (driver instanceof OrchestratingDriver) {
362
394
  const onOrchStart = () => {
@@ -445,6 +445,68 @@ export const styles = css `
445
445
  padding-left: 8px;
446
446
  }
447
447
 
448
+ .live-sub-agent-trace {
449
+ animation: slide-in-left 0.25s ease-out;
450
+ border-left: 2px solid var(--neutral-stroke-rest);
451
+ padding: 4px 8px;
452
+ margin: 4px 0;
453
+ opacity: 80%;
454
+ }
455
+
456
+ .live-sub-agent-name {
457
+ font-family: monospace;
458
+ font-size: 0.8em;
459
+ opacity: 70%;
460
+ font-style: italic;
461
+ display: block;
462
+ margin-bottom: 2px;
463
+ }
464
+
465
+ .sub-agent-trace {
466
+ margin-top: 6px;
467
+ border-left: 2px solid var(--neutral-stroke-rest);
468
+ padding-left: 8px;
469
+ }
470
+
471
+ .sub-agent-trace-summary {
472
+ font-family: monospace;
473
+ font-size: 0.85em;
474
+ opacity: 70%;
475
+ cursor: pointer;
476
+ user-select: none;
477
+ padding: 2px 0;
478
+ }
479
+
480
+ .sub-agent-trace-summary:hover {
481
+ opacity: 100%;
482
+ }
483
+
484
+ .sub-agent-message {
485
+ font-family: monospace;
486
+ font-size: 0.85em;
487
+ margin-top: 4px;
488
+ }
489
+
490
+ .sub-agent-assistant {
491
+ opacity: 85%;
492
+ white-space: pre-wrap;
493
+ }
494
+
495
+ .sub-agent-tool-call {
496
+ opacity: 60%;
497
+ }
498
+
499
+ .sub-agent-tool-name::before {
500
+ content: '⚙ ';
501
+ }
502
+
503
+ .sub-agent-tool-result {
504
+ opacity: 50%;
505
+ border-left: 1px solid var(--neutral-stroke-rest);
506
+ padding-left: 6px;
507
+ white-space: pre-wrap;
508
+ }
509
+
448
510
  .input-row {
449
511
  display: flex;
450
512
  gap: calc(var(--design-unit) * 2px);