@genesislcap/ai-assistant 15.61.0 → 15.63.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 (137) hide show
  1. package/dist/ai-assistant.api.json +18231 -11171
  2. package/dist/ai-assistant.d.ts +1235 -9
  3. package/dist/chat-driver.cjs +658 -50
  4. package/dist/chat-driver.cjs.map +4 -4
  5. package/dist/chat-driver.mjs +657 -50
  6. package/dist/chat-driver.mjs.map +4 -4
  7. package/dist/custom-elements.json +4759 -717
  8. package/dist/dts/autopilot/autopilot-driver.d.ts +278 -0
  9. package/dist/dts/autopilot/autopilot-driver.d.ts.map +1 -0
  10. package/dist/dts/autopilot/autopilot-session.d.ts +163 -0
  11. package/dist/dts/autopilot/autopilot-session.d.ts.map +1 -0
  12. package/dist/dts/autopilot/autopilot-tools.d.ts +15 -0
  13. package/dist/dts/autopilot/autopilot-tools.d.ts.map +1 -0
  14. package/dist/dts/autopilot/decision-message.d.ts +62 -0
  15. package/dist/dts/autopilot/decision-message.d.ts.map +1 -0
  16. package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts +69 -0
  17. package/dist/dts/autopilot/hand-back/hand-back.controller.d.ts.map +1 -0
  18. package/dist/dts/autopilot/hand-back/hand-back.d.ts +22 -0
  19. package/dist/dts/autopilot/hand-back/hand-back.d.ts.map +1 -0
  20. package/dist/dts/autopilot/index.d.ts +4 -0
  21. package/dist/dts/autopilot/index.d.ts.map +1 -0
  22. package/dist/dts/channel/ai-activity-bus.d.ts.map +1 -1
  23. package/dist/dts/channel/ai-activity-channel.d.ts +17 -0
  24. package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
  25. package/dist/dts/channel/autopilot-needs-user.d.ts +8 -0
  26. package/dist/dts/channel/autopilot-needs-user.d.ts.map +1 -0
  27. package/dist/dts/components/agent-picker/agent-picker.constants.d.ts +10 -0
  28. package/dist/dts/components/agent-picker/agent-picker.constants.d.ts.map +1 -1
  29. package/dist/dts/components/agent-picker/agent-picker.d.ts +4 -0
  30. package/dist/dts/components/agent-picker/agent-picker.d.ts.map +1 -1
  31. package/dist/dts/components/agent-picker/agent-picker.template.d.ts.map +1 -1
  32. package/dist/dts/components/ai-driver/ai-driver.d.ts +34 -1
  33. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  34. package/dist/dts/components/ai-driver/base-driver-decorator.d.ts +3 -1
  35. package/dist/dts/components/ai-driver/base-driver-decorator.d.ts.map +1 -1
  36. package/dist/dts/components/chat-bubble/chat-bubble.d.ts +21 -0
  37. package/dist/dts/components/chat-bubble/chat-bubble.d.ts.map +1 -1
  38. package/dist/dts/components/chat-bubble/chat-bubble.styles.d.ts.map +1 -1
  39. package/dist/dts/components/chat-bubble/chat-bubble.template.d.ts.map +1 -1
  40. package/dist/dts/components/chat-driver/chat-driver.d.ts +5 -1
  41. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  42. package/dist/dts/components/chat-driver/interaction-controller.d.ts +27 -3
  43. package/dist/dts/components/chat-driver/interaction-controller.d.ts.map +1 -1
  44. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts +10 -2
  45. package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.d.ts.map +1 -1
  46. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +9 -1
  47. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  48. package/dist/dts/config/config.d.ts +183 -0
  49. package/dist/dts/config/config.d.ts.map +1 -1
  50. package/dist/dts/config/define-stateful-agent.d.ts +31 -1
  51. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  52. package/dist/dts/genesis/write-commit.d.ts +17 -7
  53. package/dist/dts/genesis/write-commit.d.ts.map +1 -1
  54. package/dist/dts/index.d.ts +7 -3
  55. package/dist/dts/index.d.ts.map +1 -1
  56. package/dist/dts/main/main.d.ts +128 -5
  57. package/dist/dts/main/main.d.ts.map +1 -1
  58. package/dist/dts/main/main.styles.d.ts.map +1 -1
  59. package/dist/dts/main/main.template.d.ts +2 -2
  60. package/dist/dts/main/main.template.d.ts.map +1 -1
  61. package/dist/dts/react.d.ts +10 -0
  62. package/dist/dts/state/debug-event-log.d.ts +1 -1
  63. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  64. package/dist/dts/state/decision-surface.d.ts +46 -0
  65. package/dist/dts/state/decision-surface.d.ts.map +1 -0
  66. package/dist/dts/state/interaction-context.d.ts +21 -1
  67. package/dist/dts/state/interaction-context.d.ts.map +1 -1
  68. package/dist/dts/state/interaction-registry.d.ts +63 -0
  69. package/dist/dts/state/interaction-registry.d.ts.map +1 -0
  70. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
  71. package/dist/dts/state/persistence/session-snapshot.d.ts +21 -0
  72. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -1
  73. package/dist/dts/state/run-steps.d.ts +66 -0
  74. package/dist/dts/state/run-steps.d.ts.map +1 -0
  75. package/dist/dts/types/ai-chat-widget.d.ts +7 -1
  76. package/dist/dts/types/ai-chat-widget.d.ts.map +1 -1
  77. package/dist/dts/types/interaction-context.d.ts +66 -1
  78. package/dist/dts/types/interaction-context.d.ts.map +1 -1
  79. package/dist/dts/types/interaction-interceptor.d.ts +60 -2
  80. package/dist/dts/types/interaction-interceptor.d.ts.map +1 -1
  81. package/dist/dts/types/interaction-surface.d.ts +195 -0
  82. package/dist/dts/types/interaction-surface.d.ts.map +1 -0
  83. package/dist/dts/utils/context-tokens.d.ts +6 -0
  84. package/dist/dts/utils/context-tokens.d.ts.map +1 -1
  85. package/dist/dts/utils/delegated-message.d.ts +13 -0
  86. package/dist/dts/utils/delegated-message.d.ts.map +1 -1
  87. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  88. package/dist/dts/utils/preserved-user-messages.d.ts +21 -0
  89. package/dist/dts/utils/preserved-user-messages.d.ts.map +1 -0
  90. package/dist/esm/autopilot/autopilot-driver.js +906 -0
  91. package/dist/esm/autopilot/autopilot-session.js +449 -0
  92. package/dist/esm/autopilot/autopilot-tools.js +68 -0
  93. package/dist/esm/autopilot/decision-message.js +206 -0
  94. package/dist/esm/autopilot/hand-back/hand-back.controller.js +47 -0
  95. package/dist/esm/autopilot/hand-back/hand-back.js +129 -0
  96. package/dist/esm/autopilot/index.js +4 -0
  97. package/dist/esm/channel/ai-activity-bus.js +7 -0
  98. package/dist/esm/channel/autopilot-needs-user.js +26 -0
  99. package/dist/esm/components/agent-picker/agent-picker.constants.js +10 -0
  100. package/dist/esm/components/agent-picker/agent-picker.js +11 -1
  101. package/dist/esm/components/agent-picker/agent-picker.template.js +2 -4
  102. package/dist/esm/components/ai-driver/ai-driver.js +2 -0
  103. package/dist/esm/components/ai-driver/base-driver-decorator.js +8 -0
  104. package/dist/esm/components/chat-bubble/chat-bubble.js +45 -0
  105. package/dist/esm/components/chat-bubble/chat-bubble.styles.js +26 -0
  106. package/dist/esm/components/chat-bubble/chat-bubble.template.js +13 -0
  107. package/dist/esm/components/chat-driver/chat-driver.js +53 -18
  108. package/dist/esm/components/chat-driver/interaction-controller.js +297 -12
  109. package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +16 -2
  110. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -11
  111. package/dist/esm/config/define-stateful-agent.js +34 -29
  112. package/dist/esm/genesis/write-outcome.js +2 -2
  113. package/dist/esm/index.js +3 -0
  114. package/dist/esm/main/main.js +334 -15
  115. package/dist/esm/main/main.styles.js +126 -0
  116. package/dist/esm/main/main.template.js +116 -11
  117. package/dist/esm/state/debug-event-log.js +28 -1
  118. package/dist/esm/state/decision-surface.js +209 -0
  119. package/dist/esm/state/interaction-context.js +89 -1
  120. package/dist/esm/state/interaction-registry.js +33 -0
  121. package/dist/esm/state/persistence/session-persister.js +23 -11
  122. package/dist/esm/state/persistence/session-snapshot.js +1 -1
  123. package/dist/esm/state/run-steps.js +77 -0
  124. package/dist/esm/types/interaction-surface.js +1 -0
  125. package/dist/esm/utils/context-tokens.js +23 -9
  126. package/dist/esm/utils/delegated-message.js +15 -0
  127. package/dist/esm/utils/history-transform.js +18 -7
  128. package/dist/esm/utils/preserved-user-messages.js +31 -0
  129. package/dist/react.cjs +7 -0
  130. package/dist/react.mjs +6 -0
  131. package/docs/styling.md +1 -1
  132. package/package.json +17 -17
  133. package/sandbox/README.md +38 -2
  134. package/sandbox/autopilot.ts +358 -0
  135. package/sandbox/controls.ts +24 -1
  136. package/sandbox/fixtures.ts +2 -0
  137. package/sandbox/sandbox.ts +188 -1
@@ -201,6 +201,14 @@ const baseStyles = css `
201
201
  white-space: nowrap;
202
202
  }
203
203
 
204
+ /* The user's own messages a compaction kept word for word (GENC-1660), under its divider. */
205
+ .compaction-preserved {
206
+ margin: 0 0 calc(var(--design-unit) * 2px);
207
+ padding-inline-start: calc(var(--design-unit) * 5px);
208
+ font-size: var(--type-ramp-minus-1-font-size);
209
+ color: var(--neutral-foreground-hint);
210
+ }
211
+
204
212
  .message-row {
205
213
  display: flex;
206
214
  align-items: flex-start;
@@ -637,6 +645,7 @@ const baseStyles = css `
637
645
  .composer-resize-handle,
638
646
  .attachment-chips,
639
647
  .blocked-banner,
648
+ .autopilot-bar,
640
649
  chat-suggestions {
641
650
  box-sizing: border-box;
642
651
  width: 100%;
@@ -1162,6 +1171,123 @@ const baseStyles = css `
1162
1171
  margin-block-start: calc(var(--blocked-banner-gap) * -1);
1163
1172
  }
1164
1173
 
1174
+ /* ── Autopilot (GENC-1660 §4.10) ──
1175
+ A pending widget the autopilot holds whose registration asks for no chrome: the view renders
1176
+ nothing, so the row is dropped rather than left as an empty bubble. Still mounted, so the view
1177
+ keeps its subscription and reappears the moment the person holds it. */
1178
+ .message-row.held-hidden {
1179
+ display: none;
1180
+ }
1181
+
1182
+ /* Pointed out after the person opened the chat from the "needs you" badge: a brief pulse. */
1183
+ .message.needs-attention {
1184
+ animation: needs-attention 0.8s ease-in-out 3;
1185
+ }
1186
+
1187
+ @keyframes needs-attention {
1188
+ 0%,
1189
+ 100% {
1190
+ box-shadow: 0 0 0 0 transparent;
1191
+ }
1192
+
1193
+ 50% {
1194
+ box-shadow: 0 0 0 3px var(--accent-fill-rest);
1195
+ }
1196
+ }
1197
+
1198
+ /* Without motion, the same ring held still while it is pointed out. */
1199
+ @media (prefers-reduced-motion: reduce) {
1200
+ .message.needs-attention {
1201
+ animation: none;
1202
+ box-shadow: 0 0 0 3px var(--accent-fill-rest);
1203
+ }
1204
+ }
1205
+
1206
+ /* The autopilot mark: subtle, beside the sender label. */
1207
+ .delegate-mark {
1208
+ margin-inline-start: calc(var(--design-unit) * 1px);
1209
+ font-size: var(--type-ramp-minus-1-font-size);
1210
+ color: var(--neutral-foreground-hint);
1211
+ }
1212
+
1213
+ /* The hand-back banner, at the top of the handed-back widget's own bubble. */
1214
+ .autopilot-banner {
1215
+ display: flex;
1216
+ align-items: center;
1217
+ gap: calc(var(--design-unit) * 2px);
1218
+ margin-bottom: calc(var(--design-unit) * 2px);
1219
+ padding: calc(var(--design-unit) * 1.5px) calc(var(--design-unit) * 2px);
1220
+ border-radius: calc(var(--control-corner-radius) * 1px);
1221
+ background: var(--neutral-layer-2);
1222
+ color: var(--neutral-foreground-rest);
1223
+ }
1224
+
1225
+ .autopilot-banner-text {
1226
+ flex: 1 1 auto;
1227
+ min-width: 0;
1228
+ }
1229
+
1230
+ /* Its label sets its width: shrunk by the text beside it, the label spilled out of the bubble. */
1231
+ .autopilot-keep-paused {
1232
+ flex: 0 0 auto;
1233
+ }
1234
+
1235
+ .autopilot-mark {
1236
+ flex: 0 0 auto;
1237
+ color: var(--accent-foreground-rest);
1238
+ }
1239
+
1240
+ /* The slim bar: the composer's footprint with no text box, so nothing can be typed by accident. */
1241
+ .autopilot-bar {
1242
+ display: flex;
1243
+ align-items: center;
1244
+ gap: calc(var(--design-unit) * 2px);
1245
+ padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
1246
+ border-top: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-rest);
1247
+ background: var(--neutral-layer-1);
1248
+ color: var(--neutral-foreground-rest);
1249
+ }
1250
+
1251
+ .autopilot-status {
1252
+ flex: 1 1 auto;
1253
+ min-width: 0;
1254
+ overflow: hidden;
1255
+ text-overflow: ellipsis;
1256
+ white-space: nowrap;
1257
+ }
1258
+
1259
+ /* The autopilot's Pause / Resume pill: its glyph is the mark, tinted as the mark is elsewhere.
1260
+ Uncapped, unlike the agent pill: its label is short and fixed, and clipped it says nothing. */
1261
+ .autopilot-toggle {
1262
+ max-width: none;
1263
+ }
1264
+
1265
+ .autopilot-toggle .pill-glyph {
1266
+ color: var(--accent-foreground-rest);
1267
+ }
1268
+
1269
+ /* On: filled as the person's own bubble is, since it is answering for them. */
1270
+ .autopilot-toggle.is-on,
1271
+ .autopilot-toggle.is-on:hover {
1272
+ color: var(--foreground-on-accent-rest);
1273
+ background: linear-gradient(
1274
+ 135deg,
1275
+ var(--accent-fill-rest) 0%,
1276
+ color-mix(in srgb, var(--accent-fill-rest) 70%, black) 100%
1277
+ );
1278
+ }
1279
+
1280
+ .autopilot-toggle.is-on .pill-glyph {
1281
+ color: inherit;
1282
+ }
1283
+
1284
+ .autopilot-bar-end {
1285
+ display: flex;
1286
+ flex: 0 0 auto;
1287
+ align-items: center;
1288
+ gap: calc(var(--design-unit) * 1.5px);
1289
+ }
1290
+
1165
1291
  .file-input {
1166
1292
  display: none;
1167
1293
  }
@@ -37,6 +37,8 @@ function unknownToolPayload(tc) {
37
37
  }
38
38
  return lines.join('\n');
39
39
  }
40
+ /** The autopilot mark (GENC-1660 §4.5): one icon wherever the assistant acted in autopilot mode. */
41
+ const AUTOPILOT_ICON = 'wand-magic-sparkles';
40
42
  const HALO_SPEED_DEFAULT = 1.5;
41
43
  const HALO_SPEED_ORCHESTRATING = 0.4;
42
44
  const HALO_BORDER_SIZE_DEFAULT = 3;
@@ -47,6 +49,10 @@ const messageType = (m) => {
47
49
  switch (true) {
48
50
  case m.role === 'system-event':
49
51
  return 'agent-switch';
52
+ // Written on the user's behalf (GENC-1660 §4.5): it renders as the assistant, whatever its
53
+ // role, so it is never a user bubble the person did not write.
54
+ case !!m.delegate && (m.role === 'user' || m.role === 'synthetic-user'):
55
+ return 'ai';
50
56
  case m.role === 'user':
51
57
  // A synthetic-user message is a display-only echo of an interaction
52
58
  // outcome; render it identically to a real user message.
@@ -377,8 +383,8 @@ export const blockedBannerClasses = (x) => [
377
383
  .join(' ');
378
384
  /**
379
385
  * A message's interaction widget, in the wrapper that mounts it. Everything the widget is shown
380
- * comes from the history message through here, the outcome a tool recorded after the answer
381
- * included, so a remount, a pop-out and a reload all show the same thing.
386
+ * comes from the history message through here, the outcome a tool recorded after the answer and the
387
+ * edits a claimant made included, so a remount, a pop-out and a reload all show the same thing.
382
388
  *
383
389
  * @internal
384
390
  */
@@ -389,6 +395,7 @@ export const interactionWrapperTemplate = html `
389
395
  :interactionId=${(m) => m.interaction.interactionId}
390
396
  :resolved=${(m) => m.interaction.resolved}
391
397
  :outcome=${(m) => m.interaction.outcome}
398
+ :edits=${(m) => m.interaction.edits}
392
399
  @interaction-completed=${(m, c) => c.parent.handleInteractionCompleted(c.event)}
393
400
  @interaction-cost=${(m, c) => c.parent.handleInteractionCost(c.event)}
394
401
  ></ai-chat-interaction-wrapper>
@@ -583,6 +590,66 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
583
590
  ${when((tc) => { var _a; return !!((_a = tc.subAgentTrace) === null || _a === void 0 ? void 0 : _a.length); }, subAgentTraceTemplate)}
584
591
  `;
585
592
  // ── Message row ─────────────────────────────────────────────────────────────
593
+ // ── Autopilot (GENC-1660 §4.10) ─────────────────────────────────────────────
594
+ //
595
+ // The hand-back banner sits at the top of the handed-back widget's own bubble; the slim bar takes
596
+ // the composer's place while the autopilot runs. Both read the host's `AutopilotHost`. Neither is
597
+ // a live region: each is inserted already holding its text, which screen readers widely miss (see
598
+ // `blockedBannerTemplate`). The announcer below says what they show instead.
599
+ // Mounted once, empty, and only its text changes: the hand-back reason while the autopilot waits
600
+ // on an answer, otherwise the slim bar's status while that shows.
601
+ const autopilotAnnouncerTemplate = html `
602
+ <span class="visually-hidden" part="autopilot-announcer" role="status">
603
+ ${(x) => x.autopilotAnnouncement}
604
+ </span>
605
+ `;
606
+ const autopilotBannerTemplate = html `
607
+ <div class="autopilot-banner" part="autopilot-banner">
608
+ <${iconTag} class="autopilot-mark" name="${AUTOPILOT_ICON}"></${iconTag}>
609
+ <span class="autopilot-banner-text" part="autopilot-banner-text">
610
+ ${(m, c) => {
611
+ var _a;
612
+ return [(_a = c.parent.autopilotCopy) === null || _a === void 0 ? void 0 : _a.handBackLead, c.parent.handBackReasonFor(m)]
613
+ .filter(Boolean)
614
+ .join(' ');
615
+ }}
616
+ </span>
617
+ <${buttonTag}
618
+ class="autopilot-keep-paused"
619
+ part="autopilot-keep-paused"
620
+ appearance="stealth"
621
+ @click=${(m, c) => {
622
+ c.parent.handleKeepPaused(m.interaction.interactionId);
623
+ return true;
624
+ }}
625
+ >${(m, c) => { var _a; return (_a = c.parent.autopilotCopy) === null || _a === void 0 ? void 0 : _a.keepPausedLabel; }}</${buttonTag}>
626
+ </div>
627
+ `;
628
+ // The autopilot's on/off toggle, as the composer's own labelled pill: the mark is its glyph, and
629
+ // it is filled the way the person's own bubble is while it runs, since it is filling in for them.
630
+ // A toggle: `aria-pressed` carries its state, so its accessible name is constant (the pill's own
631
+ // label) and what the click does — Pause / Resume — leads its tooltip instead.
632
+ const autopilotToggleTemplate = html `
633
+ <${buttonTag}
634
+ class="${(x) => classNames('composer-pill', 'composer-pill-labelled', 'autopilot-toggle', [
635
+ 'is-on',
636
+ x.autopilotCanPause,
637
+ ])}"
638
+ part="autopilot-toggle"
639
+ appearance="stealth"
640
+ aria-pressed="${(x) => (x.autopilotCanPause ? 'true' : 'false')}"
641
+ title="${(x) => x.autopilotToggleTitle}"
642
+ aria-label="${(x) => { var _a; return (_a = x.autopilotCopy) === null || _a === void 0 ? void 0 : _a.toggleLabel; }}"
643
+ @click=${(x) => {
644
+ if (x.autopilotCanPause)
645
+ x.handleAutopilotPause();
646
+ else
647
+ x.handleAutopilotResume();
648
+ return true;
649
+ }}
650
+ ><span class="pill-glyph"><${iconTag} name="${AUTOPILOT_ICON}"></${iconTag}></span
651
+ ><span class="pill-label">${(x) => { var _a; return (_a = x.autopilotCopy) === null || _a === void 0 ? void 0 : _a.toggleLabel; }}</span></${buttonTag}>
652
+ `;
586
653
  const messageRowTemplate = html `
587
654
  ${when((m) => m.role === 'system-event', html `
588
655
  <div class="agent-switch-indicator" part="agent-switch-indicator">
@@ -593,10 +660,17 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
593
660
  <div class="agent-switch-indicator compaction-divider" part="compaction-divider">
594
661
  <span class="agent-switch-label">${(m) => compactionDividerLabel(m)}</span>
595
662
  </div>
663
+ ${when((m) => { var _a, _b; return !!((_b = (_a = m.compaction) === null || _a === void 0 ? void 0 : _a.preservedUserMessages) === null || _b === void 0 ? void 0 : _b.length); }, html `
664
+ <ul class="compaction-preserved" part="compaction-preserved">
665
+ ${repeat((m) => { var _a, _b; return (_b = (_a = m.compaction) === null || _a === void 0 ? void 0 : _a.preservedUserMessages) !== null && _b !== void 0 ? _b : []; }, html `
666
+ <li>${(u) => u.content}</li>
667
+ `)}
668
+ </ul>
669
+ `)}
596
670
  `)}
597
671
  ${when((m) => m.role !== 'system-event' && m.role !== 'compacted-summary', html `
598
672
  <div
599
- class="message-row ${(m) => messageType(m)} ${(m) => m.interaction ? `present-${interactionPresentation(m)}` : ''}"
673
+ class="message-row ${(m) => messageType(m)} ${(m) => m.interaction ? `present-${interactionPresentation(m)}` : ''} ${(m, c) => c.parent.isHiddenWhileHeld(m) ? 'held-hidden' : ''}"
600
674
  part="${(m) => messageRowParts(m)}"
601
675
  >
602
676
  <div
@@ -610,7 +684,11 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
610
684
  (m) => messageType(m) === 'user', userAvatarTemplate)}${when((m) => messageType(m) !== 'user', assistantAvatarTemplate)}
611
685
  </div>
612
686
  <div
613
- class="message ${(m) => messageType(m)} ${(m) => classNames(['narration', m.category === 'narration'])}"
687
+ class="message ${(m) => messageType(m)} ${(m, c) => classNames(['narration', m.category === 'narration'], [
688
+ 'needs-attention',
689
+ !!m.interaction &&
690
+ m.interaction.interactionId === c.parent.attentionInteractionId,
691
+ ])}"
614
692
  part="${(m) => messageParts(m)}"
615
693
  >
616
694
  ${when(
@@ -628,6 +706,15 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
628
706
  : senderLabelFor(m);
629
707
  }}
630
708
  </span>
709
+ ${when((m, c) => c.parent.showsDelegateMark(m), html `
710
+ <${iconTag}
711
+ class="delegate-mark"
712
+ part="delegate-mark"
713
+ name="${AUTOPILOT_ICON}"
714
+ title="${(m, c) => c.parent.delegateMarkLabel}"
715
+ aria-label="${(m, c) => c.parent.delegateMarkLabel}"
716
+ ></${iconTag}>
717
+ `)}
631
718
  ${when(
632
719
  // Guarded on the stamp too: a session persisted before timestamp
633
720
  // support restores without one, and an empty <time> would still
@@ -650,6 +737,7 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
650
737
  ${when((m) => m.toolCalls, html `
651
738
  ${repeat((m) => { var _a; return (_a = m.toolCalls) !== null && _a !== void 0 ? _a : []; }, toolCallItemTemplate)}
652
739
  `)}
740
+ ${when((m, c) => c.parent.handBackReasonFor(m) !== undefined, autopilotBannerTemplate)}
653
741
  ${when((m) => m.interaction, interactionWrapperTemplate)}
654
742
  </div>
655
743
  ${when((m, c) => { var _a; return isRateableReply(m) && ((_a = c.parent.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.messageFeedback) != null; }, html `
@@ -687,7 +775,7 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
687
775
  class="pill-glyph"
688
776
  style="${(x) => (x.pinnedAgentColour ? `color: ${x.pinnedAgentColour}` : '')}"
689
777
  ><${iconTag}
690
- name="${(x) => (x.pinnedAgentName === null ? 'a' : 'thumbtack')}"
778
+ name="${(x) => (x.pinnedAgentName === null ? 'sitemap' : 'thumbtack')}"
691
779
  ></${iconTag}></span
692
780
  ><span class="pill-label">${(x) => x.agentButtonLabel}</span></${buttonTag}>
693
781
  `;
@@ -735,13 +823,14 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
735
823
  ><${iconTag} name="triangle-exclamation"></${iconTag}></span
736
824
  ><span class="pill-label">${(x) => x.contextGateLabel}</span>`)}</${buttonTag}>
737
825
  `;
738
- // The agent control is LAST in the cluster because it is the only one whose
739
- // width changes: pinning an agent swaps `AUTO` for a name, and with it in
740
- // front the fixed-size buttons would slide sideways every time the pin
741
- // changed. Trailing, it grows into the empty middle of the bar instead.
826
+ // The agent control comes after the fixed-size buttons because its width changes: pinning an
827
+ // agent swaps `Orchestration` for a name, and in front of them they would slide sideways every time
828
+ // the pin changed. The autopilot pill follows it, shown whenever the composer is up with the
829
+ // autopilot available — paused, or running with nothing happening — and it does move with the
830
+ // agent pill's width; it sits with the controls it matches rather than at the far end.
742
831
  const composerBarStartTemplate = html `
743
832
  <div class="composer-bar-start" part="composer-bar-start">
744
- ${when((x) => { var _a; return !!((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.acceptedFiles); }, attachButtonTemplate)}${when((x) => x.sessionMenuEnabled, sessionMenuButtonTemplate)}${when((x) => x.agentPickerEnabled, agentToggleButtonTemplate)}
833
+ ${when((x) => { var _a; return !!((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.acceptedFiles); }, attachButtonTemplate)}${when((x) => x.sessionMenuEnabled, sessionMenuButtonTemplate)}${when((x) => x.agentPickerEnabled, agentToggleButtonTemplate)}${when((x) => x.composerAutopilotVisible, autopilotToggleTemplate)}
745
834
  </div>
746
835
  `;
747
836
  // ── Send / Stop ─────────────────────────────────────────────────────────────
@@ -783,6 +872,18 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
783
872
  @click=${(x) => x.handleSendClick()}
784
873
  >Send</${buttonTag}>
785
874
  </span>
875
+ `;
876
+ // The slim bar, after Stop: it reuses the same button.
877
+ const autopilotBarTemplate = html `
878
+ <div class="autopilot-bar" part="autopilot-bar">
879
+ <span class="autopilot-status" part="autopilot-status">
880
+ ${(x) => { var _a; return (_a = x.autopilotState) === null || _a === void 0 ? void 0 : _a.status; }}
881
+ </span>
882
+ <div class="autopilot-bar-end" part="autopilot-bar-end">
883
+ ${autopilotToggleTemplate}
884
+ ${when((x) => x.stopButtonVisible && !x.autopilotSelfPaused, stopButtonTemplate)}
885
+ </div>
886
+ </div>
786
887
  `;
787
888
  // ── Composer menus ──────────────────────────────────────────────────────────
788
889
  //
@@ -799,6 +900,8 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
799
900
  :agents="${(x) => { var _a; return (_a = x.agents) !== null && _a !== void 0 ? _a : []; }}"
800
901
  :pinnedAgentName="${(x) => x.pinnedAgentName}"
801
902
  :disablePinColours="${(x) => { var _a; return !!((_a = x.chatConfig.picker) === null || _a === void 0 ? void 0 : _a.disablePinColours); }}"
903
+ :orchestrationLabel="${(x) => x.orchestrationMenuLabel}"
904
+ :orchestrationDescription="${(x) => x.orchestrationDescription}"
802
905
  @agent-pinned="${(x, c) => {
803
906
  x.pinnedAgentName = c.event.detail;
804
907
  x.toggleAgentPicker();
@@ -1073,8 +1176,10 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
1073
1176
  /* Rendered above the composer and independently of it — a host that hides the
1074
1177
  built-in composer (`composerHiddenByConfig`) still needs to be told why its own
1075
1178
  input is being refused. */ ''}
1076
- ${blockedBannerTemplate}
1179
+ ${blockedBannerTemplate} ${autopilotAnnouncerTemplate}
1180
+ ${when((x) => x.slimBarVisible, autopilotBarTemplate)}
1077
1181
  ${when((x) => !x.composerHiddenByConfig &&
1182
+ !x.slimBarVisible &&
1078
1183
  !(x.busy && x.effectiveChatInputDuringExecution === 'hidden'), html `
1079
1184
  ${when((x) => x.isComposerResizable, html `
1080
1185
  <div
@@ -76,10 +76,36 @@ export const META_EVENT_IMPORTANCE = {
76
76
  'interaction.outcome': 'normal',
77
77
  // An outcome that could not be recorded: the widget goes on showing only the answer.
78
78
  'interaction.outcome-dropped': 'high',
79
+ // A long action's step starting, finishing, or skipped on resume because it was already done.
80
+ 'interaction.step': 'low',
81
+ // A cut checkpoint taken over after a reload, refused (by sameWork, or as malformed), or dropped
82
+ // at flow end.
83
+ 'interaction.checkpoint': 'normal',
79
84
  // A claimant handing a pending interaction to the person, or taking it back.
80
85
  'interaction.held': 'normal',
81
86
  // A claim on a `userOnly` interaction, refused. Expected when an interceptor claims broadly.
82
87
  'interaction.claim-refused': 'low',
88
+ // A claimant ran one of the surface's actions: what it did, and whether that settled it.
89
+ 'interaction.invoked': 'normal',
90
+ 'autopilot.claimed': 'normal',
91
+ // One per interaction the autopilot does not take, so frequent and mostly expected.
92
+ 'autopilot.declined': 'low',
93
+ 'autopilot.decision-sent': 'low',
94
+ 'autopilot.acted': 'normal',
95
+ 'autopilot.handed-back': 'normal',
96
+ 'autopilot.paused': 'normal',
97
+ 'autopilot.resumed': 'normal',
98
+ // The wrapper itself failed: the journey stops being automatic without anyone choosing that.
99
+ 'autopilot.error': 'high',
100
+ // A written answer counted against the step's limit; reaching it withdraws `reply`.
101
+ 'autopilot.refinement': 'normal',
102
+ // The autopilot showed its own widget because a working turn ended needing the person.
103
+ 'autopilot.handback-presented': 'normal',
104
+ // The autopilot was given the whole visible transcript: its first decision, a rebuild, or after
105
+ // a compaction removed the last message it saw.
106
+ 'autopilot.catch-up': 'normal',
107
+ // Autopilot spend that reached no message, so it is missing from the session total.
108
+ 'autopilot.cost-dropped': 'high',
83
109
  'external-diagnostics.folded': 'normal',
84
110
  'session.cleared': 'normal',
85
111
  'session.switched': 'low',
@@ -227,6 +253,7 @@ export const DEBUG_LOG_README = [
227
253
  '`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
228
254
  'Timestamps are millisecond-resolution; entries that share the same millisecond are ordered by a fixed kind rank (event, then turn, then message), which is a heuristic and may not reflect exact causal order within that millisecond — e.g. a user message and the turn it triggered, or a final assistant message and its turn.end event, can appear in either order depending on whether they landed in the same millisecond. Read the logical structure of a turn rather than over-interpreting the micro-ordering of co-timestamped entries of different kinds.',
229
255
  "kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, where `inputTokens` is the WHOLE prompt for that request and `cacheReadTokens`/`cacheWriteTokens` BREAK IT DOWN rather than add to it — uncached input is `inputTokens` minus those two, and adding the cache fields to `inputTokens` counts the prompt twice. The cache fields are absent on providers that report no cache split (Gemini reports reads only, since implicit caching bills no write) and on messages persisted before they existed, so read them as 0 when missing. Each bucket bills at a different rate — cache reads a fraction of uncached input, cache writes a premium, output highest — so a large token count at a small cost means the prompt was mostly cache hits. `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). On model-produced assistant messages, `model` is the concrete model id that generated it (e.g. 'gemini-2.5-flash-lite') and `providerName` is the registry slot it resolved under (e.g. a tier name like 'high'/'low', or the default); together they attribute the message — and any tool calls it carries — to an exact model even across a mid-session vendor/tier switch, where one slot name can map to different models before and after the switch. Both are undefined on any entry that is NOT an LLM response: non-assistant roles (user/tool/system-event) and 'synthetic-user' echoes; assistant interaction/widget entries (empty content carrying an `interaction` — a rendered widget, not a model turn); driver-authored assistant fallbacks (the timeout, repeated-malformed-call, and empty-response apology messages); and messages restored from a session persisted before these fields existed. One partial case: on a genuine model turn whose provider exposes no `getStatus` (or reports no model), `providerName` is still set but `model` alone is undefined. A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result. An echo of an answer an interceptor (such as an autopilot) gave is NOT synthetic-user: see the delegated-messages paragraph.",
256
+ 'The autopilot (GENC-1660) runs on a driver of its own, and everything it did is harvested into this log: entries with delegate: autopilot and an agentName ending in › Autopilot are its turns (the decision message it was sent is the user message of its turn, so what it was shown is there verbatim; its systemPrompt is its grounding, such as the brief), its tool calls (widget_read, widget_act, hand_back) and their results, and its own events. Its cost is already in the session total: it is booked as externalCostUsd on the interaction each decision answered, or, for a written question it answered, on the user message it sent on the user behalf (delegate: autopilot), so do not add its turns to that total. Spend that reached no message is recorded as autopilot.cost-dropped instead.',
230
257
  "Delegated messages (GENC-1660): a message with a `delegate` field was written or sent ON THE USER'S BEHALF by a driver decorator, and `delegate` names it (e.g. autopilot). The platform stamps it; nothing else can. It renders as the assistant whatever its role. A delegated `user` message was NOT typed by the user: it is an answer given on their behalf, and the model read it with `delegateFraming` in brackets in front of the content (the stored `content` is the bare text). A delegated `assistant` message is display-only (`excludeFromProvider`): the decorator's narration, or the echo of a widget answer it gave, which takes the place of a synthetic-user echo. To tell what the person decided from what was decided for them, read `interaction.answeredBy` and these fields, never the role alone.",
231
258
  "Sub-agent messages appear inline. When a tool delegates to a sub-agent (via `requestSubAgent`), the sub-agent's whole conversation — its own assistant/tool messages, each with their own `content`/`thinking`/`toolCalls`/`toolResult` and per-message `model`/`providerName`/`inputTokens`/`outputTokens`/`cacheReadTokens`/`cacheWriteTokens`/`cost` — is hoisted into the timeline as ordinary kind:'message' entries, interleaved by timestamp right after the tool call that spawned them (so you read the delegation top-to-bottom). A hoisted entry is marked: `subAgentDepth` is its delegation depth (1 for a sub-agent, 2 for a sub-agent's sub-agent, …), `subAgentOf` is the id of the parent tool call that spawned it (correlates it back even when two sub-agents run in one parent turn), `subAgentName` is the sub-agent's own name, and `agentName` is rewritten to a `\"<parent> › <sub-agent>\"` breadcrumb (composing when nested, e.g. `\"UI Builder › Planner › Grounding\"`). The sub-agent's per-LLM-call snapshots also surface as kind:'turn' entries with an N-M `turnIndex`, and subagent.started/completed (or subagent.failed) events bracket the run. Per-message `cost` on hoisted entries is already part of the session total (it is summed from the un-flattened history), so summing the top-level timeline does NOT double-count.",
232
259
  "kind:'turn' — one LLM call. `turnIndex` is a string: a top-level turn is the bare counter ('0', '1', …); a sub-agent's turns are numbered under the parent turn that activated them ('3-1', '3-2', …, and a nested sub-agent contributes '3-2-1', …), and `agentName` names the agent that ran the turn. `systemPrompt` and `toolNames` are what the model saw. A systemPrompt of '<repeated — identical to turn N>' was byte-identical to turn N and de-duplicated; the full prompt is shown whenever it changes (often because a stateful agent advanced), so prompt evolution is visible.",
@@ -235,7 +262,7 @@ export const DEBUG_LOG_README = [
235
262
  "kind:'turn'.`agentSnapshot` — the active agent's own view of its internal state, captured at that turn. An agent opts into this by exposing a `getDebugSnapshot()` that returns JSON-serializable per-state info; stateful/flow agents wire it automatically, so you can watch a flow advance turn-by-turn (e.g. current step, cursor, collected fields, pending changes). Absent for agents that don't expose one.",
236
263
  "kind:'event' — a meta/lifecycle event. `type` names it (see below); `detail` carries structured data. `detail.placement` is the emitting UI instance: 'bubble' (collapsed), 'panel' (popped-out), or 'standalone'.",
237
264
  "Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, subagent.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions, sub-agent start/complete), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated, context.condensed). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. A 'high' turn.error is often preceded by one or more 'normal' turn.retry events for the same reason — read them together to see how many attempts were made before bailing. 'message' and 'turn' entries carry no importance — they are the substance, always read them.",
238
- 'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated/refusal/budget-exhausted/provider-refused/context-exhausted/ai-service-error, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, for budget-exhausted the budgetUsd + spentUsd figures reported by the proxy plus the resolved vendor, for provider-refused the kind (spend|auth) plus the upstream status and error type — the vendor refusing the ACCOUNT rather than us refusing to spend, so the remedy is a top-up or a key rotation rather than a raised cap, and the kind is the only place that distinction survives because the user-facing copy is deliberately cause-free, for context-exhausted the estimated contextTokens against the guard that stopped the turn plus the iterations reached — the driver ending a runaway tool loop at the edge of the context window rather than issuing a request the provider would reject, so the transcript survives intact and can still be compacted, for ai-service-error the code (one of AI_SERVICE_ERROR_CODES, e.g. NO_API_KEY) and the status the AI service answered with — its own text goes to the browser console only, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated/refusal/budget_exhausted/provider_refused/context_exhausted/ai_service_error; budget_exhausted and provider_refused are terminal for the PARENT turn too, and so is ai_service_error when the refusal is about the service rather than the request and came from the vendor the parent calls — the parent stops rather than calling the model again into the same wall; after context_exhausted the parent goes on, but its own next call is likely to stop on the same guard), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), routing.decided (ONE PER ROUTING DECISION — see its own paragraph below), provider.selected (the resolved provider for the upcoming turns — detail.provider is the registry SLOT/tier name, detail.model the concrete model behind it and detail.vendor its vendor; emitted only when the slot CHANGES, so read the per-turn `model` for the model of any given call rather than assuming the nearest event still applies), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver; detail.ownerPath names the agents that actually asked, outermost first, the last being the one whose tool requested it, and detail.heldBy says who held it when it opened: user, or the id of the interceptor that claimed it, such as autopilot; on interaction.resolved, detail.answeredBy is who answered — the claimant id when it settled through its own hold, user for any other answer (a click counts as the person even on a widget a claimant held) — absent when it closed unanswered (a timeout or a stop); the same value is stamped on the interaction message as answeredBy, and when it is not user the tool result that interaction returned carries a decidedOnUsersBehalf record, so the model was told the choice was not made by the person), interaction.held (a claimant moved a pending interaction to the person or back to itself — detail.heldBy is the new holder, detail.claimant the interceptor; a widget held by anything but user was not answered by the person), interaction.claim-refused (a claim on a userOnly interaction, which stays with the person — detail.claimant + reason), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
265
+ 'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated/refusal/budget-exhausted/provider-refused/context-exhausted/ai-service-error, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, for budget-exhausted the budgetUsd + spentUsd figures reported by the proxy plus the resolved vendor, for provider-refused the kind (spend|auth) plus the upstream status and error type — the vendor refusing the ACCOUNT rather than us refusing to spend, so the remedy is a top-up or a key rotation rather than a raised cap, and the kind is the only place that distinction survives because the user-facing copy is deliberately cause-free, for context-exhausted the estimated contextTokens against the guard that stopped the turn plus the iterations reached — the driver ending a runaway tool loop at the edge of the context window rather than issuing a request the provider would reject, so the transcript survives intact and can still be compacted, for ai-service-error the code (one of AI_SERVICE_ERROR_CODES, e.g. NO_API_KEY) and the status the AI service answered with — its own text goes to the browser console only, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated/refusal/budget_exhausted/provider_refused/context_exhausted/ai_service_error; budget_exhausted and provider_refused are terminal for the PARENT turn too, and so is ai_service_error when the refusal is about the service rather than the request and came from the vendor the parent calls — the parent stops rather than calling the model again into the same wall; after context_exhausted the parent goes on, but its own next call is likely to stop on the same guard), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), routing.decided (ONE PER ROUTING DECISION — see its own paragraph below), provider.selected (the resolved provider for the upcoming turns — detail.provider is the registry SLOT/tier name, detail.model the concrete model behind it and detail.vendor its vendor; emitted only when the slot CHANGES, so read the per-turn `model` for the model of any given call rather than assuming the nearest event still applies), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver; detail.ownerPath names the agents that actually asked, outermost first, the last being the one whose tool requested it, and detail.heldBy says who held it when it opened: user, or the id of the interceptor that claimed it, such as autopilot; on interaction.resolved, detail.answeredBy is who answered — the claimant id when it settled through its own hold, user for any other answer (a click counts as the person even on a widget a claimant held) — absent when it closed unanswered (a timeout or a stop); the same value is stamped on the interaction message as answeredBy, and when it is not user the tool result that interaction returned carries a decidedOnUsersBehalf record, so the model was told the choice was not made by the person; detail.edits, present only when non-zero, counts the editing actions a claimant ran on the widget before it was settled, whoever settled it, and those edits are stamped on the interaction message as edits, each with by, action, choice and args when it had some, in the order they ran, while a decidedOnUsersBehalf record carries their choice texts as edits, and when the person settled it after such edits the tool result carries an editedOnUsersBehalf record instead — the framing and those choice texts — so the model knows the widget was changed on the user behalf before the user answered, and that the user then confirmed it by finishing it), interaction.held (a claimant moved a pending interaction to the person or back to itself — detail.heldBy is the new holder, detail.claimant the interceptor; a widget held by anything but user was not answered by the person), interaction.claim-refused (a claim on a userOnly interaction, which stays with the person — detail.claimant + reason), autopilot.* (the autopilot, a decorator that answers interactions on behalf of the user — autopilot.claimed / autopilot.declined say whether it took each interaction and, for a decline, detail.reason: paused, other-flow, no-autopilot, unavailable, userOnly, policy or no-controller; autopilot.decision-sent is what it was sent (detail.chars); autopilot.acted is what it did (detail.action, detail.note, detail.finished); autopilot.handed-back is a decision it gave to the person (detail.reason and detail.trigger: model, no-decision or error); autopilot.paused / autopilot.resumed are mode changes (detail.scope, detail.trigger, detail.decideYourself when the person resumed during a hand-back; detail.trigger is restored when the mode came back with the session after a reload, applied once its flow re-activated, which starts nothing by itself); autopilot.error is the wrapper itself failing; autopilot.refinement counts a written answer against its step (detail.stepKey, detail.count, detail.limitReached, after which reply is withdrawn for that step); autopilot.handback-presented is the turn-level hand-back, shown when a working turn ended needing the person with no widget to hand over (detail.why: max-iterations, context-exhausted, failure or question; detail.options; detail.choice once answered); autopilot.catch-up is the autopilot being given the whole visible transcript at once (detail.why: first, for its first decision in a flow or after a reload; size, when its own history passed its budget and was rebuilt short — detail.ownChars; or compacted, when a compaction removed the last message it had seen, so it reads the summary and everything since, short; in each, what it had sent on the user behalf is included and labelled as its own, since its own history does not hold it); autopilot.cost-dropped is autopilot spend that no message carries, so it is NOT in the session total (detail.costUsd and detail.why: handed-back, aborted or stale for a written question it answered that no reply was sent for — stale when a pause, a resume, a new turn or the end of its flow came before the answer landed, which then went nowhere — or no-recorder when the driver had no way to book a decision to its interaction); a hand-back trigger can also be budget (the decision passed its spend cap), watchdog (its work went quiet: no ready surface and no heartbeat, or its own decision stalled with no model call landing, which stops that decision) or detached (the autopilot was removed while it held the interaction, which went to the person with no reason shown). To debug one autopilot decision, follow its interactionId: interaction.requested (heldBy), autopilot.claimed, autopilot.decision-sent, autopilot.acted or autopilot.handed-back, interaction.invoked, interaction.resolved (answeredBy), then the next turn of the working agent), interaction.invoked (a claimant ran one of the actions the widget itself exposes, through its hold, the same code the button runs — detail.claimant, detail.action (an option id, select or reply) and detail.finished, whether that settled the interaction, and when it did not, the run is recorded as an edit; the choice and narration it recorded reach the result of the tool that asked as decidedOnUsersBehalf), interaction.step (a long action run as named steps through runSteps, which survive a reload — detail.step and detail.outcome: started, done, skipped when the step was already done before a reload, or resumed when it started before a reload and runs again with what it saved part-way, such as a job id it re-attaches to), interaction.checkpoint (the one checkpoint a conversation keeps for its long action — detail.action: resumed when a reload cut the work and the next request of the same widget type took it over, refused when the registration sameWork said the new request is different work (detail.saved is the identity it kept) or when runSteps found the handed-over checkpoint malformed and started fresh (detail.reason, detail.saved), or cleared when its flow ended with nothing taking it over), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
239
266
  'Sub-agent meta events: a sub-agent\'s own turn.retry/turn.error/tool.failed/tool.unresolved events are merged into this same timeline, tagged with `detail.subAgent` — a `"<parent> › <sub-agent>"` breadcrumb that composes when nested (e.g. `"UI Builder › Planner › Grounding"`) — and interleaved by their original timestamps within the subagent.started→completed/failed bracket. These are the per-attempt/per-failure signals that do NOT appear among the sub-agent\'s (hoisted) messages: a malformed/empty attempt that gets retried produces no message, and the stale-vs-hallucinated split and streak counts live only on the event. A sub-agent\'s high-volume, message-derivable events (turn.start/turn.end, provider.selected, context.updated) are intentionally NOT merged — read its hoisted messages for model/tokens/cost and turn-by-turn activity, and the bracketing subagent.* events for the run\'s span.',
240
267
  "`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, contextTokens/contextLimit/contextUsagePercent for the last call, and the session totals — sessionCostUsd, sessionTokensConsumed, and the four-bucket sessionUsage lifted to the top of this log), activeDebugSnapshot (the active agent's `getDebugSnapshot()` taken fresh at export — reflects state NOW, which may have advanced beyond the last turn's agentSnapshot), debug (optional host-supplied debug state), host, and the export timestamp.",
241
268
  'Note the two different scopes in `meta.context`: `contextTokens` is the prompt size of the LAST call (against `contextLimit`, the model context window), while `sessionUsage`/`sessionTokensConsumed` are cumulative BILLED throughput. Every turn resends the conversation, so the cumulative figure counts each turn’s prompt again in the next turn’s and is expected to dwarf the context size — that is not double-counting.',