@genesislcap/ai-assistant 15.58.1 → 15.59.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 (58) hide show
  1. package/dist/ai-assistant.api.json +10096 -6986
  2. package/dist/ai-assistant.d.ts +545 -101
  3. package/dist/chat-driver.cjs +624 -207
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +624 -207
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +1836 -378
  8. package/dist/dts/components/agent-session/agent-session.d.ts +19 -3
  9. package/dist/dts/components/agent-session/agent-session.d.ts.map +1 -1
  10. package/dist/dts/components/ai-driver/ai-driver.d.ts +120 -10
  11. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  12. package/dist/dts/components/ai-driver/base-driver-decorator.d.ts +103 -0
  13. package/dist/dts/components/ai-driver/base-driver-decorator.d.ts.map +1 -0
  14. package/dist/dts/components/ai-driver/index.d.ts +3 -1
  15. package/dist/dts/components/ai-driver/index.d.ts.map +1 -1
  16. package/dist/dts/components/chat-driver/chat-driver.d.ts +84 -8
  17. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  18. package/dist/dts/components/chat-driver/interaction-controller.d.ts +37 -3
  19. package/dist/dts/components/chat-driver/interaction-controller.d.ts.map +1 -1
  20. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +30 -93
  21. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  22. package/dist/dts/index.d.ts +4 -1
  23. package/dist/dts/index.d.ts.map +1 -1
  24. package/dist/dts/main/main.d.ts +4 -4
  25. package/dist/dts/main/main.d.ts.map +1 -1
  26. package/dist/dts/state/debug-event-log.d.ts +1 -1
  27. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  28. package/dist/dts/state/interaction-context.d.ts +7 -1
  29. package/dist/dts/state/interaction-context.d.ts.map +1 -1
  30. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  31. package/dist/dts/suggestions/generate-suggestions.d.ts.map +1 -1
  32. package/dist/dts/types/handler-capability.d.ts +28 -0
  33. package/dist/dts/types/handler-capability.d.ts.map +1 -0
  34. package/dist/dts/types/interaction-context.d.ts +16 -0
  35. package/dist/dts/types/interaction-context.d.ts.map +1 -1
  36. package/dist/dts/types/interaction-interceptor.d.ts +109 -0
  37. package/dist/dts/types/interaction-interceptor.d.ts.map +1 -0
  38. package/dist/dts/utils/delegated-message.d.ts +52 -0
  39. package/dist/dts/utils/delegated-message.d.ts.map +1 -0
  40. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  41. package/dist/esm/components/agent-session/agent-session.js +17 -0
  42. package/dist/esm/components/ai-driver/ai-driver.js +21 -1
  43. package/dist/esm/components/ai-driver/base-driver-decorator.js +211 -0
  44. package/dist/esm/components/ai-driver/index.js +2 -1
  45. package/dist/esm/components/chat-driver/chat-driver.js +195 -14
  46. package/dist/esm/components/chat-driver/interaction-controller.js +168 -14
  47. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +72 -196
  48. package/dist/esm/index.js +1 -0
  49. package/dist/esm/main/main.js +79 -77
  50. package/dist/esm/state/debug-event-log.js +7 -2
  51. package/dist/esm/state/interaction-context.js +11 -0
  52. package/dist/esm/state/persistence/session-persister.js +5 -6
  53. package/dist/esm/suggestions/generate-suggestions.js +8 -1
  54. package/dist/esm/types/handler-capability.js +1 -0
  55. package/dist/esm/types/interaction-interceptor.js +1 -0
  56. package/dist/esm/utils/delegated-message.js +25 -0
  57. package/dist/esm/utils/history-transform.js +12 -1
  58. package/package.json +17 -17
@@ -3,10 +3,12 @@ import { AIServiceError, BudgetExhaustedError } from '@genesislcap/foundation-ai
3
3
  import { validateStaticAgentProviders } from '../../config/validate-providers';
4
4
  import { recordMetaEvent } from '../../state/debug-event-log';
5
5
  import { reachesProvider } from '../../utils/context-tokens';
6
+ import { DEFAULT_DELEGATE_FRAMING, frameDelegatedContent, } from '../../utils/delegated-message';
6
7
  import { transformHistoryForAgent } from '../../utils/history-transform';
7
8
  import { logger } from '../../utils/logger';
8
9
  import { messageUsage } from '../../utils/sum-usage';
9
- import { ChatDriver, REQUEST_CONTINUATION_TOOL, } from '../chat-driver/chat-driver';
10
+ import { BaseDriverDecorator } from '../ai-driver/base-driver-decorator';
11
+ import { ChatDriver, REQUEST_CONTINUATION_TOOL } from '../chat-driver/chat-driver';
10
12
  const DEFAULT_MAX_HANDOFFS = 3;
11
13
  const DEFAULT_CLASSIFIER_HISTORY_LENGTH = 4;
12
14
  const DEFAULT_CLASSIFIER_RETRIES = 2;
@@ -146,9 +148,19 @@ function buildFallbackSystemPrompt(fallback, specialists) {
146
148
  * and `ChatDriver`, classifying each user message and routing it to the right
147
149
  * specialist — each with its own focused system prompt, tools, and primer.
148
150
  *
151
+ * A {@link BaseDriverDecorator} over the `ChatDriver` it constructs (GENC-1485, GENC-1660): every
152
+ * `AiDriver` member and event it does not override passes straight through. Its inner driver is
153
+ * always one it built, never a composition choice, because routing reaches `ChatDriver`-only
154
+ * members (`installAgent`, `agentSession`, `getHistory`, `setProviderHistoryTransform`, the
155
+ * release flag and the `report…` exits). Other decorators go outside it.
156
+ *
149
157
  * @beta
150
158
  */
151
- export class OrchestratingDriver extends EventTarget {
159
+ export class OrchestratingDriver extends BaseDriverDecorator {
160
+ /** The inner driver this one built and routes onto. */
161
+ get chatDriver() {
162
+ return this.inner;
163
+ }
152
164
  /**
153
165
  * The stateful-agent contract (§7.1): active agent, user pin, flow lock, restored state, and
154
166
  * the `onActivate` / `onDeactivate` hooks.
@@ -173,7 +185,7 @@ export class OrchestratingDriver extends EventTarget {
173
185
  }
174
186
  constructor(providerRegistry, agents, options = {}) {
175
187
  var _a, _b, _c, _d;
176
- super();
188
+ super(OrchestratingDriver.createInner(providerRegistry, agents, options));
177
189
  this.providerRegistry = providerRegistry;
178
190
  this.agents = agents;
179
191
  /**
@@ -198,10 +210,6 @@ export class OrchestratingDriver extends EventTarget {
198
210
  this.classifierHistoryLength =
199
211
  (_c = options.classifierHistoryLength) !== null && _c !== void 0 ? _c : DEFAULT_CLASSIFIER_HISTORY_LENGTH;
200
212
  this.classifierRetries = (_d = options.classifierRetries) !== null && _d !== void 0 ? _d : DEFAULT_CLASSIFIER_RETRIES;
201
- // Static-name validation: walk every agent (and nested sub-agents). Any
202
- // `provider: '<name>'` that isn't registered, or resolves to a provider
203
- // without `chat()`, throws now rather than at first turn.
204
- validateStaticAgentProviders(agents, providerRegistry);
205
213
  // Specialists drive the classifier. `excludeFromClassifier` agents are still
206
214
  // resolvable by name (so manual pinning works) but never auto-routed.
207
215
  this.specialists = agents.filter(isSpecialist).filter((a) => !a.excludeFromClassifier);
@@ -212,151 +220,28 @@ export class OrchestratingDriver extends EventTarget {
212
220
  const rawFallback = fallbacks[0];
213
221
  this.fallback = rawFallback
214
222
  ? Object.assign(Object.assign({}, rawFallback), { systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }) : undefined;
215
- this.chatDriver = new ChatDriver(providerRegistry, {
223
+ }
224
+ /**
225
+ * Build the inner driver, after validating the agents' providers, so a bad config throws before
226
+ * a driver exists to subscribe to the registry. Static because it runs before `super()`.
227
+ */
228
+ static createInner(providerRegistry, agents, options = {}) {
229
+ var _a;
230
+ // Static-name validation: walk every agent (and nested sub-agents). Any
231
+ // `provider: '<name>'` that isn't registered, or resolves to a provider
232
+ // without `chat()`, throws now rather than at first turn.
233
+ validateStaticAgentProviders(agents, providerRegistry);
234
+ return new ChatDriver(providerRegistry, {
216
235
  maxToolIterations: options.maxToolIterations,
217
236
  maxFoldOperations: options.maxFoldOperations,
218
237
  maxTurnSnapshots: options.maxTurnSnapshots,
219
- sessionKey: this.sessionKey,
238
+ sessionKey: (_a = options.sessionKey) !== null && _a !== void 0 ? _a : '',
220
239
  activityBus: options.activityBus,
221
240
  budgetExhaustedMessage: options.budgetExhaustedMessage,
222
241
  aiServiceErrorMessages: options.aiServiceErrorMessages,
223
- // This driver releases after the turn so it can fire the host-facing events; see the option.
242
+ // A release ends this driver's handoff loop, so it acts on the flag itself; see the option.
224
243
  deferAgentRelease: true,
225
244
  });
226
- // Proxy events from the shared driver
227
- this.chatDriver.addEventListener('history-updated', (e) => {
228
- this.dispatchEvent(new CustomEvent('history-updated', { detail: e.detail }));
229
- });
230
- this.chatDriver.addEventListener('sub-agent-history-updated', (e) => {
231
- this.dispatchEvent(new CustomEvent('sub-agent-history-updated', { detail: e.detail }));
232
- });
233
- this.chatDriver.addEventListener('sub-agent-start', (e) => {
234
- this.dispatchEvent(new CustomEvent('sub-agent-start', { detail: e.detail }));
235
- });
236
- this.chatDriver.addEventListener('sub-agent-stop', (e) => {
237
- this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: e.detail }));
238
- });
239
- this.chatDriver.addEventListener('interaction-start', (e) => {
240
- this.dispatchEvent(new CustomEvent('interaction-start', { detail: e.detail }));
241
- });
242
- this.chatDriver.addEventListener('interaction-stop', (e) => {
243
- this.dispatchEvent(new CustomEvent('interaction-stop', { detail: e.detail }));
244
- });
245
- this.chatDriver.addEventListener('provider-changed', (e) => {
246
- this.dispatchEvent(new CustomEvent('provider-changed', { detail: e.detail }));
247
- });
248
- // Post-resolve external cost. The host only ever holds THIS wrapper (`createDriver` returns
249
- // one for any non-empty `agents`), so an event the inner driver raises and this list omits is
250
- // an event the host never sees: `recordExternalCost` still delegates and the money still lands
251
- // on the message, but the totals never refresh and the save is never scheduled — and in the
252
- // flow this exists for the interaction is the last thing in the session, so "stale until the
253
- // next history change" means permanently. Reviewer catch on #2474.
254
- this.chatDriver.addEventListener('external-cost-recorded', (e) => {
255
- this.dispatchEvent(new CustomEvent('external-cost-recorded', { detail: e.detail }));
256
- });
257
- }
258
- resolveInteraction(interactionId, result) {
259
- this.chatDriver.resolveInteraction(interactionId, result);
260
- }
261
- /** Delegated for the same reason as `resolveInteraction`: the history that owns the
262
- * interaction — and therefore the cost — lives on the wrapped driver, not here. */
263
- recordExternalCost(interactionId, costUsd) {
264
- return this.chatDriver.recordExternalCost(interactionId, costUsd);
265
- }
266
- getInteractionContext(interactionId) {
267
- return this.chatDriver.getInteractionContext(interactionId);
268
- }
269
- isBusy() {
270
- return this.chatDriver.isBusy();
271
- }
272
- /** Currently active provider name from the underlying ChatDriver. */
273
- getActiveProviderName() {
274
- return this.chatDriver.getActiveProviderName();
275
- }
276
- /**
277
- * Pins routing to a specific agent by name. While pinned, the classifier is
278
- * skipped and the continuation tool is hidden from the agent's tool list, so
279
- * the agent cannot quietly hand back to the orchestrator. Pass `null` to
280
- * return to automatic routing.
281
- */
282
- setPinnedAgent(name) {
283
- this.agentSession.setPinned(name);
284
- }
285
- /**
286
- * Set the flow-owner lock — used to restore state from Redux on driver
287
- * reconstruction (agents-array changes, pop-in/pop-out). Not intended for
288
- * external callers to manipulate routing; that's what `setPinnedAgent` is
289
- * for.
290
- */
291
- setFlowOwner(name) {
292
- this.agentSession.setFlowOwner(name);
293
- }
294
- loadHistory(messages) {
295
- this.chatDriver.loadHistory(messages);
296
- }
297
- getRawHistory() {
298
- return this.chatDriver.getHistory();
299
- }
300
- /**
301
- * Delegates to the inner {@link ChatDriver} — condensation policies are registered there, so it
302
- * is the only object that knows which payloads the model has stopped seeing (GENC-1566).
303
- *
304
- * **This forward is the whole feature, for any multi-agent host.** `AiDriver` declares
305
- * `getHistoryForPersistence` optional, and `SessionPersister` calls it through `?.`, falling back
306
- * to {@link OrchestratingDriver.getRawHistory}. This class does NOT extend `ChatDriver` — it
307
- * wraps one — so without an explicit forward the optional call resolves to `undefined` and every
308
- * host on an orchestrating driver silently keeps persisting the full payloads. Measured exactly
309
- * that way on a Create run: 10 `context.condensed` events fired, and the session still stored
310
- * the two largest `read_context` results at 26,907 and 23,060 chars. The method existed, the
311
- * tests passed, and the feature did nothing.
312
- */
313
- getHistoryForPersistence() {
314
- return this.chatDriver.getHistoryForPersistence();
315
- }
316
- /**
317
- * Delegates to the inner {@link ChatDriver}, which owns the condensation registry and clocks
318
- * (GENC-1632). The forward matters for the same reason as `getHistoryForPersistence` above: the
319
- * persister reaches it through `?.`, so a missing forward would compile, pass the ChatDriver
320
- * tests, and do nothing for every orchestrated host — which is every host with an agents array.
321
- */
322
- getCondensePoliciesForPersistence(messages) {
323
- return this.chatDriver.getCondensePoliciesForPersistence(messages);
324
- }
325
- /**
326
- * Delegates to the inner {@link ChatDriver} (GENC-1632). The deferred activation stamp it
327
- * performs is taken by the inner driver's `installAgent`, which this driver's own
328
- * `installAgent` always reaches, so nothing extra is needed here.
329
- */
330
- restoreCondensePolicies(entries) {
331
- return this.chatDriver.restoreCondensePolicies(entries);
332
- }
333
- /** {@inheritDoc AiDriver.setContextGuard} */
334
- setContextGuard(policy) {
335
- this.chatDriver.setContextGuard(policy);
336
- }
337
- /** {@inheritDoc AiDriver.compact} */
338
- compact(options) {
339
- return this.chatDriver.compact(options);
340
- }
341
- /** {@inheritDoc AiDriver.canCompact} */
342
- canCompact(options) {
343
- return this.chatDriver.canCompact(options);
344
- }
345
- /** {@inheritDoc AiDriver.getPreambleTokens} */
346
- getPreambleTokens() {
347
- return this.chatDriver.getPreambleTokens();
348
- }
349
- /** {@inheritDoc AiDriver.getCompactionPlan} */
350
- getCompactionPlan(options) {
351
- return this.chatDriver.getCompactionPlan(options);
352
- }
353
- /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
354
- getTurnSnapshots() {
355
- return this.chatDriver.getTurnSnapshots();
356
- }
357
- /** Delegates to the inner {@link ChatDriver} — interactions resolve there, so it holds the buffer. */
358
- getExternalDiagnostics() {
359
- return this.chatDriver.getExternalDiagnostics();
360
245
  }
361
246
  getSuggestions(history, prompt, count, allAgentInfo) {
362
247
  return __awaiter(this, void 0, void 0, function* () {
@@ -375,10 +260,18 @@ export class OrchestratingDriver extends EventTarget {
375
260
  return this.chatDriver.getSuggestions(history, prompt, count, agentInfo);
376
261
  });
377
262
  }
378
- sendMessage(input, attachments) {
263
+ sendMessage(input, attachments, options) {
379
264
  return __awaiter(this, void 0, void 0, function* () {
265
+ var _a;
266
+ // Who it is from travels with every copy of the message this method makes (GENC-1660).
267
+ const delegated = (options === null || options === void 0 ? void 0 : options.delegate)
268
+ ? {
269
+ delegate: options.delegate,
270
+ delegateFraming: (_a = options.delegateFraming) !== null && _a !== void 0 ? _a : DEFAULT_DELEGATE_FRAMING,
271
+ }
272
+ : {};
380
273
  try {
381
- return yield this.runOrchestratedTurn(input, attachments);
274
+ return yield this.runOrchestratedTurn(input, attachments, options, delegated);
382
275
  }
383
276
  catch (e) {
384
277
  // The classifier calls the provider directly, outside any ChatDriver turn,
@@ -398,7 +291,7 @@ export class OrchestratingDriver extends EventTarget {
398
291
  // snapshot, and the history sent to the model once the budget is raised.
399
292
  const pendingUserMessage = this.userMessageAppended
400
293
  ? undefined
401
- : Object.assign({ role: 'user', content: input }, (attachments ? { attachments } : {}));
294
+ : Object.assign(Object.assign({ role: 'user', content: input }, (attachments ? { attachments } : {})), delegated);
402
295
  if (e instanceof BudgetExhaustedError) {
403
296
  return this.chatDriver.reportBudgetExhausted(e, pendingUserMessage);
404
297
  }
@@ -410,7 +303,7 @@ export class OrchestratingDriver extends EventTarget {
410
303
  }
411
304
  });
412
305
  }
413
- runOrchestratedTurn(input, attachments) {
306
+ runOrchestratedTurn(input, attachments, options, delegated) {
414
307
  return __awaiter(this, void 0, void 0, function* () {
415
308
  this.cancelled = false;
416
309
  this.userMessageAppended = false;
@@ -418,7 +311,7 @@ export class OrchestratingDriver extends EventTarget {
418
311
  // Emit the user message immediately so the UI reflects it during the classify
419
312
  // round-trip — without this the chat appears frozen until classify returns.
420
313
  this.dispatchEvent(new CustomEvent('history-updated', {
421
- detail: [...history, { role: 'user', content: input, attachments }],
314
+ detail: [...history, Object.assign({ role: 'user', content: input, attachments }, delegated)],
422
315
  }));
423
316
  const pinned = this.resolvePinnedAgent();
424
317
  if (!pinned)
@@ -432,7 +325,7 @@ export class OrchestratingDriver extends EventTarget {
432
325
  routed = { agent: pinned, decision: 'pinned' };
433
326
  }
434
327
  else {
435
- routed = yield this.classify(input, history, this.activeAgent);
328
+ routed = yield this.classify(delegated.delegate ? frameDelegatedContent(input, delegated.delegateFraming) : input, history, this.activeAgent);
436
329
  }
437
330
  let currentAgent = routed.agent;
438
331
  let isHandoff = false;
@@ -503,7 +396,7 @@ export class OrchestratingDriver extends EventTarget {
503
396
  // handoff classify) sees the append as done.
504
397
  this.userMessageAppended = true;
505
398
  // oxlint-disable-next-line no-await-in-loop
506
- result = yield this.chatDriver.sendMessage(input, attachments);
399
+ result = yield this.chatDriver.sendMessage(input, attachments, options);
507
400
  }
508
401
  lastResult = result;
509
402
  // Release check: a stateful agent called `releaseAgent` from a terminal
@@ -559,20 +452,11 @@ export class OrchestratingDriver extends EventTarget {
559
452
  // install — leaves it shown only by the optimistic dispatch above, and it would vanish on the
560
453
  // next update and from a reload. Kept, as the budget wall keeps it (see sendMessage).
561
454
  if (this.cancelled && !this.userMessageAppended) {
562
- this.chatDriver.reportStoppedBeforeTurn(Object.assign({ role: 'user', content: input }, (attachments ? { attachments } : {})));
455
+ this.chatDriver.reportStoppedBeforeTurn(Object.assign(Object.assign({ role: 'user', content: input }, (attachments ? { attachments } : {})), delegated));
563
456
  }
564
457
  return (lastResult === null || lastResult === void 0 ? void 0 : lastResult.reason) === 'done' ? lastResult : { reason: 'done' };
565
458
  });
566
459
  }
567
- continueFromHistory(transientPrimer, options) {
568
- return __awaiter(this, void 0, void 0, function* () {
569
- return this.chatDriver.continueFromHistory(transientPrimer, options);
570
- });
571
- }
572
- /** {@inheritDoc AiDriver.primeRestoredAgentState} */
573
- primeRestoredAgentState(snapshots) {
574
- this.agentSession.primeRestoredStates(snapshots);
575
- }
576
460
  /** {@inheritDoc AiDriver.canRestoreFlowOwner} */
577
461
  canRestoreFlowOwner(agentName) {
578
462
  return this.agents.some((a) => a.name === agentName);
@@ -589,6 +473,10 @@ export class OrchestratingDriver extends EventTarget {
589
473
  * to classify, and a restored lock already says which agent owns the flow. `continueFromHistory`
590
474
  * on its own would not do — it neither routes nor installs, so it would run on whatever agent
591
475
  * happened to be installed (none, straight after a reload).
476
+ *
477
+ * Looks the owner up in its own agents list, so the `agent` argument is not needed and is
478
+ * ignored; and does not delegate to the inner driver's resume, which would install the agent
479
+ * undecorated.
592
480
  */
593
481
  resumeRestoredFlow(resumeNote) {
594
482
  return __awaiter(this, void 0, void 0, function* () {
@@ -659,19 +547,12 @@ export class OrchestratingDriver extends EventTarget {
659
547
  }
660
548
  return winner ? Object.assign(Object.assign({}, winner), { agentName, basis: 'reachable-max' }) : undefined;
661
549
  }
662
- /** {@inheritDoc AiDriver.getActiveAgent} */
663
- getActiveAgent() {
664
- return this.agentSession.getActive();
665
- }
666
550
  installAgent(agent) {
667
551
  return __awaiter(this, void 0, void 0, function* () {
668
552
  // The transition — handoff record, lifecycle hooks, restored state, flow lock — belongs to
669
- // the session. What is left below is this driver's own job: what the agent may route to, and
670
- // what the transcript says happened.
671
- const { previousAgent, flowOwnerChangedTo } = yield this.agentSession.activate(agent);
672
- if (flowOwnerChangedTo !== undefined) {
673
- this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: flowOwnerChangedTo }));
674
- }
553
+ // the session, and the inner driver raises its events (GENC-1485). What is left below is this
554
+ // driver's own job: what the agent may route to, and what the transcript says happened.
555
+ const { previousAgent } = yield this.agentSession.activate(agent);
675
556
  // Terminal agents do not get the cross-agent handoff tool. Four cases:
676
557
  // • fallback — already a leaf; handoff would loop
677
558
  // • user-pinned — user explicitly selected this agent; do not auto-route away
@@ -707,13 +588,12 @@ export class OrchestratingDriver extends EventTarget {
707
588
  this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
708
589
  this.chatDriver.installAgent(agentToApply);
709
590
  this.dispatchEvent(new CustomEvent('orchestrating-stop'));
710
- this.dispatchEvent(new CustomEvent('agent-changed', { detail: agent }));
711
591
  });
712
592
  }
713
593
  /**
714
- * Release the current stateful agent: fire `onDeactivate`, clear the
715
- * flow-owner lock, dispatch events so the host (and Redux) reflect the
716
- * unlocked state. Called automatically when a tool handler invokes
594
+ * Release the current stateful agent: fire `onDeactivate` and clear the flow-owner lock. The
595
+ * inner driver raises the events from the session's transition, so the host (and Redux) reflect
596
+ * the unlocked state (GENC-1485). Called automatically when a tool handler invokes
717
597
  * `context.releaseAgent`.
718
598
  *
719
599
  * The user-pin (`pinnedAgentName`) is intentionally *not* cleared — a user
@@ -723,14 +603,7 @@ export class OrchestratingDriver extends EventTarget {
723
603
  */
724
604
  releaseActiveAgent() {
725
605
  return __awaiter(this, void 0, void 0, function* () {
726
- const released = yield this.agentSession.release();
727
- if (!released)
728
- return;
729
- if (released.flowOwnerChangedTo !== undefined) {
730
- this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: released.flowOwnerChangedTo }));
731
- }
732
- this.dispatchEvent(new CustomEvent('agent-released', { detail: released.previousAgent }));
733
- this.dispatchEvent(new CustomEvent('agent-changed', { detail: undefined }));
606
+ yield this.agentSession.release();
734
607
  });
735
608
  }
736
609
  /**
@@ -755,6 +628,7 @@ export class OrchestratingDriver extends EventTarget {
755
628
  yield this.agentSession.dispose();
756
629
  // Tear down the inner driver too, so any in-flight provider request aborts.
757
630
  yield this.chatDriver.dispose();
631
+ this.detach();
758
632
  });
759
633
  }
760
634
  /**
@@ -796,9 +670,12 @@ export class OrchestratingDriver extends EventTarget {
796
670
  .filter((m) => { var _a; return !!((_a = m.content) === null || _a === void 0 ? void 0 : _a.trim()); })
797
671
  .slice(-this.classifierHistoryLength)
798
672
  .map((m) => {
799
- var _a;
673
+ var _a, _b;
800
674
  const speaker = m.role === 'user' ? 'User' : 'Assistant';
801
- const content = (_a = m.content) !== null && _a !== void 0 ? _a : '';
675
+ // A delegated message is framed for the router as for every model (GENC-1660).
676
+ const content = m.role === 'user' && m.delegate
677
+ ? frameDelegatedContent((_a = m.content) !== null && _a !== void 0 ? _a : '', m.delegateFraming)
678
+ : ((_b = m.content) !== null && _b !== void 0 ? _b : '');
802
679
  const body = content.length > CLASSIFIER_MESSAGE_CHARS
803
680
  ? `${content.slice(0, CLASSIFIER_MESSAGE_CHARS)}…`
804
681
  : content;
@@ -1019,20 +896,19 @@ export class OrchestratingDriver extends EventTarget {
1019
896
  }
1020
897
  }
1021
898
  /**
1022
- * Narrow a driver to an orchestrator, or `undefined` for a bare `ChatDriver`.
899
+ * The orchestrator in a driver's decorator stack, or `undefined` when there is none (a bare
900
+ * `ChatDriver`, possibly decorated).
1023
901
  *
1024
- * **The one place in the package that tests for this type** (`docs/code-health.md`
1025
- * §11.2 constraint 3). Orchestration-only capabilities — the agent pin, the
1026
- * flow-owner lock, the context guard — are reached through it, so when §7.1 lifts
1027
- * those onto `AiDriver` and they stop being orchestrator-only, this function is
1028
- * what changes rather than every consumer.
1029
- *
1030
- * Lives beside the class rather than on the assistant element because two modules
1031
- * need it: the element (for its own driver) and the session persister (for the
1032
- * driver it restores into), and a private element accessor cannot serve both.
902
+ * **The one place in the package that tests for this type** (`docs/code-health.md` §11.2
903
+ * constraint 3). Since GENC-1660 nothing behavioural goes through it: the agent pin, the
904
+ * flow-owner lock, the context guard and the lifecycle events are all reached on `AiDriver`
905
+ * itself, which every decorator forwards. What is left is labelling — the host's `driverKind`
906
+ * in its log lines — and it looks through decorators so that label stays true behind one.
1033
907
  *
1034
908
  * @internal
1035
909
  */
1036
910
  export function asOrchestrator(driver) {
1037
- return driver instanceof OrchestratingDriver ? driver : undefined;
911
+ if (!driver)
912
+ return undefined;
913
+ return BaseDriverDecorator.layers(driver).find((layer) => layer instanceof OrchestratingDriver);
1038
914
  }
package/dist/esm/index.js CHANGED
@@ -17,6 +17,7 @@ export * from './provider/ai-provider-switcher';
17
17
  export * from './provider/tiered-provider-switcher';
18
18
  export * from './provider/assistant-app-settings';
19
19
  export * from './utils/tool-fold';
20
+ export { DEFAULT_DELEGATE_FRAMING, isDelegated, } from './utils/delegated-message';
20
21
  export { assistantSettingsSectionTitleStyles } from './styles/settings-section';
21
22
  // MARKDOWN_EXPORTPARTS travels with the component: a host rendering `<ai-chat-markdown>` in its
22
23
  // own shadow root has to forward its parts, and hand-copying the list is how it drifts.