@genesislcap/ai-assistant 14.498.0 → 14.499.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/ai-assistant.api.json +132 -0
  2. package/dist/ai-assistant.d.ts +35 -0
  3. package/dist/chat-driver.cjs +157 -0
  4. package/dist/chat-driver.cjs.map +3 -3
  5. package/dist/chat-driver.mjs +151 -0
  6. package/dist/chat-driver.mjs.map +3 -3
  7. package/dist/custom-elements.json +156 -1
  8. package/dist/dts/chat-driver-node.d.ts +7 -1
  9. package/dist/dts/chat-driver-node.d.ts.map +1 -1
  10. package/dist/dts/components/ai-driver/ai-driver.d.ts +8 -0
  11. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  12. package/dist/dts/components/chat-driver/chat-driver.d.ts +27 -0
  13. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  14. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +3 -0
  15. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  16. package/dist/dts/main/main.d.ts.map +1 -1
  17. package/dist/dts/state/debug-event-log.d.ts +1 -1
  18. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  19. package/dist/dts/state/persistence/build-timeline-entries.d.ts +37 -0
  20. package/dist/dts/state/persistence/build-timeline-entries.d.ts.map +1 -0
  21. package/dist/esm/chat-driver-node.js +16 -1
  22. package/dist/esm/components/chat-driver/chat-driver.js +103 -0
  23. package/dist/esm/components/chat-driver/chat-driver.test.js +165 -0
  24. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +4 -0
  25. package/dist/esm/main/main.js +18 -28
  26. package/dist/esm/state/debug-event-log.js +1 -0
  27. package/dist/esm/state/persistence/build-timeline-entries.js +46 -0
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +17 -17
  30. package/src/chat-driver-node.ts +20 -0
  31. package/src/components/ai-driver/ai-driver.ts +9 -0
  32. package/src/components/chat-driver/chat-driver.test.ts +215 -0
  33. package/src/components/chat-driver/chat-driver.ts +109 -0
  34. package/src/components/orchestrating-driver/orchestrating-driver.ts +6 -0
  35. package/src/main/main.ts +17 -30
  36. package/src/state/debug-event-log.ts +3 -0
  37. package/src/state/persistence/build-timeline-entries.ts +66 -0
@@ -179,6 +179,23 @@ export class ChatDriver extends EventTarget {
179
179
  * arrive. See {@link TurnSnapshot} for the captured shape.
180
180
  */
181
181
  this.turnSnapshots = [];
182
+ /**
183
+ * `turn` + `message` diagnostic-log entries harvested from an out-of-band source (a resolved
184
+ * interaction whose widget returned `InteractionResult.diagnostics` — e.g. a server-side
185
+ * ChatDriver's debug log). `event` entries are NOT kept here — they're folded into the session's
186
+ * meta-event registry via `mergeMetaEvents` (see `resolveInteraction`) so they inherit fresh host
187
+ * indices, and reach the log through `getMetaEvents`. Append-only; the host folds these into its
188
+ * debug log alongside this driver's own timeline. Not cleared here — it lives with the driver
189
+ * instance, like `turnSnapshots`, so a new chat (fresh driver) starts empty while a compaction
190
+ * (same instance) keeps it. (GENC-1461.)
191
+ */
192
+ this.externalDiagnostics = [];
193
+ /**
194
+ * Monotonic count of external-diagnostics batches harvested this driver-lifetime. Namespaces each
195
+ * batch's re-keyed `turn` entries so distinct server generations in one chat session can't collide
196
+ * on `turnIndex` in the forward-capture persister (which dedups turns by that key). (GENC-1461.)
197
+ */
198
+ this.externalDiagnosticsBatches = 0;
182
199
  /** Monotonic counter that survives agent swaps — useful for cross-referencing with history. */
183
200
  this.globalTurnIndex = 0;
184
201
  /**
@@ -606,6 +623,17 @@ export class ChatDriver extends EventTarget {
606
623
  getTurnSnapshots() {
607
624
  return this.turnSnapshots;
608
625
  }
626
+ /**
627
+ * The `turn` + `message` diagnostic-log entries harvested from resolved interactions'
628
+ * `InteractionResult.diagnostics` (e.g. a server-side ChatDriver's collated debug log). The host
629
+ * concatenates these into its own debug log so `downloadDebugLog`/the persisted diagnostics stream
630
+ * include external-engine logs; `event` entries are excluded here because they're merged into the
631
+ * session's meta-event registry instead (see `resolveInteraction`) and reach the log via
632
+ * `getMetaEvents`. Opaque, chronologically sorted by the host at assemble time. (GENC-1461.)
633
+ */
634
+ getExternalDiagnostics() {
635
+ return this.externalDiagnostics;
636
+ }
609
637
  /**
610
638
  * Merge a sub-agent's turn snapshots into this driver's buffer so they surface
611
639
  * as `kind:'turn'` entries in the exported debug log. The child runs as a
@@ -988,6 +1016,81 @@ export class ChatDriver extends EventTarget {
988
1016
  // timeout (and a timeout firing here clears its own already-fired handle).
989
1017
  if (interaction.timeoutHandle)
990
1018
  clearTimeout(interaction.timeoutHandle);
1019
+ // Harvest any out-of-band diagnostic entries the widget collated (e.g. from a server-side
1020
+ // ChatDriver) so the host's debug log + persisted diagnostics include them. Independent of the
1021
+ // message `idx` below — they describe the widget's own work, not this message.
1022
+ //
1023
+ // The harvested entries carry the SOURCE engine's own index space (0-based turns/events), which
1024
+ // would collide with this host's cursors in the forward-capture persister — turns dedup by
1025
+ // `turnIndex`, events by an `index` high-water mark — so foreign entries get silently dropped
1026
+ // from the persisted stream. Route each kind through the host's own collision-safe machinery,
1027
+ // mirroring how a sub-agent's log is folded in (see `invokeSubAgent`):
1028
+ // • `event` → `mergeMetaEvents`, which re-indexes them onto this session's monotonic event
1029
+ // counter (clearing the high-water) while preserving their timestamps; they then
1030
+ // reach the log via `getMetaEvents` in `buildTimelineEntries`, not this buffer.
1031
+ // • `turn` → re-key `turnIndex` to the turn's local position within a per-batch namespace,
1032
+ // so turns stay unique even when one interaction folds in several independent
1033
+ // server runs that each number their own turns from 0 (a consolidator carries a
1034
+ // consolidator-code run AND a table-code run), and never collide with the host's
1035
+ // own bare-integer turn keys.
1036
+ // • `message` → kept as-is; the persister already keys messages by content, so they survive.
1037
+ // TODO(GENC-1461, option 2 / streaming): expose an imperative `appendDiagnostics(entries)` on
1038
+ // the handler context (see buildHandlerContext) that runs this same decomposition as SSE ticks
1039
+ // arrive, so a streaming widget can surface its external engine's log live rather than only at
1040
+ // resolve time. Batch-at-resolve (here) is the current, non-streaming path.
1041
+ const harvestedDiagnostics = result === null || result === void 0 ? void 0 : result.diagnostics;
1042
+ if (Array.isArray(harvestedDiagnostics) && harvestedDiagnostics.length) {
1043
+ const batch = (this.externalDiagnosticsBatches += 1);
1044
+ const externalEvents = [];
1045
+ let turnCount = 0;
1046
+ let messageCount = 0;
1047
+ for (const raw of harvestedDiagnostics) {
1048
+ // The harvest is external and typed `unknown[]`, so an element could be a primitive, null,
1049
+ // or an object without a string `kind`. Guard before reading `.kind` so a malformed entry
1050
+ // can't throw here (and gets skipped rather than derailing the whole fold).
1051
+ if (!raw ||
1052
+ typeof raw !== 'object' ||
1053
+ typeof raw.kind !== 'string') {
1054
+ continue;
1055
+ }
1056
+ const entry = raw;
1057
+ if (entry.kind === 'event') {
1058
+ const { kind: _kind } = entry, event = __rest(entry, ["kind"]);
1059
+ externalEvents.push(event);
1060
+ }
1061
+ else if (entry.kind === 'turn') {
1062
+ // Local position within THIS harvest, not the source engine's own `turnIndex`: one
1063
+ // interaction can carry several independent server runs (a consolidator folds in its
1064
+ // consolidator-code run and its table-code run), each numbering its turns from 0, so
1065
+ // keying by the source index would collide them. The local position keeps every turn key
1066
+ // distinct within the batch, so the persister's per-`turnIndex` dedup keeps them all.
1067
+ this.externalDiagnostics.push(Object.assign(Object.assign({}, entry), { turnIndex: `server-generation.${batch}:${turnCount}` }));
1068
+ turnCount += 1;
1069
+ }
1070
+ else if (entry.kind === 'message') {
1071
+ messageCount += 1;
1072
+ this.externalDiagnostics.push(entry);
1073
+ }
1074
+ // Any other kind is intentionally dropped, not folded. A sibling's `buildTimelineEntries`
1075
+ // only ever emits message/turn/event; `meta-snapshot` is host/DOM-owned (latest-wins on
1076
+ // reassembly, plus its own persister dedup), so folding a foreign one would pollute this
1077
+ // host's timeline and meta cursor. Restricting to the three real kinds also keeps the
1078
+ // manifest counts accurate (nothing miscounted as a `message`).
1079
+ }
1080
+ if (externalEvents.length)
1081
+ mergeMetaEvents(this.sessionKey, externalEvents);
1082
+ // Manifest: one provenance marker for the fold itself — records that an out-of-band engine's
1083
+ // log crossed into this session and how much of each kind, so a regression that silently
1084
+ // drops a generation's diagnostics (e.g. a new path that forgets to thread them across the
1085
+ // boundary) surfaces as a zero/absent manifest rather than only by diffing a download. Also
1086
+ // the natural seed for the streaming PR's open/close bracket. Stamped at resolve, so it
1087
+ // closes the block on the sorted timeline. (GENC-1461.)
1088
+ recordMetaEvent(this.sessionKey, 'external-diagnostics.folded', {
1089
+ interactionId,
1090
+ batch,
1091
+ counts: { turn: turnCount, event: externalEvents.length, message: messageCount },
1092
+ });
1093
+ }
991
1094
  const idx = this.history.findIndex((m) => { var _a; return ((_a = m.interaction) === null || _a === void 0 ? void 0 : _a.interactionId) === interactionId; });
992
1095
  if (idx !== -1) {
993
1096
  // Fold any widget-reported external (non-LLM) cost onto the message, so
@@ -1069,6 +1069,171 @@ interactionPresentation('presentation is absent when the option is omitted', ()
1069
1069
  }));
1070
1070
  interactionPresentation.run();
1071
1071
  // ---------------------------------------------------------------------------
1072
+ // external diagnostics harvest (GENC-1461) — a widget may return an out-of-band
1073
+ // engine's collated debug log on `InteractionResult.diagnostics` (e.g. a
1074
+ // server-side ChatDriver's). Those entries carry the SOURCE engine's own 0-based
1075
+ // index space, which would collide with this host's forward-capture cursors and
1076
+ // be dropped: the persister dedups `turn` entries by `turnIndex` and `event`
1077
+ // entries by an `index` high-water mark. So the driver decomposes the harvest by
1078
+ // kind — events fold into the session's meta-event registry (re-indexed onto the
1079
+ // host's monotonic counter), turns are re-keyed into a per-batch namespace, and
1080
+ // messages pass through unchanged (the persister already keys those by content).
1081
+ // ---------------------------------------------------------------------------
1082
+ const externalDiagnostics = createLogicSuite('ChatDriver external diagnostics harvest');
1083
+ /** A `diagnostics` payload as a headless engine would collate it — one of each kind,
1084
+ * all carrying that engine's own 0-based indices (`turnIndex: '0'`, event `index: 0`). */
1085
+ const harvestPayload = (marker) => [
1086
+ { kind: 'message', timestamp: '2026-07-23T10:00:00.000Z', role: 'assistant', content: marker },
1087
+ { kind: 'turn', timestamp: '2026-07-23T10:00:01.000Z', turnIndex: '0', systemPrompt: 'sys' },
1088
+ {
1089
+ kind: 'event',
1090
+ index: 0,
1091
+ timestamp: '2026-07-23T10:00:02.000Z',
1092
+ type: 'turn.error',
1093
+ importance: 'high',
1094
+ detail: { marker },
1095
+ },
1096
+ ];
1097
+ externalDiagnostics('routes harvested events to the meta-event registry (re-indexed) and keeps turns + messages', () => __awaiter(void 0, void 0, void 0, function* () {
1098
+ var _a, _b;
1099
+ clearMetaEventRegistry();
1100
+ const sessionKey = 'external-diag-harvest';
1101
+ const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]), sessionKey);
1102
+ // Requesting the interaction records a host `interaction.requested` meta-event, so the
1103
+ // registry's index counter is already advanced when the harvested event is merged — exactly
1104
+ // the condition that made the foreign 0-based `index` lose the persister's high-water race.
1105
+ const pending = driver.requestInteraction('w', {});
1106
+ const { interactionId } = driver.getHistory().at(-1).interaction;
1107
+ const requestedIndex = getMetaEvents(sessionKey).find((e) => e.type === 'interaction.requested').index;
1108
+ driver.resolveInteraction(interactionId, {
1109
+ status: 'approved',
1110
+ diagnostics: harvestPayload('m1'),
1111
+ });
1112
+ yield pending;
1113
+ // The event left the external buffer and landed in the registry, re-indexed ABOVE the host's
1114
+ // prior event (so it beats the persister's `index <= lastEventIndex` high-water), timestamp kept.
1115
+ const external = driver.getExternalDiagnostics();
1116
+ assert.is(external.filter((e) => e.kind === 'event').length, 0, 'events do not stay in the buffer');
1117
+ const merged = getMetaEvents(sessionKey).find((e) => { var _a; return ((_a = e.detail) === null || _a === void 0 ? void 0 : _a.marker) === 'm1'; });
1118
+ assert.ok(merged, 'the harvested event is folded into the session registry');
1119
+ assert.ok(merged.index > requestedIndex, 're-indexed onto the host counter, above prior events');
1120
+ assert.is(merged.timestamp, '2026-07-23T10:00:02.000Z', 'original timestamp preserved for ordering');
1121
+ // The turn is kept in the buffer but re-keyed out of the host's bare-integer turn-key space,
1122
+ // and the message passes through untouched.
1123
+ const turn = external.find((e) => e.kind === 'turn');
1124
+ assert.ok(turn, 'the harvested turn is kept in the external buffer');
1125
+ assert.is(turn.turnIndex, 'server-generation.1:0', 'turnIndex namespaced per batch');
1126
+ const message = external.find((e) => e.kind === 'message');
1127
+ assert.ok(message, 'the harvested message is kept in the external buffer');
1128
+ assert.is(message.content, 'm1', 'message passes through unchanged');
1129
+ // A single manifest event records the fold's provenance — the fragile boundary this feature
1130
+ // guards is now observable: a dropped generation shows up as a zero/absent manifest.
1131
+ const manifest = getMetaEvents(sessionKey).find((e) => e.type === 'external-diagnostics.folded');
1132
+ assert.ok(manifest, 'a manifest meta-event is recorded for the fold');
1133
+ assert.is((_a = manifest.detail) === null || _a === void 0 ? void 0 : _a.interactionId, interactionId);
1134
+ assert.equal((_b = manifest.detail) === null || _b === void 0 ? void 0 : _b.counts, { turn: 1, event: 1, message: 1 });
1135
+ }));
1136
+ externalDiagnostics('namespaces each batch so distinct server generations cannot collide on turnIndex', () => __awaiter(void 0, void 0, void 0, function* () {
1137
+ clearMetaEventRegistry();
1138
+ const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]), 'external-diag-batches');
1139
+ const first = driver.requestInteraction('w', {});
1140
+ const firstId = driver.getHistory().at(-1).interaction.interactionId;
1141
+ driver.resolveInteraction(firstId, { status: 'approved', diagnostics: harvestPayload('a') });
1142
+ yield first;
1143
+ const second = driver.requestInteraction('w', {});
1144
+ const secondId = driver.getHistory().at(-1).interaction.interactionId;
1145
+ driver.resolveInteraction(secondId, { status: 'approved', diagnostics: harvestPayload('b') });
1146
+ yield second;
1147
+ // Both harvests carried `turnIndex: '0'`; the per-batch namespace keeps them distinct, so the
1148
+ // persister's `emittedTurnKeys` can't fold the second generation's turn into the first.
1149
+ const turnKeys = driver
1150
+ .getExternalDiagnostics()
1151
+ .filter((e) => e.kind === 'turn')
1152
+ .map((e) => e.turnIndex);
1153
+ assert.equal(turnKeys, ['server-generation.1:0', 'server-generation.2:0']);
1154
+ }));
1155
+ externalDiagnostics('keys turns by local position so several server runs in one harvest do not collide', () => __awaiter(void 0, void 0, void 0, function* () {
1156
+ clearMetaEventRegistry();
1157
+ const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]), 'external-diag-multirun');
1158
+ // One interaction carrying two independent server runs (e.g. a consolidator's consolidator-code
1159
+ // run and its table-code run) — each numbers its own turns from 0, so the harvest has two
1160
+ // `turnIndex: '0'` entries. Keying by source index would collapse them; local position keeps
1161
+ // both, so the persister's per-turnIndex dedup drops neither.
1162
+ const pending = driver.requestInteraction('w', {});
1163
+ const { interactionId } = driver.getHistory().at(-1).interaction;
1164
+ driver.resolveInteraction(interactionId, {
1165
+ status: 'approved',
1166
+ diagnostics: [
1167
+ {
1168
+ kind: 'turn',
1169
+ timestamp: '2026-07-23T10:00:00.000Z',
1170
+ turnIndex: '0',
1171
+ systemPrompt: 'run1',
1172
+ },
1173
+ {
1174
+ kind: 'turn',
1175
+ timestamp: '2026-07-23T10:00:01.000Z',
1176
+ turnIndex: '1',
1177
+ systemPrompt: 'run1',
1178
+ },
1179
+ {
1180
+ kind: 'turn',
1181
+ timestamp: '2026-07-23T10:00:02.000Z',
1182
+ turnIndex: '0',
1183
+ systemPrompt: 'run2',
1184
+ },
1185
+ ],
1186
+ });
1187
+ yield pending;
1188
+ const turnKeys = driver
1189
+ .getExternalDiagnostics()
1190
+ .filter((e) => e.kind === 'turn')
1191
+ .map((e) => e.turnIndex);
1192
+ assert.equal(turnKeys, [
1193
+ 'server-generation.1:0',
1194
+ 'server-generation.1:1',
1195
+ 'server-generation.1:2',
1196
+ ]);
1197
+ // All distinct — no key repeats despite the repeated source `turnIndex: '0'`.
1198
+ assert.is(new Set(turnKeys).size, 3, 'every harvested turn gets a distinct key');
1199
+ }));
1200
+ externalDiagnostics('tolerates malformed entries and does not fold or miscount non-standard kinds', () => __awaiter(void 0, void 0, void 0, function* () {
1201
+ var _a;
1202
+ clearMetaEventRegistry();
1203
+ const sessionKey = 'external-diag-malformed';
1204
+ const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]), sessionKey);
1205
+ const pending = driver.requestInteraction('w', {});
1206
+ const { interactionId } = driver.getHistory().at(-1).interaction;
1207
+ // The harvest is untrusted `unknown[]`: primitives/null and an object without a string `kind`
1208
+ // must be skipped (not throw), and a `meta-snapshot` (host-owned, never emitted by a sibling)
1209
+ // must be dropped rather than folded or miscounted as a message.
1210
+ driver.resolveInteraction(interactionId, {
1211
+ status: 'approved',
1212
+ diagnostics: [
1213
+ null,
1214
+ 'oops',
1215
+ 42,
1216
+ { timestamp: '2026-07-23T10:00:00.000Z' }, // no kind
1217
+ {
1218
+ kind: 'message',
1219
+ timestamp: '2026-07-23T10:00:01.000Z',
1220
+ role: 'assistant',
1221
+ content: 'real',
1222
+ },
1223
+ { kind: 'meta-snapshot', timestamp: '2026-07-23T10:00:02.000Z', meta: { host: 'foreign' } },
1224
+ ],
1225
+ });
1226
+ yield pending;
1227
+ // Only the one real message survives the buffer; the meta-snapshot is not folded.
1228
+ const external = driver.getExternalDiagnostics();
1229
+ assert.equal(external.map((e) => e.kind), ['message']);
1230
+ assert.is(external[0].content, 'real');
1231
+ // Manifest counts only the three real kinds — the meta-snapshot is not miscounted as a message.
1232
+ const manifest = getMetaEvents(sessionKey).find((e) => e.type === 'external-diagnostics.folded');
1233
+ assert.equal((_a = manifest.detail) === null || _a === void 0 ? void 0 : _a.counts, { turn: 0, event: 0, message: 1 });
1234
+ }));
1235
+ externalDiagnostics.run();
1236
+ // ---------------------------------------------------------------------------
1072
1237
  // interaction context lifecycle (GENC-1390) — the driver creates a live
1073
1238
  // InteractionContext on requestInteraction and disposes+drops it on resolve, so
1074
1239
  // widget state survives element remounts but never outlives the interaction.
@@ -188,6 +188,10 @@ export class OrchestratingDriver extends EventTarget {
188
188
  getTurnSnapshots() {
189
189
  return this.chatDriver.getTurnSnapshots();
190
190
  }
191
+ /** Delegates to the inner {@link ChatDriver} — interactions resolve there, so it holds the buffer. */
192
+ getExternalDiagnostics() {
193
+ return this.chatDriver.getExternalDiagnostics();
194
+ }
191
195
  getSuggestions(history, prompt, count, allAgentInfo) {
192
196
  return __awaiter(this, void 0, void 0, function* () {
193
197
  // When pinned to a specialist, scope suggestions to that agent so the
@@ -42,6 +42,7 @@ import { AiWavesIndicator } from '../components/waves-indicator';
42
42
  import { AssistantAppSettingsProvider, } from '../provider/assistant-app-settings';
43
43
  import { recordMetaEvent, getMetaEvents, clearSession as clearMetaSession, DEBUG_LOG_README, } from '../state/debug-event-log';
44
44
  import { getOrCreateDriver, getDriver, getDriverAgentsKey, deleteDriver, } from '../state/driver-registry';
45
+ import { buildTimelineEntries } from '../state/persistence/build-timeline-entries';
45
46
  import { assembleDebugLog } from '../state/persistence/diagnostics';
46
47
  import { deleteDiagnosticsCursorsFor, resetDiagnosticsCursorsFor, } from '../state/persistence/diagnostics-cursors';
47
48
  import { getOrCreatePersister, getPersister, deletePersister, } from '../state/persistence/persister-registry';
@@ -55,7 +56,6 @@ import { resolveExclusiveLoadingStyle } from '../utils/animation-exclusivity';
55
56
  import { collectSessionModels } from '../utils/collect-session-models';
56
57
  import { appendCostSessionRecord, clearCostSessionHistory, loadCostSessionHistory, removeCostSessionRecord, } from '../utils/cost-session-history';
57
58
  import { deriveCostSessionTitleFromMessages, resolveCostSessionTitle, } from '../utils/derive-cost-session-title';
58
- import { flattenSubAgentMessages } from '../utils/flatten-sub-agent-messages';
59
59
  import { logger } from '../utils/logger';
60
60
  import { filterVisibleMessages, trailingInteractionRow } from '../utils/message-partition';
61
61
  import { resolveCostHistoryConfig, } from '../utils/resolve-cost-history-config';
@@ -2399,7 +2399,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2399
2399
  * been appended to the persisted stream.
2400
2400
  */
2401
2401
  buildDiagnosticEntries() {
2402
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
2402
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
2403
2403
  const timestamp = new Date().toISOString().replace(/:/g, '-');
2404
2404
  // Snapshot the live active agent from the DRIVER — the instance whose
2405
2405
  // `onActivate` ran, so its `getDebugSnapshot` closure holds state.
@@ -2417,32 +2417,14 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2417
2417
  ? Math.round((this.contextTokens / this.contextLimit) * 100)
2418
2418
  : undefined;
2419
2419
  const stateKey = this.getStateKey();
2420
- // Collapse repeated system prompts across consecutive turns. A stateful
2421
- // agent's prompt changes between turns, but a stable agent repeats the same
2422
- // (often multi-KB) prompt every turn so replace a turn's `systemPrompt`
2423
- // with a sentinel when it's byte-identical to the previous turn's. The
2424
- // prompt is still shown in full whenever it changes, so prompt evolution
2425
- // stays visible.
2426
- let lastFullPrompt;
2427
- let lastFullIndex = '';
2428
- const turns = ((_f = (_e = (_d = this.driver) === null || _d === void 0 ? void 0 : _d.getTurnSnapshots) === null || _e === void 0 ? void 0 : _e.call(_d)) !== null && _f !== void 0 ? _f : []).map((t) => {
2429
- let { systemPrompt } = t;
2430
- if (systemPrompt != null && systemPrompt === lastFullPrompt) {
2431
- systemPrompt = `<repeated — identical to turn ${lastFullIndex}>`;
2432
- }
2433
- else if (systemPrompt != null) {
2434
- lastFullPrompt = systemPrompt;
2435
- lastFullIndex = t.turnIndex;
2436
- }
2437
- return Object.assign(Object.assign({ kind: 'turn' }, t), { systemPrompt });
2420
+ // The message/turn/event timeline entries built by the shared, pure `buildTimelineEntries`
2421
+ // (the same helper a headless consumer uses to harvest its own log), from the driver's pull
2422
+ // surfaces. Prefer the driver's raw history (carries sub-agent traces) over the redux projection.
2423
+ const timelineEntries = buildTimelineEntries({
2424
+ turnSnapshots: (_f = (_e = (_d = this.driver) === null || _d === void 0 ? void 0 : _d.getTurnSnapshots) === null || _e === void 0 ? void 0 : _e.call(_d)) !== null && _f !== void 0 ? _f : [],
2425
+ messages: (_j = (_h = (_g = this.driver) === null || _g === void 0 ? void 0 : _g.getRawHistory) === null || _h === void 0 ? void 0 : _h.call(_g)) !== null && _j !== void 0 ? _j : this.messages,
2426
+ metaEvents: stateKey ? getMetaEvents(stateKey) : [],
2438
2427
  });
2439
- // Sub-agent conversations are stored nested on the parent tool call's
2440
- // `subAgentTrace`; `flattenSubAgentMessages` hoists them to top-level
2441
- // `kind: 'message'` entries (breadcrumbed + correlated, the nested copy moved
2442
- // out — not duplicated) so the timeline reads as one chronological sequence.
2443
- const messages = (_j = (_h = (_g = this.driver) === null || _g === void 0 ? void 0 : _g.getRawHistory) === null || _h === void 0 ? void 0 : _h.call(_g)) !== null && _j !== void 0 ? _j : this.messages;
2444
- const messageEntries = flattenSubAgentMessages(messages);
2445
- const eventEntries = (stateKey ? getMetaEvents(stateKey) : []).map((e) => (Object.assign({ kind: 'event' }, e)));
2446
2428
  // The export-time `meta` block, carried on a `meta-snapshot` entry so it lives
2447
2429
  // in the same forward stream (the latest one wins on reassembly, and the
2448
2430
  // accumulated history exposes config/state evolution across the lifetime).
@@ -2509,7 +2491,15 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2509
2491
  activePrimerHistory: m.activePrimerHistory,
2510
2492
  activeFoldStack: m.activeFoldStack,
2511
2493
  });
2512
- return [...messageEntries, ...turns, ...eventEntries, metaSnapshot];
2494
+ // Fold in any external diagnostics harvested from an out-of-band driver (e.g. a server-side
2495
+ // ChatDriver whose collated log an interaction widget returned on its result). They ride the
2496
+ // same download + persisted-diagnostics path; `assembleDebugLog` sorts the whole timeline by
2497
+ // timestamp so they interleave chronologically. (GENC-1461 unified diagnostics.)
2498
+ return [
2499
+ ...timelineEntries,
2500
+ ...((_s = (_r = (_q = this.driver) === null || _q === void 0 ? void 0 : _q.getExternalDiagnostics) === null || _r === void 0 ? void 0 : _r.call(_q)) !== null && _s !== void 0 ? _s : []),
2501
+ metaSnapshot,
2502
+ ];
2513
2503
  }
2514
2504
  downloadDebugLog() {
2515
2505
  return __awaiter(this, void 0, void 0, function* () {
@@ -60,6 +60,7 @@ export const META_EVENT_IMPORTANCE = {
60
60
  'provider.selected': 'normal',
61
61
  'interaction.requested': 'normal',
62
62
  'interaction.resolved': 'normal',
63
+ 'external-diagnostics.folded': 'normal',
63
64
  'session.cleared': 'normal',
64
65
  'session.switched': 'low',
65
66
  'session.switch-deferred': 'low',
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared, pure builder for the debug-log timeline entries (`message` / `turn` / `event`) — the
3
+ * common core of the host element's `buildDiagnosticEntries` and any HEADLESS consumer that drives
4
+ * a `ChatDriver` of its own (e.g. `ai-service` running the engine server-side) and needs to harvest
5
+ * that engine's debug log for the client to fold into its own download / persisted stream
6
+ * (GENC-1461 unified diagnostics). DOM-clean and dependency-light so it works in bare Node.
7
+ *
8
+ * It deliberately excludes the `meta-snapshot` entry: that block is host/DOM-specific (it reads
9
+ * `window.location.host`, the element's agent list, live context/cost), and a merged log keeps only
10
+ * the newest snapshot anyway — a headless source's agent/prompt/tools are already captured in its
11
+ * `turn` entries.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ import { flattenSubAgentMessages } from '../../utils/flatten-sub-agent-messages';
16
+ /**
17
+ * Build the `message` / `turn` / `event` timeline entries from a driver's pull surfaces
18
+ * (`getRawHistory()` / `getTurnSnapshots()` / `getMetaEvents(sessionKey)`). Order within the array
19
+ * is message → turn → event; the caller (`assembleDebugLog`) sorts the whole timeline by timestamp,
20
+ * so this ordering only sets the co-timestamp tie-break. Pure.
21
+ */
22
+ export function buildTimelineEntries(input) {
23
+ const { turnSnapshots, messages, metaEvents } = input;
24
+ // Collapse a turn's systemPrompt when byte-identical to the previous turn's — a stable agent
25
+ // repeats the same (often multi-KB) prompt every turn, while a stateful agent's changes. The
26
+ // prompt is shown in full whenever it changes, so prompt evolution stays visible.
27
+ let lastFullPrompt;
28
+ let lastFullIndex = '';
29
+ const turns = turnSnapshots.map((t) => {
30
+ let { systemPrompt } = t;
31
+ if (systemPrompt != null && systemPrompt === lastFullPrompt) {
32
+ systemPrompt = `<repeated — identical to turn ${lastFullIndex}>`;
33
+ }
34
+ else if (systemPrompt != null) {
35
+ lastFullPrompt = systemPrompt;
36
+ lastFullIndex = t.turnIndex;
37
+ }
38
+ return Object.assign(Object.assign({ kind: 'turn' }, t), { systemPrompt });
39
+ });
40
+ // Sub-agent conversations are stored nested on the parent tool call's `subAgentTrace`;
41
+ // `flattenSubAgentMessages` hoists them to top-level `kind: 'message'` entries (breadcrumbed +
42
+ // correlated, the nested copy moved out — not duplicated) so the timeline reads chronologically.
43
+ const messageEntries = flattenSubAgentMessages(messages);
44
+ const eventEntries = metaEvents.map((e) => (Object.assign({ kind: 'event' }, e)));
45
+ return [...messageEntries, ...turns, ...eventEntries];
46
+ }
@@ -1 +1 @@
1
- {"root":["../src/chat-driver-node.ts","../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
1
+ {"root":["../src/chat-driver-node.ts","../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/build-timeline-entries.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/ai-assistant",
3
3
  "description": "Genesis AI Assistant micro-frontend",
4
- "version": "14.498.0",
4
+ "version": "14.499.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -73,26 +73,26 @@
73
73
  }
74
74
  },
75
75
  "devDependencies": {
76
- "@genesislcap/foundation-testing": "14.498.0",
77
- "@genesislcap/genx": "14.498.0",
78
- "@genesislcap/rollup-builder": "14.498.0",
79
- "@genesislcap/ts-builder": "14.498.0",
80
- "@genesislcap/uvu-playwright-builder": "14.498.0",
81
- "@genesislcap/vite-builder": "14.498.0",
82
- "@genesislcap/webpack-builder": "14.498.0",
76
+ "@genesislcap/foundation-testing": "14.499.0",
77
+ "@genesislcap/genx": "14.499.0",
78
+ "@genesislcap/rollup-builder": "14.499.0",
79
+ "@genesislcap/ts-builder": "14.499.0",
80
+ "@genesislcap/uvu-playwright-builder": "14.499.0",
81
+ "@genesislcap/vite-builder": "14.499.0",
82
+ "@genesislcap/webpack-builder": "14.499.0",
83
83
  "@types/dompurify": "^3.0.5",
84
84
  "@types/marked": "^5.0.2",
85
85
  "esbuild": "0.25.12"
86
86
  },
87
87
  "dependencies": {
88
- "@genesislcap/foundation-ai": "14.498.0",
89
- "@genesislcap/foundation-logger": "14.498.0",
90
- "@genesislcap/foundation-notifications": "14.498.0",
91
- "@genesislcap/foundation-redux": "14.498.0",
92
- "@genesislcap/foundation-ui": "14.498.0",
93
- "@genesislcap/foundation-utils": "14.498.0",
94
- "@genesislcap/rapid-design-system": "14.498.0",
95
- "@genesislcap/web-core": "14.498.0",
88
+ "@genesislcap/foundation-ai": "14.499.0",
89
+ "@genesislcap/foundation-logger": "14.499.0",
90
+ "@genesislcap/foundation-notifications": "14.499.0",
91
+ "@genesislcap/foundation-redux": "14.499.0",
92
+ "@genesislcap/foundation-ui": "14.499.0",
93
+ "@genesislcap/foundation-utils": "14.499.0",
94
+ "@genesislcap/rapid-design-system": "14.499.0",
95
+ "@genesislcap/web-core": "14.499.0",
96
96
  "dompurify": "^3.3.1",
97
97
  "marked": "^17.0.3"
98
98
  },
@@ -105,5 +105,5 @@
105
105
  "access": "public"
106
106
  },
107
107
  "customElements": "dist/custom-elements.json",
108
- "gitHead": "88a07e46851fa94efc9399c50975c9eb2c21168c"
108
+ "gitHead": "49903bfdc57c5c5a7dd0d711d9717908df0b7ac7"
109
109
  }
@@ -37,6 +37,12 @@ export {
37
37
  GeminiProvider,
38
38
  GeminiTransport,
39
39
  MutableAIProviderRegistry,
40
+ // Supported-model allowlists — so a headless consumer validates a configured model id against
41
+ // foundation-ai's REAL list instead of maintaining a parallel hardcoded copy that silently drifts
42
+ // as the platform adds models (a valid new model would otherwise look like a misconfiguration).
43
+ // GENC-1461, create PR #1644 review.
44
+ SUPPORTED_ANTHROPIC_MODEL_IDS,
45
+ SUPPORTED_GEMINI_MODEL_IDS,
40
46
  isObservableAIProviderRegistry,
41
47
  } from '@genesislcap/foundation-ai';
42
48
  export type {
@@ -52,3 +58,17 @@ export type {
52
58
  ChatToolHandlers,
53
59
  GeminiModelId,
54
60
  } from '@genesislcap/foundation-ai';
61
+
62
+ // Debug-log harvesting (GENC-1461 unified diagnostics). A headless consumer collates its driver's
63
+ // log — `buildTimelineEntries({ turnSnapshots: driver.getTurnSnapshots(), messages:
64
+ // driver.getHistory(), metaEvents: getMetaEvents(sessionKey) })` → `DiagnosticEntry[]` — ships it to
65
+ // the client, which folds it into the assistant's debug log via `InteractionResult.diagnostics`
66
+ // (see `ChatDriver.getExternalDiagnostics`). `clearSession(sessionKey)` drops the per-request
67
+ // meta-event bucket so the long-lived server process's module registry doesn't grow unbounded.
68
+ // All three modules are DOM-clean, so the node entry stays Node-loadable.
69
+ export { clearSession, getMetaEvents } from './state/debug-event-log';
70
+ export type { MetaEvent } from './state/debug-event-log';
71
+ export { buildTimelineEntries } from './state/persistence/build-timeline-entries';
72
+ export type { TurnSnapshotLike } from './state/persistence/build-timeline-entries';
73
+ export { assembleDebugLog } from './state/persistence/diagnostics';
74
+ export type { DebugLog, DiagnosticEntry } from './state/persistence/diagnostics';
@@ -5,6 +5,7 @@ import type {
5
5
  ChatToolDefinition,
6
6
  } from '@genesislcap/foundation-ai';
7
7
  import type { AgentConfig } from '../../config/config';
8
+ import type { DiagnosticEntry } from '../../state/persistence/diagnostics';
8
9
  import type { InteractionContext } from '../../types/interaction-context';
9
10
  import type { TurnSnapshot } from '../chat-driver/chat-driver';
10
11
 
@@ -121,6 +122,14 @@ export interface AiDriver extends EventTarget {
121
122
  */
122
123
  getTurnSnapshots?(): ReadonlyArray<TurnSnapshot>;
123
124
 
125
+ /**
126
+ * Diagnostic-log entries harvested from resolved interactions (e.g. a server-side ChatDriver's
127
+ * collated debug log, returned on `InteractionResult.diagnostics`). The host folds these into its
128
+ * debug log. Optional for the same leaf/orchestrator reason as `getTurnSnapshots`.
129
+ * (GENC-1461 unified diagnostics.)
130
+ */
131
+ getExternalDiagnostics?(): ReadonlyArray<DiagnosticEntry>;
132
+
124
133
  /**
125
134
  * Name of the AI provider used on the most recent turn. Falls back to the
126
135
  * registry's default name when no turn has run yet.