@genesislcap/ai-assistant 15.58.2 → 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
@@ -4109,6 +4109,10 @@ var META_EVENT_IMPORTANCE = {
4109
4109
  "interaction.outcome": "normal",
4110
4110
  // An outcome that could not be recorded: the widget goes on showing only the answer.
4111
4111
  "interaction.outcome-dropped": "high",
4112
+ // A claimant handing a pending interaction to the person, or taking it back.
4113
+ "interaction.held": "normal",
4114
+ // A claim on a `userOnly` interaction, refused. Expected when an interceptor claims broadly.
4115
+ "interaction.claim-refused": "low",
4112
4116
  "external-diagnostics.folded": "normal",
4113
4117
  "session.cleared": "normal",
4114
4118
  "session.switched": "low",
@@ -4320,6 +4324,14 @@ function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKE
4320
4324
  };
4321
4325
  }
4322
4326
 
4327
+ // src/utils/delegated-message.ts
4328
+ var DEFAULT_DELEGATE_FRAMING = "Sent on the user's behalf.";
4329
+ function frameDelegatedContent(content, framing) {
4330
+ return `(${framing ?? DEFAULT_DELEGATE_FRAMING})
4331
+
4332
+ ${content}`;
4333
+ }
4334
+
4323
4335
  // src/utils/logger.ts
4324
4336
  var logger2 = createLogger("ai-assistant");
4325
4337
 
@@ -4371,7 +4383,9 @@ Additional guidance: "${prompt2}"` : "";
4371
4383
  }
4372
4384
  const conversationContext = history.filter(
4373
4385
  (m) => reachesProvider(m) && (m.role === "user" || m.role === "assistant") && !m.toolCalls?.length && !m.thinking && !!m.content?.trim()
4374
- ).slice(-HISTORY_WINDOW).map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content}`).join("\n");
4386
+ ).slice(-HISTORY_WINDOW).map(
4387
+ (m) => m.role === "user" ? `User: ${m.delegate ? frameDelegatedContent(m.content ?? "", m.delegateFraming) : m.content}` : `Assistant: ${m.content}`
4388
+ ).join("\n");
4375
4389
  return `<conversation_history>
4376
4390
  ${conversationContext}
4377
4391
  </conversation_history>
@@ -4761,7 +4775,8 @@ function renderMessageForSummary(m) {
4761
4775
  case "compacted-summary":
4762
4776
  return `[Earlier summary] ${m.content}`;
4763
4777
  case "user":
4764
- return m.content ? `User: ${m.content}` : "";
4778
+ if (!m.content) return "";
4779
+ return `User: ${m.delegate ? frameDelegatedContent(m.content, m.delegateFraming) : m.content}`;
4765
4780
  case "assistant": {
4766
4781
  if (m.category === "reasoning") {
4767
4782
  return m.content ? `Thinking: ${m.content}` : "";
@@ -4793,6 +4808,9 @@ Summarize the conversation above so it can be dropped from context while preserv
4793
4808
  }
4794
4809
  function normalizeForProvider(history) {
4795
4810
  return history.map((m) => {
4811
+ if (m.role === "user" && m.delegate) {
4812
+ return { ...m, content: frameDelegatedContent(m.content ?? "", m.delegateFraming) };
4813
+ }
4796
4814
  if (m.role !== "compacted-summary") return m;
4797
4815
  return {
4798
4816
  ...m,
@@ -5007,6 +5025,7 @@ var AgentSession = class {
5007
5025
  transition.flowOwnerChangedTo = agent.name;
5008
5026
  }
5009
5027
  this.active = agent;
5028
+ if (switched) this.reportTransition(transition);
5010
5029
  return transition;
5011
5030
  }
5012
5031
  /**
@@ -5028,8 +5047,21 @@ var AgentSession = class {
5028
5047
  this.flowOwner = null;
5029
5048
  transition.flowOwnerChangedTo = null;
5030
5049
  }
5050
+ this.reportTransition(transition);
5031
5051
  return transition;
5032
5052
  }
5053
+ /**
5054
+ * Hand a transition to the owner. A throwing listener is logged rather than propagated, for the
5055
+ * same reason a throwing hook is: the transition has already happened, and failing the caller
5056
+ * would leave it believing it had not.
5057
+ */
5058
+ reportTransition(transition) {
5059
+ try {
5060
+ this.deps.onTransition?.(transition);
5061
+ } catch (e) {
5062
+ logger2.warn(`${WARN_PREFIX}: onTransition threw:`, e);
5063
+ }
5064
+ }
5033
5065
  /**
5034
5066
  * Fire `onDeactivate` on the active agent and abort pending lifecycle work, for session
5035
5067
  * teardown, so machines release resources cleanly. The session is not reusable afterwards: the
@@ -5255,8 +5287,12 @@ function createInteractionContext(interactionId) {
5255
5287
  const listeners = /* @__PURE__ */ new Set();
5256
5288
  const disposers = /* @__PURE__ */ new Set();
5257
5289
  let disposed = false;
5290
+ let heldBy = "user";
5258
5291
  const context = {
5259
5292
  interactionId,
5293
+ get heldBy() {
5294
+ return heldBy;
5295
+ },
5260
5296
  state,
5261
5297
  once(key, factory) {
5262
5298
  if (onceCache.has(key)) return onceCache.get(key);
@@ -5308,11 +5344,18 @@ function createInteractionContext(interactionId) {
5308
5344
  disposers.clear();
5309
5345
  listeners.clear();
5310
5346
  onceCache.clear();
5347
+ },
5348
+ setHeldBy(holder) {
5349
+ if (disposed || holder === heldBy) return false;
5350
+ heldBy = holder;
5351
+ context.notify();
5352
+ return true;
5311
5353
  }
5312
5354
  };
5313
5355
  }
5314
5356
 
5315
5357
  // src/components/chat-driver/interaction-controller.ts
5358
+ var DEFAULT_DECISION_FRAMING = "Decided on the user's behalf";
5316
5359
  var STOPPED = {
5317
5360
  status: "timeout",
5318
5361
  message: "The turn was stopped before this was answered."
@@ -5330,6 +5373,94 @@ var InteractionController = class {
5330
5373
  * one-interaction-at-a-time guard in `request`).
5331
5374
  */
5332
5375
  this.contexts = /* @__PURE__ */ new Map();
5376
+ /**
5377
+ * Told about every interaction this controller opens (GENC-1660), in registration order. Only
5378
+ * the root's are ever consulted: a sub-agent's request is opened by the root.
5379
+ */
5380
+ this.interceptors = [];
5381
+ }
5382
+ /** See `ChatDriver.addInteractionInterceptor`. Returns a remover. */
5383
+ addInterceptor(interceptor) {
5384
+ if (!interceptor.id || interceptor.id === "user") {
5385
+ throw new Error(
5386
+ `addInteractionInterceptor: an interceptor needs an id other than 'user' (got "${interceptor.id}").`
5387
+ );
5388
+ }
5389
+ this.interceptors.push(interceptor);
5390
+ return () => {
5391
+ const index = this.interceptors.indexOf(interceptor);
5392
+ if (index >= 0) this.interceptors.splice(index, 1);
5393
+ };
5394
+ }
5395
+ /**
5396
+ * Offer a new interaction to the interceptors and record who ends up holding it.
5397
+ *
5398
+ * **Outermost first.** A decorator installs its interceptor on the driver it wraps when it is
5399
+ * constructed, and decorators are constructed innermost first, so registration order is
5400
+ * innermost first and the walk runs it backwards. The first claim wins; later interceptors still
5401
+ * see the interaction but their `hold` does nothing. A claim on a `userOnly` interaction is
5402
+ * refused.
5403
+ *
5404
+ * Runs before the interaction's message is appended, so the widget's first render already knows
5405
+ * its holder and nothing appears only to disappear.
5406
+ */
5407
+ offer(handle, opened) {
5408
+ let claimant;
5409
+ for (const interceptor of [...this.interceptors].reverse()) {
5410
+ const hold = this.holdFor(handle, interceptor, () => claimant === interceptor);
5411
+ let answer;
5412
+ try {
5413
+ answer = interceptor.onInteractionOpened({ ...opened, ctx: handle.context, hold });
5414
+ } catch (e) {
5415
+ logger2.warn(`InteractionController: interceptor "${interceptor.id}" threw:`, e);
5416
+ continue;
5417
+ }
5418
+ if (answer !== "claim" || claimant) continue;
5419
+ if (opened.userOnly) {
5420
+ recordMetaEvent(this.deps.sessionKey, "interaction.claim-refused", {
5421
+ interactionId: opened.interactionId,
5422
+ claimant: interceptor.id,
5423
+ reason: "userOnly"
5424
+ });
5425
+ continue;
5426
+ }
5427
+ claimant = interceptor;
5428
+ handle.setHeldBy(interceptor.id);
5429
+ const pending = this.pending.get(opened.interactionId);
5430
+ if (pending) pending.claimant = interceptor;
5431
+ }
5432
+ return handle.context.heldBy;
5433
+ }
5434
+ /** The {@link InteractionHold} for one interceptor: it applies only while that one is the claimant. */
5435
+ holdFor(handle, interceptor, isClaimant) {
5436
+ return {
5437
+ set: (holder) => {
5438
+ if (!isClaimant()) return false;
5439
+ const next = holder === "user" ? "user" : interceptor.id;
5440
+ if (!handle.setHeldBy(next)) return false;
5441
+ recordMetaEvent(this.deps.sessionKey, "interaction.held", {
5442
+ interactionId: handle.context.interactionId,
5443
+ heldBy: next,
5444
+ claimant: interceptor.id
5445
+ });
5446
+ return true;
5447
+ },
5448
+ resolve: (result) => {
5449
+ const { interactionId } = handle.context;
5450
+ const pending = this.pending.get(interactionId);
5451
+ if (!isClaimant() || !pending || handle.context.heldBy !== interceptor.id) return false;
5452
+ pending.settledBy = interceptor.id;
5453
+ this.resolve(interactionId, result);
5454
+ return true;
5455
+ },
5456
+ recordDecision: (decision) => {
5457
+ if (!isClaimant()) return false;
5458
+ const pending = this.pending.get(handle.context.interactionId);
5459
+ if (!pending) return false;
5460
+ pending.decision = { choice: decision.choice, shownToUser: decision.shownToUser };
5461
+ return true;
5462
+ }
5463
+ };
5333
5464
  }
5334
5465
  /** See `ChatDriver.setHostInteractionRequester`. */
5335
5466
  setHostRequester(fn, recordOutcome) {
@@ -5339,10 +5470,14 @@ var InteractionController = class {
5339
5470
  /**
5340
5471
  * See `ChatDriver.requestInteraction`. `onOpened` is told the id of the interaction this call
5341
5472
  * opens, wherever it opens it, so the caller can later record its outcome and no other's.
5473
+ * `innerPath` is the owner path a sub-agent's request has gathered on its way up, and
5474
+ * `onSettled` is told who answered, wherever it resolves (GENC-1660).
5342
5475
  */
5343
- request(componentName, data, options, onOpened) {
5476
+ request(componentName, data, options, onOpened, innerPath = [], onSettled) {
5477
+ const agent = this.deps.activeAgentName();
5478
+ const ownerPath = agent ? [agent, ...innerPath] : [...innerPath];
5344
5479
  if (this.hostRequester) {
5345
- return this.hostRequester(componentName, data, options, onOpened);
5480
+ return this.hostRequester(componentName, data, options, onOpened, ownerPath, onSettled);
5346
5481
  }
5347
5482
  if (this.pending.size > 0) {
5348
5483
  throw new Error(
@@ -5358,6 +5493,7 @@ var InteractionController = class {
5358
5493
  this.pending.set(interactionId, {
5359
5494
  resolve,
5360
5495
  reject,
5496
+ onSettled,
5361
5497
  overrideId: chatInputDuringExecution ? interactionId : void 0,
5362
5498
  // Resolve the interaction with a timeout result if the user does not
5363
5499
  // respond in time. `resolve` runs the same teardown a normal resolution
@@ -5370,10 +5506,22 @@ var InteractionController = class {
5370
5506
  });
5371
5507
  }, timeoutMs) : void 0
5372
5508
  });
5509
+ const handle = createInteractionContext(interactionId);
5510
+ this.contexts.set(interactionId, handle);
5511
+ const heldBy = this.offer(handle, {
5512
+ interactionId,
5513
+ componentName,
5514
+ data,
5515
+ ownerPath,
5516
+ userOnly: options?.userOnly === true
5517
+ });
5373
5518
  recordMetaEvent(this.deps.sessionKey, "interaction.requested", {
5374
5519
  interactionId,
5375
5520
  component: componentName,
5376
- agent: this.deps.activeAgentName()
5521
+ agent: this.deps.activeAgentName(),
5522
+ ownerPath,
5523
+ heldBy,
5524
+ ...options?.userOnly ? { userOnly: true } : {}
5377
5525
  });
5378
5526
  this.deps.activityBus.publish("interaction-requested", void 0);
5379
5527
  if (chatInputDuringExecution) {
@@ -5383,7 +5531,6 @@ var InteractionController = class {
5383
5531
  })
5384
5532
  );
5385
5533
  }
5386
- this.contexts.set(interactionId, createInteractionContext(interactionId));
5387
5534
  this.deps.appendToHistory({
5388
5535
  role: "assistant",
5389
5536
  content: "",
@@ -5394,6 +5541,11 @@ var InteractionController = class {
5394
5541
  ...presentation ? { presentation } : {}
5395
5542
  }
5396
5543
  });
5544
+ const opened = this.pending.get(interactionId);
5545
+ if (opened) {
5546
+ opened.appended = true;
5547
+ if (opened.early) this.resolve(interactionId, opened.early.result);
5548
+ }
5397
5549
  const signal = options?.signal;
5398
5550
  if (signal) {
5399
5551
  const onAbort = () => this.resolve(interactionId, STOPPED);
@@ -5419,6 +5571,10 @@ var InteractionController = class {
5419
5571
  logger2.warn(`Interaction with ID ${interactionId} not found.`);
5420
5572
  return;
5421
5573
  }
5574
+ if (!interaction.appended) {
5575
+ interaction.early ??= { result };
5576
+ return;
5577
+ }
5422
5578
  if (interaction.timeoutHandle) clearTimeout(interaction.timeoutHandle);
5423
5579
  interaction.unlinkSignal?.();
5424
5580
  this.deps.foldDiagnostics(
@@ -5426,20 +5582,50 @@ var InteractionController = class {
5426
5582
  result?.diagnostics
5427
5583
  );
5428
5584
  const costUsd = typeof result?.costUsd === "number" && Number.isFinite(result.costUsd) && result.costUsd > 0 ? result.costUsd : void 0;
5585
+ const unanswered = result?.status === "timeout";
5586
+ const answeredBy = unanswered ? void 0 : interaction.settledBy ?? "user";
5587
+ const decision = answeredBy && answeredBy !== "user" ? {
5588
+ framing: interaction.claimant?.decisionFraming ?? DEFAULT_DECISION_FRAMING,
5589
+ ...interaction.decision?.choice !== void 0 && {
5590
+ choice: interaction.decision.choice
5591
+ },
5592
+ ...interaction.decision?.shownToUser !== void 0 && {
5593
+ shownToUser: interaction.decision.shownToUser
5594
+ }
5595
+ } : void 0;
5429
5596
  const patched = this.deps.patchInteractionMessage(interactionId, (message) => ({
5430
5597
  ...message,
5431
- interaction: { ...message.interaction, resolved: result },
5598
+ interaction: {
5599
+ ...message.interaction,
5600
+ resolved: result,
5601
+ ...answeredBy ? { answeredBy } : {}
5602
+ },
5432
5603
  ...costUsd != null ? { externalCostUsd: (message.externalCostUsd ?? 0) + costUsd } : {}
5433
5604
  }));
5434
5605
  if (patched) this.deps.notifyHistoryUpdated();
5435
5606
  if (typeof result?.userMessage === "string" && result.userMessage.trim()) {
5436
- this.deps.appendToHistory({ role: "synthetic-user", content: result.userMessage });
5607
+ this.deps.appendToHistory(
5608
+ answeredBy && answeredBy !== "user" ? {
5609
+ role: "assistant",
5610
+ content: result.userMessage,
5611
+ delegate: answeredBy,
5612
+ excludeFromProvider: true
5613
+ } : { role: "synthetic-user", content: result.userMessage }
5614
+ );
5437
5615
  }
5438
5616
  if (interaction.overrideId) {
5439
5617
  this.deps.emit(new CustomEvent("interaction-stop", { detail: { interactionId } }));
5440
5618
  }
5441
- recordMetaEvent(this.deps.sessionKey, "interaction.resolved", { interactionId });
5619
+ recordMetaEvent(this.deps.sessionKey, "interaction.resolved", {
5620
+ interactionId,
5621
+ ...answeredBy ? { answeredBy } : {}
5622
+ });
5442
5623
  this.deps.activityBus.publish("interaction-resolved", void 0);
5624
+ try {
5625
+ interaction.onSettled?.({ answeredBy, decision });
5626
+ } catch (e) {
5627
+ logger2.warn("InteractionController: onSettled threw:", e);
5628
+ }
5443
5629
  interaction.resolve(result);
5444
5630
  this.pending.delete(interactionId);
5445
5631
  this.contexts.get(interactionId)?.dispose();
@@ -5838,6 +6024,18 @@ var budgetDetailOf = (e) => {
5838
6024
  ...e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}
5839
6025
  };
5840
6026
  };
6027
+ function withDelegatedDecisions(result, content, decisions, hasImages) {
6028
+ if (decisions.length === 0) return content;
6029
+ if (!hasImages && result !== null && typeof result === "object" && !Array.isArray(result)) {
6030
+ return JSON.stringify({ ...result, decidedOnUsersBehalf: decisions });
6031
+ }
6032
+ return [content, ...decisions.map(describeDelegatedDecision)].join("\n\n");
6033
+ }
6034
+ function describeDelegatedDecision(decision) {
6035
+ const choice = decision.choice !== void 0 ? `: "${decision.choice}"` : "";
6036
+ const shown = decision.shownToUser ? ` What the user was shown, as your words: "${decision.shownToUser}"` : " Nothing was shown to the user about it.";
6037
+ return `${decision.framing}${choice}.${shown}`;
6038
+ }
5841
6039
  function inFlightToolCallsAt(history) {
5842
6040
  for (let i = history.length - 1; i >= 0; i -= 1) {
5843
6041
  const message = history[i];
@@ -6003,6 +6201,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
6003
6201
  * on that one was reset by every handoff inside a single message.
6004
6202
  */
6005
6203
  this.messageController = new AbortController();
6204
+ /** Capabilities provided on this driver by decorators (GENC-1660). Only the root's are used. */
6205
+ this.handlerCapabilities = /* @__PURE__ */ new Map();
6006
6206
  /**
6007
6207
  * Caches validated provider lookups per name within the current agent. Cleared
6008
6208
  * by `installAgent` so each new agent's static/function-resolved names are
@@ -6026,9 +6226,17 @@ var ChatDriver = class _ChatDriver extends EventTarget {
6026
6226
  * the point: two would let `getActiveAgent()` disagree between the outer and inner driver.
6027
6227
  * `@internal` rather than private so that reach is legal without six delegating one-liners.
6028
6228
  *
6229
+ * **This driver raises the lifecycle events for every transition of the session**, whoever made
6230
+ * it: itself, through {@link ChatDriver.activateAgent} or a release, or the orchestrator reaching
6231
+ * through (GENC-1485). Before, only the orchestrator dispatched them, so a host on a bare
6232
+ * `ChatDriver` never learned its flow owner.
6233
+ *
6029
6234
  * @internal
6030
6235
  */
6031
- this.agentSession = new AgentSession({ sessionKey: () => this.sessionKey });
6236
+ this.agentSession = new AgentSession({
6237
+ sessionKey: () => this.sessionKey,
6238
+ onTransition: (t) => this.dispatchAgentTransition(t)
6239
+ });
6032
6240
  /**
6033
6241
  * A provider refusal was observed this turn, so the tool loop must not call the model again
6034
6242
  * (GENC-1506).
@@ -6537,6 +6745,51 @@ var ChatDriver = class _ChatDriver extends EventTarget {
6537
6745
  await this.agentSession.activate(config);
6538
6746
  this.installAgent(config);
6539
6747
  }
6748
+ /**
6749
+ * {@inheritDoc AiDriver.resumeRestoredFlow}
6750
+ *
6751
+ * This driver holds no list of agents, so it resumes only the `agent` it is handed (GENC-1660):
6752
+ * whoever runs a bare `ChatDriver` knows which agent that is, and after a reload only the name
6753
+ * survived. Without one it does nothing, as a driver without this member did before; given one
6754
+ * that is not the restored flow owner, it refuses.
6755
+ *
6756
+ * For headless callers: the shipped host restores and resumes through `OrchestratingDriver`,
6757
+ * and a bare `ChatDriver` does not answer `canRestoreFlowOwner`, so the host never plans a resume
6758
+ * for one.
6759
+ *
6760
+ * The orchestrator does not delegate here, because it installs a decorated config of its own;
6761
+ * this is the bare-driver path.
6762
+ *
6763
+ * @beta
6764
+ */
6765
+ async resumeRestoredFlow(resumeNote, agent) {
6766
+ if (!agent) return void 0;
6767
+ const owner = this.agentSession.getFlowOwner();
6768
+ if (owner !== null && owner !== agent.name) {
6769
+ logger2.warn(
6770
+ `ChatDriver: resumeRestoredFlow was handed "${agent.name}", but the flow belongs to "${owner}" \u2014 not resuming.`
6771
+ );
6772
+ return void 0;
6773
+ }
6774
+ await this.activateAgent(agent);
6775
+ if (!resumeNote || agent.resumeOnRestore === false) return void 0;
6776
+ return this.continueFromHistory(void 0, { tailNote: resumeNote });
6777
+ }
6778
+ /**
6779
+ * Raise the host-facing lifecycle events for one session transition (GENC-1485), in the order
6780
+ * the orchestrator raised them before it stopped doing so itself: the flow lock first, then the
6781
+ * release, then the active agent.
6782
+ */
6783
+ dispatchAgentTransition(t) {
6784
+ if (t.flowOwnerChangedTo !== void 0) {
6785
+ this.dispatchEvent(new CustomEvent("flow-owner-changed", { detail: t.flowOwnerChangedTo }));
6786
+ }
6787
+ const active = this.agentSession.getActive();
6788
+ if (!active && t.previousAgent) {
6789
+ this.dispatchEvent(new CustomEvent("agent-released", { detail: t.previousAgent }));
6790
+ }
6791
+ this.dispatchEvent(new CustomEvent("agent-changed", { detail: active }));
6792
+ }
6540
6793
  /**
6541
6794
  * {@inheritDoc AiDriver.getActiveAgent}
6542
6795
  *
@@ -6572,6 +6825,14 @@ var ChatDriver = class _ChatDriver extends EventTarget {
6572
6825
  setFlowOwner(name) {
6573
6826
  this.agentSession.setFlowOwner(name);
6574
6827
  }
6828
+ /**
6829
+ * {@inheritDoc AiDriver.getFlowOwner}
6830
+ *
6831
+ * @beta
6832
+ */
6833
+ getFlowOwner() {
6834
+ return this.agentSession.getFlowOwner();
6835
+ }
6575
6836
  /**
6576
6837
  * Install `config` as the active agent **without firing lifecycle hooks**.
6577
6838
  *
@@ -7145,8 +7406,84 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7145
7406
  * and `presentation` to control whether the host wraps the widget in a chat
7146
7407
  * bubble and/or shows the "Assistant" label.
7147
7408
  */
7148
- async requestInteraction(componentName, data, options, onOpened) {
7149
- return this.interactions.request(componentName, data, options, onOpened);
7409
+ async requestInteraction(componentName, data, options, onOpened, innerPath, onSettled) {
7410
+ return this.interactions.request(
7411
+ componentName,
7412
+ data,
7413
+ options,
7414
+ onOpened,
7415
+ innerPath,
7416
+ onSettled
7417
+ );
7418
+ }
7419
+ /**
7420
+ * {@inheritDoc AiDriver.addInteractionInterceptor}
7421
+ *
7422
+ * Consulted only on the root driver, where every interaction opens: a sub-agent's request
7423
+ * travels up to its root, carrying its owner path, and is offered there once.
7424
+ *
7425
+ * @beta
7426
+ */
7427
+ addInteractionInterceptor(interceptor) {
7428
+ return this.interactions.addInterceptor(interceptor);
7429
+ }
7430
+ /**
7431
+ * {@inheritDoc AiDriver.presentInteraction}
7432
+ *
7433
+ * @beta
7434
+ */
7435
+ presentInteraction(componentName, data, options) {
7436
+ return this.requestInteraction(componentName, data, options);
7437
+ }
7438
+ /**
7439
+ * {@inheritDoc AiDriver.provideHandlerCapability}
7440
+ *
7441
+ * Throws if `name` is already provided: two decorators answering to one name would leave a
7442
+ * handler unable to tell which it is talking to.
7443
+ *
7444
+ * @beta
7445
+ */
7446
+ provideHandlerCapability(name, factory) {
7447
+ if (this.handlerCapabilities.has(name)) {
7448
+ throw new Error(`provideHandlerCapability: "${name}" is already provided.`);
7449
+ }
7450
+ this.handlerCapabilities.set(name, factory);
7451
+ return () => {
7452
+ if (this.handlerCapabilities.get(name) === factory) this.handlerCapabilities.delete(name);
7453
+ };
7454
+ }
7455
+ /**
7456
+ * The capabilities this driver's handlers see, and who they see them as: the root's factories,
7457
+ * the root's flow owner, and the owner path from the root down to this driver's agent.
7458
+ */
7459
+ capabilityScope() {
7460
+ const own = this.activeAgentName ? [this.activeAgentName] : [];
7461
+ if (!this.capabilityParent) {
7462
+ return {
7463
+ factories: this.handlerCapabilities,
7464
+ scope: { ownerPath: own, flowOwner: this.agentSession.getFlowOwner() }
7465
+ };
7466
+ }
7467
+ const up = this.capabilityParent.capabilityScope();
7468
+ return {
7469
+ factories: up.factories,
7470
+ scope: { ownerPath: [...up.scope.ownerPath, ...own], flowOwner: up.scope.flowOwner }
7471
+ };
7472
+ }
7473
+ /** Build `context.capabilities` for one handler call, or `undefined` when nothing is provided. */
7474
+ buildCapabilities() {
7475
+ const { factories, scope } = this.capabilityScope();
7476
+ if (factories.size === 0) return void 0;
7477
+ const capabilities2 = {};
7478
+ for (const [name, factory] of factories) {
7479
+ try {
7480
+ const value = factory(scope);
7481
+ if (value !== void 0) capabilities2[name] = value;
7482
+ } catch (e) {
7483
+ logger2.warn(`ChatDriver: handler capability "${name}" threw \u2014 leaving it out:`, e);
7484
+ }
7485
+ }
7486
+ return capabilities2;
7150
7487
  }
7151
7488
  /**
7152
7489
  * Record what came of an interaction, after it resolved, on its history message
@@ -7218,8 +7555,10 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7218
7555
  this.history = [...messages];
7219
7556
  this.notifyHistoryUpdated();
7220
7557
  }
7221
- async sendMessage(userInput, attachments) {
7558
+ async sendMessage(userInput, attachments, options) {
7222
7559
  if (this.busy || !userInput.trim() && !attachments?.length) return { reason: "done" };
7560
+ const delegate = options?.delegate;
7561
+ const delegateFraming = delegate ? options?.delegateFraming ?? DEFAULT_DELEGATE_FRAMING : void 0;
7223
7562
  this.busy = true;
7224
7563
  this.beginTurn();
7225
7564
  this.beginMessage();
@@ -7236,7 +7575,12 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7236
7575
  this.aiServiceWall = void 0;
7237
7576
  this.aiServiceNoticesShown.clear();
7238
7577
  this.aiServiceNoticesQueued = [];
7239
- this.appendToHistory({ role: "user", content: userInput, attachments });
7578
+ this.appendToHistory({
7579
+ role: "user",
7580
+ content: userInput,
7581
+ attachments,
7582
+ ...delegate ? { delegate, delegateFraming } : {}
7583
+ });
7240
7584
  this.turnStartedAt = Date.now();
7241
7585
  recordMetaEvent(this.sessionKey, "turn.start", {
7242
7586
  phase: "sendMessage",
@@ -7245,7 +7589,10 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7245
7589
  this.activityBus.publish("tool-loop-start", void 0);
7246
7590
  let result = { reason: "done" };
7247
7591
  try {
7248
- result = await this.runToolLoop(userInput, attachments);
7592
+ result = await this.runToolLoop(
7593
+ delegate ? frameDelegatedContent(userInput, delegateFraming) : userInput,
7594
+ attachments
7595
+ );
7249
7596
  return result;
7250
7597
  } catch (e) {
7251
7598
  logger2.error("ChatDriver error:", e);
@@ -7298,13 +7645,25 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7298
7645
  * kept on a seven-way fan-out, reporting ~2.3x under the real cost, with the
7299
7646
  * error growing as the fan-out widens.
7300
7647
  */
7301
- buildHandlerContext(activeToolCallId, traceCapture) {
7648
+ buildHandlerContext(activeToolCallId, traceCapture, decisions) {
7302
7649
  let opened;
7303
7650
  let recorded = false;
7651
+ const capabilities2 = this.buildCapabilities();
7304
7652
  return {
7305
- requestInteraction: (componentName, data, options) => this.requestInteraction(componentName, data, options, (interactionId) => {
7306
- opened = interactionId;
7307
- }),
7653
+ ...capabilities2 && { capabilities: capabilities2 },
7654
+ requestInteraction: (componentName, data, options) => this.requestInteraction(
7655
+ componentName,
7656
+ data,
7657
+ options,
7658
+ (interactionId) => {
7659
+ opened = interactionId;
7660
+ },
7661
+ void 0,
7662
+ // An interaction an interceptor answered is reported in THIS call's result (GENC-1660).
7663
+ (settled) => {
7664
+ if (settled.decision) decisions?.push(settled.decision);
7665
+ }
7666
+ ),
7308
7667
  recordInteractionOutcome: (outcome) => {
7309
7668
  if (!opened) return false;
7310
7669
  if (recorded) {
@@ -7423,11 +7782,12 @@ var ChatDriver = class _ChatDriver extends EventTarget {
7423
7782
  }
7424
7783
  child.installAgent({ ...subConfig, primerHistory: effectivePrimer });
7425
7784
  child.setHostInteractionRequester(
7426
- (componentName, data, opts, onOpened) => this.requestInteraction(componentName, data, opts, onOpened),
7785
+ (componentName, data, opts, onOpened, ownerPath, onSettled) => this.requestInteraction(componentName, data, opts, onOpened, ownerPath, onSettled),
7427
7786
  // An outcome goes where its interaction went.
7428
7787
  (interactionId, outcome) => this.recordInteractionOutcome(interactionId, outcome)
7429
7788
  );
7430
7789
  child.hostAIServiceNotice = (code) => this.noticeAIServiceRefusal(code);
7790
+ child.capabilityParent = this;
7431
7791
  const forwardTrace = (e) => {
7432
7792
  this.dispatchEvent(
7433
7793
  new CustomEvent("sub-agent-history-updated", {
@@ -8138,10 +8498,19 @@ ${tailBody}
8138
8498
  }
8139
8499
  const traceCapture = { traces: [] };
8140
8500
  const capturedTrace = () => traceCapture.traces.length ? traceCapture.traces.flat() : void 0;
8501
+ const decisions = [];
8141
8502
  try {
8142
- const result = await handler(tc.args, this.buildHandlerContext(tc.id, traceCapture));
8503
+ const result = await handler(
8504
+ tc.args,
8505
+ this.buildHandlerContext(tc.id, traceCapture, decisions)
8506
+ );
8143
8507
  const resultImages = toolResultImages(result);
8144
- const content = resultImages ? typeof result.content === "string" ? result.content : JSON.stringify({ ...result, attachments: void 0 }) : typeof result === "string" ? result : JSON.stringify(result);
8508
+ const content = withDelegatedDecisions(
8509
+ result,
8510
+ resultImages ? typeof result.content === "string" ? result.content : JSON.stringify({ ...result, attachments: void 0 }) : typeof result === "string" ? result : JSON.stringify(result),
8511
+ decisions,
8512
+ !!resultImages
8513
+ );
8145
8514
  executedById.set(tc.id, {
8146
8515
  toolCallId: tc.id,
8147
8516
  content,
@@ -8341,6 +8710,189 @@ RECOVERY: this tool failed once \u2014 you may retry it, or take a different val
8341
8710
  }
8342
8711
  };
8343
8712
 
8713
+ // src/components/ai-driver/ai-driver.ts
8714
+ var AI_DRIVER_EVENT_TYPES = Object.keys({
8715
+ "history-updated": true,
8716
+ "sub-agent-history-updated": true,
8717
+ "sub-agent-start": true,
8718
+ "sub-agent-stop": true,
8719
+ "interaction-start": true,
8720
+ "interaction-stop": true,
8721
+ "provider-changed": true,
8722
+ "external-cost-recorded": true,
8723
+ "agent-changed": true,
8724
+ "agent-released": true,
8725
+ "flow-owner-changed": true,
8726
+ "orchestrating-start": true,
8727
+ "orchestrating-stop": true
8728
+ });
8729
+
8730
+ // src/components/ai-driver/base-driver-decorator.ts
8731
+ var BaseDriverDecorator = class _BaseDriverDecorator extends EventTarget {
8732
+ constructor(inner) {
8733
+ super();
8734
+ this.inner = inner;
8735
+ /** Undoes this layer's hold on its inner driver; see {@link BaseDriverDecorator.detach}. */
8736
+ this.detachers = [];
8737
+ for (const type of AI_DRIVER_EVENT_TYPES) {
8738
+ const forward = (e) => {
8739
+ this.dispatchEvent(new CustomEvent(type, { detail: e.detail }));
8740
+ };
8741
+ inner.addEventListener(type, forward);
8742
+ this.onDetach(() => inner.removeEventListener(type, forward));
8743
+ }
8744
+ }
8745
+ /**
8746
+ * Let go of the inner driver WITHOUT disposing it: stop forwarding its events, and undo whatever
8747
+ * a subclass registered through {@link BaseDriverDecorator.onDetach} (its interceptors, its
8748
+ * capabilities, its own listeners). For dropping or replacing a decorator while the driver it
8749
+ * wrapped lives on — otherwise the old layer keeps re-dispatching every event and is never
8750
+ * collected. Idempotent. `dispose()` detaches too.
8751
+ */
8752
+ detach() {
8753
+ for (const undo of this.detachers.splice(0)) {
8754
+ try {
8755
+ undo();
8756
+ } catch {
8757
+ }
8758
+ }
8759
+ }
8760
+ /** Register something {@link BaseDriverDecorator.detach} must undo. */
8761
+ onDetach(undo) {
8762
+ this.detachers.push(undo);
8763
+ }
8764
+ sendMessage(input, attachments, options) {
8765
+ return this.inner.sendMessage(input, attachments, options);
8766
+ }
8767
+ continueFromHistory(transientPrimer, options) {
8768
+ return this.inner.continueFromHistory(transientPrimer, options);
8769
+ }
8770
+ resolveInteraction(interactionId, result) {
8771
+ this.inner.resolveInteraction(interactionId, result);
8772
+ }
8773
+ recordExternalCost(interactionId, costUsd) {
8774
+ return this.inner.recordExternalCost?.(interactionId, costUsd) ?? false;
8775
+ }
8776
+ getInteractionContext(interactionId) {
8777
+ return this.inner.getInteractionContext?.(interactionId);
8778
+ }
8779
+ loadHistory(messages) {
8780
+ this.inner.loadHistory(messages);
8781
+ }
8782
+ primeRestoredAgentState(snapshots) {
8783
+ this.inner.primeRestoredAgentState(snapshots);
8784
+ }
8785
+ canRestoreFlowOwner(agentName) {
8786
+ return this.inner.canRestoreFlowOwner?.(agentName) ?? false;
8787
+ }
8788
+ canResumeRestoredFlow(agentName) {
8789
+ return this.inner.canResumeRestoredFlow?.(agentName) ?? false;
8790
+ }
8791
+ resumeRestoredFlow(resumeNote, agent) {
8792
+ return this.inner.resumeRestoredFlow?.(resumeNote, agent) ?? Promise.resolve(void 0);
8793
+ }
8794
+ getRawHistory() {
8795
+ return this.inner.getRawHistory?.() ?? [];
8796
+ }
8797
+ getHistoryForPersistence() {
8798
+ return this.inner.getHistoryForPersistence?.() ?? [...this.getRawHistory()];
8799
+ }
8800
+ getCondensePoliciesForPersistence(messages) {
8801
+ return this.inner.getCondensePoliciesForPersistence?.(messages) ?? [];
8802
+ }
8803
+ restoreCondensePolicies(entries) {
8804
+ return this.inner.restoreCondensePolicies?.(entries) ?? { restored: 0, awaitingReactivation: 0 };
8805
+ }
8806
+ getActiveAgent() {
8807
+ return this.inner.getActiveAgent();
8808
+ }
8809
+ setPinnedAgent(name) {
8810
+ this.inner.setPinnedAgent(name);
8811
+ }
8812
+ setFlowOwner(name) {
8813
+ this.inner.setFlowOwner(name);
8814
+ }
8815
+ getFlowOwner() {
8816
+ return this.inner.getFlowOwner?.() ?? null;
8817
+ }
8818
+ /**
8819
+ * Forwarded, so an interceptor installed on any layer lands on the driver that opens the
8820
+ * interactions. A decorator that answers interactions installs its own on `this.inner`.
8821
+ */
8822
+ addInteractionInterceptor(interceptor) {
8823
+ return this.inner.addInteractionInterceptor?.(interceptor) ?? (() => void 0);
8824
+ }
8825
+ presentInteraction(componentName, data, options) {
8826
+ return this.inner.presentInteraction?.(componentName, data, options) ?? Promise.reject(new Error("presentInteraction: the inner driver cannot show interactions."));
8827
+ }
8828
+ provideHandlerCapability(name, factory) {
8829
+ return this.inner.provideHandlerCapability?.(name, factory) ?? (() => void 0);
8830
+ }
8831
+ getContextReserve() {
8832
+ return this.inner.getContextReserve?.();
8833
+ }
8834
+ getPreambleTokens() {
8835
+ return this.inner.getPreambleTokens?.();
8836
+ }
8837
+ setContextGuard(policy) {
8838
+ this.inner.setContextGuard?.(policy);
8839
+ }
8840
+ compact(options) {
8841
+ return this.inner.compact?.(options) ?? Promise.resolve(null);
8842
+ }
8843
+ getCompactionPlan(options) {
8844
+ return this.inner.getCompactionPlan?.(options) ?? null;
8845
+ }
8846
+ canCompact(options) {
8847
+ return this.inner.canCompact?.(options) ?? false;
8848
+ }
8849
+ isBusy() {
8850
+ return this.inner.isBusy();
8851
+ }
8852
+ getSuggestions(history, prompt2, count, allAgentInfo) {
8853
+ return this.inner.getSuggestions(history, prompt2, count, allAgentInfo);
8854
+ }
8855
+ getTurnSnapshots() {
8856
+ return this.inner.getTurnSnapshots?.() ?? [];
8857
+ }
8858
+ /**
8859
+ * The inner driver's entries. Diagnostics compose down the stack: a decorator with entries of
8860
+ * its own overrides this and appends them to `super.getExternalDiagnostics()`.
8861
+ */
8862
+ getExternalDiagnostics() {
8863
+ return this.inner.getExternalDiagnostics?.() ?? [];
8864
+ }
8865
+ getActiveProviderName() {
8866
+ return this.inner.getActiveProviderName();
8867
+ }
8868
+ dispose() {
8869
+ const disposed = this.inner.dispose?.();
8870
+ this.detach();
8871
+ return disposed;
8872
+ }
8873
+ cancel() {
8874
+ this.inner.cancel?.();
8875
+ }
8876
+ /**
8877
+ * Every driver in the stack, outermost first: `driver`, then each inner driver down to the one
8878
+ * that is not a decorator.
8879
+ *
8880
+ * For diagnostics and labelling only: behaviour must go through `AiDriver` members, which every
8881
+ * layer forwards, never through a concrete class found here.
8882
+ *
8883
+ * @internal
8884
+ */
8885
+ static layers(driver) {
8886
+ const layers = [driver];
8887
+ let current = driver;
8888
+ while (current instanceof _BaseDriverDecorator) {
8889
+ current = current.inner;
8890
+ layers.push(current);
8891
+ }
8892
+ return layers;
8893
+ }
8894
+ };
8895
+
8344
8896
  // src/components/orchestrating-driver/orchestrating-driver.ts
8345
8897
  var DEFAULT_MAX_HANDOFFS = 3;
8346
8898
  var DEFAULT_CLASSIFIER_HISTORY_LENGTH = 4;
@@ -8403,9 +8955,9 @@ Politely let the user know what you can help with and invite them to rephrase th
8403
8955
 
8404
8956
  ${UNROUTABLE_INSTRUCTION}`;
8405
8957
  }
8406
- var OrchestratingDriver = class extends EventTarget {
8958
+ var OrchestratingDriver = class _OrchestratingDriver extends BaseDriverDecorator {
8407
8959
  constructor(providerRegistry, agents, options = {}) {
8408
- super();
8960
+ super(_OrchestratingDriver.createInner(providerRegistry, agents, options));
8409
8961
  this.providerRegistry = providerRegistry;
8410
8962
  this.agents = agents;
8411
8963
  /**
@@ -8429,7 +8981,6 @@ var OrchestratingDriver = class extends EventTarget {
8429
8981
  this.maxHandoffs = options.maxHandoffs ?? DEFAULT_MAX_HANDOFFS;
8430
8982
  this.classifierHistoryLength = options.classifierHistoryLength ?? DEFAULT_CLASSIFIER_HISTORY_LENGTH;
8431
8983
  this.classifierRetries = options.classifierRetries ?? DEFAULT_CLASSIFIER_RETRIES;
8432
- validateStaticAgentProviders(agents, providerRegistry);
8433
8984
  this.specialists = agents.filter(isSpecialist).filter((a) => !a.excludeFromClassifier);
8434
8985
  const fallbacks = agents.filter(isFallback);
8435
8986
  if (fallbacks.length > 1) {
@@ -8439,51 +8990,10 @@ var OrchestratingDriver = class extends EventTarget {
8439
8990
  }
8440
8991
  const rawFallback = fallbacks[0];
8441
8992
  this.fallback = rawFallback ? { ...rawFallback, systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) } : void 0;
8442
- this.chatDriver = new ChatDriver(providerRegistry, {
8443
- maxToolIterations: options.maxToolIterations,
8444
- maxFoldOperations: options.maxFoldOperations,
8445
- maxTurnSnapshots: options.maxTurnSnapshots,
8446
- sessionKey: this.sessionKey,
8447
- activityBus: options.activityBus,
8448
- budgetExhaustedMessage: options.budgetExhaustedMessage,
8449
- aiServiceErrorMessages: options.aiServiceErrorMessages,
8450
- // This driver releases after the turn so it can fire the host-facing events; see the option.
8451
- deferAgentRelease: true
8452
- });
8453
- this.chatDriver.addEventListener("history-updated", (e) => {
8454
- this.dispatchEvent(new CustomEvent("history-updated", { detail: e.detail }));
8455
- });
8456
- this.chatDriver.addEventListener("sub-agent-history-updated", (e) => {
8457
- this.dispatchEvent(
8458
- new CustomEvent("sub-agent-history-updated", { detail: e.detail })
8459
- );
8460
- });
8461
- this.chatDriver.addEventListener("sub-agent-start", (e) => {
8462
- this.dispatchEvent(new CustomEvent("sub-agent-start", { detail: e.detail }));
8463
- });
8464
- this.chatDriver.addEventListener("sub-agent-stop", (e) => {
8465
- this.dispatchEvent(new CustomEvent("sub-agent-stop", { detail: e.detail }));
8466
- });
8467
- this.chatDriver.addEventListener("interaction-start", (e) => {
8468
- this.dispatchEvent(
8469
- new CustomEvent("interaction-start", { detail: e.detail })
8470
- );
8471
- });
8472
- this.chatDriver.addEventListener("interaction-stop", (e) => {
8473
- this.dispatchEvent(
8474
- new CustomEvent("interaction-stop", { detail: e.detail })
8475
- );
8476
- });
8477
- this.chatDriver.addEventListener("provider-changed", (e) => {
8478
- this.dispatchEvent(
8479
- new CustomEvent("provider-changed", { detail: e.detail })
8480
- );
8481
- });
8482
- this.chatDriver.addEventListener("external-cost-recorded", (e) => {
8483
- this.dispatchEvent(
8484
- new CustomEvent("external-cost-recorded", { detail: e.detail })
8485
- );
8486
- });
8993
+ }
8994
+ /** The inner driver this one built and routes onto. */
8995
+ get chatDriver() {
8996
+ return this.inner;
8487
8997
  }
8488
8998
  /**
8489
8999
  * The stateful-agent contract (§7.1): active agent, user pin, flow lock, restored state, and
@@ -8507,108 +9017,23 @@ var OrchestratingDriver = class extends EventTarget {
8507
9017
  get activeAgent() {
8508
9018
  return this.agentSession.getActive();
8509
9019
  }
8510
- resolveInteraction(interactionId, result) {
8511
- this.chatDriver.resolveInteraction(interactionId, result);
8512
- }
8513
- /** Delegated for the same reason as `resolveInteraction`: the history that owns the
8514
- * interaction — and therefore the cost — lives on the wrapped driver, not here. */
8515
- recordExternalCost(interactionId, costUsd) {
8516
- return this.chatDriver.recordExternalCost(interactionId, costUsd);
8517
- }
8518
- getInteractionContext(interactionId) {
8519
- return this.chatDriver.getInteractionContext(interactionId);
8520
- }
8521
- isBusy() {
8522
- return this.chatDriver.isBusy();
8523
- }
8524
- /** Currently active provider name from the underlying ChatDriver. */
8525
- getActiveProviderName() {
8526
- return this.chatDriver.getActiveProviderName();
8527
- }
8528
- /**
8529
- * Pins routing to a specific agent by name. While pinned, the classifier is
8530
- * skipped and the continuation tool is hidden from the agent's tool list, so
8531
- * the agent cannot quietly hand back to the orchestrator. Pass `null` to
8532
- * return to automatic routing.
8533
- */
8534
- setPinnedAgent(name) {
8535
- this.agentSession.setPinned(name);
8536
- }
8537
- /**
8538
- * Set the flow-owner lock — used to restore state from Redux on driver
8539
- * reconstruction (agents-array changes, pop-in/pop-out). Not intended for
8540
- * external callers to manipulate routing; that's what `setPinnedAgent` is
8541
- * for.
8542
- */
8543
- setFlowOwner(name) {
8544
- this.agentSession.setFlowOwner(name);
8545
- }
8546
- loadHistory(messages) {
8547
- this.chatDriver.loadHistory(messages);
8548
- }
8549
- getRawHistory() {
8550
- return this.chatDriver.getHistory();
8551
- }
8552
9020
  /**
8553
- * Delegates to the inner {@link ChatDriver} — condensation policies are registered there, so it
8554
- * is the only object that knows which payloads the model has stopped seeing (GENC-1566).
8555
- *
8556
- * **This forward is the whole feature, for any multi-agent host.** `AiDriver` declares
8557
- * `getHistoryForPersistence` optional, and `SessionPersister` calls it through `?.`, falling back
8558
- * to {@link OrchestratingDriver.getRawHistory}. This class does NOT extend `ChatDriver` — it
8559
- * wraps one — so without an explicit forward the optional call resolves to `undefined` and every
8560
- * host on an orchestrating driver silently keeps persisting the full payloads. Measured exactly
8561
- * that way on a Create run: 10 `context.condensed` events fired, and the session still stored
8562
- * the two largest `read_context` results at 26,907 and 23,060 chars. The method existed, the
8563
- * tests passed, and the feature did nothing.
8564
- */
8565
- getHistoryForPersistence() {
8566
- return this.chatDriver.getHistoryForPersistence();
8567
- }
8568
- /**
8569
- * Delegates to the inner {@link ChatDriver}, which owns the condensation registry and clocks
8570
- * (GENC-1632). The forward matters for the same reason as `getHistoryForPersistence` above: the
8571
- * persister reaches it through `?.`, so a missing forward would compile, pass the ChatDriver
8572
- * tests, and do nothing for every orchestrated host — which is every host with an agents array.
9021
+ * Build the inner driver, after validating the agents' providers, so a bad config throws before
9022
+ * a driver exists to subscribe to the registry. Static because it runs before `super()`.
8573
9023
  */
8574
- getCondensePoliciesForPersistence(messages) {
8575
- return this.chatDriver.getCondensePoliciesForPersistence(messages);
8576
- }
8577
- /**
8578
- * Delegates to the inner {@link ChatDriver} (GENC-1632). The deferred activation stamp it
8579
- * performs is taken by the inner driver's `installAgent`, which this driver's own
8580
- * `installAgent` always reaches, so nothing extra is needed here.
8581
- */
8582
- restoreCondensePolicies(entries) {
8583
- return this.chatDriver.restoreCondensePolicies(entries);
8584
- }
8585
- /** {@inheritDoc AiDriver.setContextGuard} */
8586
- setContextGuard(policy) {
8587
- this.chatDriver.setContextGuard(policy);
8588
- }
8589
- /** {@inheritDoc AiDriver.compact} */
8590
- compact(options) {
8591
- return this.chatDriver.compact(options);
8592
- }
8593
- /** {@inheritDoc AiDriver.canCompact} */
8594
- canCompact(options) {
8595
- return this.chatDriver.canCompact(options);
8596
- }
8597
- /** {@inheritDoc AiDriver.getPreambleTokens} */
8598
- getPreambleTokens() {
8599
- return this.chatDriver.getPreambleTokens();
8600
- }
8601
- /** {@inheritDoc AiDriver.getCompactionPlan} */
8602
- getCompactionPlan(options) {
8603
- return this.chatDriver.getCompactionPlan(options);
8604
- }
8605
- /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
8606
- getTurnSnapshots() {
8607
- return this.chatDriver.getTurnSnapshots();
8608
- }
8609
- /** Delegates to the inner {@link ChatDriver} — interactions resolve there, so it holds the buffer. */
8610
- getExternalDiagnostics() {
8611
- return this.chatDriver.getExternalDiagnostics();
9024
+ static createInner(providerRegistry, agents, options = {}) {
9025
+ validateStaticAgentProviders(agents, providerRegistry);
9026
+ return new ChatDriver(providerRegistry, {
9027
+ maxToolIterations: options.maxToolIterations,
9028
+ maxFoldOperations: options.maxFoldOperations,
9029
+ maxTurnSnapshots: options.maxTurnSnapshots,
9030
+ sessionKey: options.sessionKey ?? "",
9031
+ activityBus: options.activityBus,
9032
+ budgetExhaustedMessage: options.budgetExhaustedMessage,
9033
+ aiServiceErrorMessages: options.aiServiceErrorMessages,
9034
+ // A release ends this driver's handoff loop, so it acts on the flag itself; see the option.
9035
+ deferAgentRelease: true
9036
+ });
8612
9037
  }
8613
9038
  async getSuggestions(history, prompt2, count, allAgentInfo) {
8614
9039
  const pinned = this.resolvePinnedAgent();
@@ -8623,14 +9048,19 @@ var OrchestratingDriver = class extends EventTarget {
8623
9048
  }));
8624
9049
  return this.chatDriver.getSuggestions(history, prompt2, count, agentInfo);
8625
9050
  }
8626
- async sendMessage(input, attachments) {
9051
+ async sendMessage(input, attachments, options) {
9052
+ const delegated = options?.delegate ? {
9053
+ delegate: options.delegate,
9054
+ delegateFraming: options.delegateFraming ?? DEFAULT_DELEGATE_FRAMING
9055
+ } : {};
8627
9056
  try {
8628
- return await this.runOrchestratedTurn(input, attachments);
9057
+ return await this.runOrchestratedTurn(input, attachments, options, delegated);
8629
9058
  } catch (e) {
8630
9059
  const pendingUserMessage = this.userMessageAppended ? void 0 : {
8631
9060
  role: "user",
8632
9061
  content: input,
8633
- ...attachments ? { attachments } : {}
9062
+ ...attachments ? { attachments } : {},
9063
+ ...delegated
8634
9064
  };
8635
9065
  if (e instanceof BudgetExhaustedError) {
8636
9066
  return this.chatDriver.reportBudgetExhausted(e, pendingUserMessage);
@@ -8641,13 +9071,13 @@ var OrchestratingDriver = class extends EventTarget {
8641
9071
  throw e;
8642
9072
  }
8643
9073
  }
8644
- async runOrchestratedTurn(input, attachments) {
9074
+ async runOrchestratedTurn(input, attachments, options, delegated) {
8645
9075
  this.cancelled = false;
8646
9076
  this.userMessageAppended = false;
8647
9077
  const history = this.chatDriver.getHistory();
8648
9078
  this.dispatchEvent(
8649
9079
  new CustomEvent("history-updated", {
8650
- detail: [...history, { role: "user", content: input, attachments }]
9080
+ detail: [...history, { role: "user", content: input, attachments, ...delegated }]
8651
9081
  })
8652
9082
  );
8653
9083
  const pinned = this.resolvePinnedAgent();
@@ -8657,7 +9087,11 @@ var OrchestratingDriver = class extends EventTarget {
8657
9087
  this.recordRouting("pinned", pinned, { durationMs: 0 });
8658
9088
  routed = { agent: pinned, decision: "pinned" };
8659
9089
  } else {
8660
- routed = await this.classify(input, history, this.activeAgent);
9090
+ routed = await this.classify(
9091
+ delegated.delegate ? frameDelegatedContent(input, delegated.delegateFraming) : input,
9092
+ history,
9093
+ this.activeAgent
9094
+ );
8661
9095
  }
8662
9096
  let currentAgent = routed.agent;
8663
9097
  let isHandoff = false;
@@ -8684,7 +9118,7 @@ var OrchestratingDriver = class extends EventTarget {
8684
9118
  result = await this.chatDriver.continueFromHistory(contextPrimer);
8685
9119
  } else {
8686
9120
  this.userMessageAppended = true;
8687
- result = await this.chatDriver.sendMessage(input, attachments);
9121
+ result = await this.chatDriver.sendMessage(input, attachments, options);
8688
9122
  }
8689
9123
  lastResult = result;
8690
9124
  if (this.chatDriver.getAgentReleaseRequested()) {
@@ -8717,18 +9151,12 @@ var OrchestratingDriver = class extends EventTarget {
8717
9151
  this.chatDriver.reportStoppedBeforeTurn({
8718
9152
  role: "user",
8719
9153
  content: input,
8720
- ...attachments ? { attachments } : {}
9154
+ ...attachments ? { attachments } : {},
9155
+ ...delegated
8721
9156
  });
8722
9157
  }
8723
9158
  return lastResult?.reason === "done" ? lastResult : { reason: "done" };
8724
9159
  }
8725
- async continueFromHistory(transientPrimer, options) {
8726
- return this.chatDriver.continueFromHistory(transientPrimer, options);
8727
- }
8728
- /** {@inheritDoc AiDriver.primeRestoredAgentState} */
8729
- primeRestoredAgentState(snapshots) {
8730
- this.agentSession.primeRestoredStates(snapshots);
8731
- }
8732
9160
  /** {@inheritDoc AiDriver.canRestoreFlowOwner} */
8733
9161
  canRestoreFlowOwner(agentName) {
8734
9162
  return this.agents.some((a) => a.name === agentName);
@@ -8745,6 +9173,10 @@ var OrchestratingDriver = class extends EventTarget {
8745
9173
  * to classify, and a restored lock already says which agent owns the flow. `continueFromHistory`
8746
9174
  * on its own would not do — it neither routes nor installs, so it would run on whatever agent
8747
9175
  * happened to be installed (none, straight after a reload).
9176
+ *
9177
+ * Looks the owner up in its own agents list, so the `agent` argument is not needed and is
9178
+ * ignored; and does not delegate to the inner driver's resume, which would install the agent
9179
+ * undecorated.
8748
9180
  */
8749
9181
  async resumeRestoredFlow(resumeNote) {
8750
9182
  const owner = this.agentSession.getFlowOwner();
@@ -8781,15 +9213,8 @@ var OrchestratingDriver = class extends EventTarget {
8781
9213
  }
8782
9214
  return winner ? { ...winner, agentName, basis: "reachable-max" } : void 0;
8783
9215
  }
8784
- /** {@inheritDoc AiDriver.getActiveAgent} */
8785
- getActiveAgent() {
8786
- return this.agentSession.getActive();
8787
- }
8788
9216
  async installAgent(agent) {
8789
- const { previousAgent, flowOwnerChangedTo } = await this.agentSession.activate(agent);
8790
- if (flowOwnerChangedTo !== void 0) {
8791
- this.dispatchEvent(new CustomEvent("flow-owner-changed", { detail: flowOwnerChangedTo }));
8792
- }
9217
+ const { previousAgent } = await this.agentSession.activate(agent);
8793
9218
  const isTerminal = isFallback(agent) || this.agentSession.getPinned() !== null || this.agentSession.getFlowOwner() !== null || !!(agent.onActivate || agent.onDeactivate);
8794
9219
  let agentToApply = agent;
8795
9220
  if (!isTerminal) {
@@ -8812,12 +9237,11 @@ var OrchestratingDriver = class extends EventTarget {
8812
9237
  this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
8813
9238
  this.chatDriver.installAgent(agentToApply);
8814
9239
  this.dispatchEvent(new CustomEvent("orchestrating-stop"));
8815
- this.dispatchEvent(new CustomEvent("agent-changed", { detail: agent }));
8816
9240
  }
8817
9241
  /**
8818
- * Release the current stateful agent: fire `onDeactivate`, clear the
8819
- * flow-owner lock, dispatch events so the host (and Redux) reflect the
8820
- * unlocked state. Called automatically when a tool handler invokes
9242
+ * Release the current stateful agent: fire `onDeactivate` and clear the flow-owner lock. The
9243
+ * inner driver raises the events from the session's transition, so the host (and Redux) reflect
9244
+ * the unlocked state (GENC-1485). Called automatically when a tool handler invokes
8821
9245
  * `context.releaseAgent`.
8822
9246
  *
8823
9247
  * The user-pin (`pinnedAgentName`) is intentionally *not* cleared — a user
@@ -8826,15 +9250,7 @@ var OrchestratingDriver = class extends EventTarget {
8826
9250
  * routing through the classifier.
8827
9251
  */
8828
9252
  async releaseActiveAgent() {
8829
- const released = await this.agentSession.release();
8830
- if (!released) return;
8831
- if (released.flowOwnerChangedTo !== void 0) {
8832
- this.dispatchEvent(
8833
- new CustomEvent("flow-owner-changed", { detail: released.flowOwnerChangedTo })
8834
- );
8835
- }
8836
- this.dispatchEvent(new CustomEvent("agent-released", { detail: released.previousAgent }));
8837
- this.dispatchEvent(new CustomEvent("agent-changed", { detail: void 0 }));
9253
+ await this.agentSession.release();
8838
9254
  }
8839
9255
  /**
8840
9256
  * Stop the current turn (user "stop" button). Cancels the inner chat driver's
@@ -8853,6 +9269,7 @@ var OrchestratingDriver = class extends EventTarget {
8853
9269
  async dispose() {
8854
9270
  await this.agentSession.dispose();
8855
9271
  await this.chatDriver.dispose();
9272
+ this.detach();
8856
9273
  }
8857
9274
  /**
8858
9275
  * Record one routing decision on the debug timeline — what was chosen, why, what it cost, and
@@ -8894,7 +9311,7 @@ var OrchestratingDriver = class extends EventTarget {
8894
9311
  classifierTranscript(history) {
8895
9312
  const lines = history.filter((m) => (m.role === "user" || m.role === "assistant") && reachesProvider(m)).filter((m) => !!m.content?.trim()).slice(-this.classifierHistoryLength).map((m) => {
8896
9313
  const speaker = m.role === "user" ? "User" : "Assistant";
8897
- const content = m.content ?? "";
9314
+ const content = m.role === "user" && m.delegate ? frameDelegatedContent(m.content ?? "", m.delegateFraming) : m.content ?? "";
8898
9315
  const body = content.length > CLASSIFIER_MESSAGE_CHARS ? `${content.slice(0, CLASSIFIER_MESSAGE_CHARS)}\u2026` : content;
8899
9316
  return `${speaker}: ${body}`;
8900
9317
  });