@genesislcap/ai-assistant 14.461.0 → 14.461.2-GENC-1346.1

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.
@@ -5394,7 +5394,7 @@
5394
5394
  },
5395
5395
  {
5396
5396
  "kind": "Content",
5397
- "text": ";\n kind: \"message\";\n } | {\n index: number;\n timestamp: string;\n type: "
5397
+ "text": ";\n model?: string;\n providerName?: string;\n kind: \"message\";\n } | {\n index: number;\n timestamp: string;\n type: "
5398
5398
  },
5399
5399
  {
5400
5400
  "kind": "Reference",
@@ -803,6 +803,27 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
803
803
  private lastResolvedProviderName?;
804
804
  /** Last dispatched `provider-changed` name; avoids duplicate events on stable turns. */
805
805
  private lastDispatchedProviderName?;
806
+ /**
807
+ * Concrete model id of the provider resolved for the current turn, read from
808
+ * its `getStatus()` and stamped onto the resulting assistant message so the
809
+ * debug log carries per-message model lineage. Re-resolved each turn in
810
+ * `resolveProviderForTurn`.
811
+ */
812
+ private lastResolvedModel?;
813
+ /**
814
+ * Memoised `name → model id` lookups, so the per-turn model resolution doesn't
815
+ * re-await `getStatus()` every turn. Cleared (with `resolvedProviderCache`) on
816
+ * an observable-registry change, so a model swapped behind a stable name is
817
+ * picked up on the next turn.
818
+ */
819
+ private resolvedModelCache;
820
+ /**
821
+ * Unsubscribe handle for the provider-registry change listener (only set when
822
+ * the injected registry is observable). Called in `dispose` so the long-lived
823
+ * registry doesn't retain this driver — see the constructor and the sub-agent
824
+ * teardown in `runSubAgent`.
825
+ */
826
+ private unsubscribeRegistry?;
806
827
  constructor(providerRegistry: AIProviderRegistry, toolHandlers?: ToolHandlersInput, toolDefinitions?: ToolDefinitionsInput, systemPrompt?: SystemPromptInput, primerHistory?: ChatMessage[], maxToolIterations?: number, maxFoldOperations?: number, maxTurnSnapshots?: number,
807
828
  /** Session identity used to file meta events onto the shared debug-log timeline. */
808
829
  sessionKey?: string);
@@ -866,6 +887,13 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
866
887
  * differs from the last dispatched value.
867
888
  */
868
889
  private resolveProviderForTurn;
890
+ /**
891
+ * Resolve the concrete model id for a provider via its optional `getStatus()`,
892
+ * memoised by registry name. Used to stamp `model` onto outgoing messages.
893
+ * Best-effort: a provider without `getStatus`, a null status, or a throw all
894
+ * yield `undefined` — model attribution is diagnostic, never load-bearing.
895
+ */
896
+ private resolveModelForProvider;
869
897
  /**
870
898
  * Resolve a per-turn config input that is either a static value or a function
871
899
  * of the turn context — the value-or-resolver shape shared by `provider`,
@@ -1402,6 +1430,8 @@ export declare class FoundationAiAssistant extends GenesisElement {
1402
1430
  private driverCleanup?;
1403
1431
  private loadingTimer;
1404
1432
  private unsubBus?;
1433
+ /** Unsubscribe handle for the provider-registry change listener (observable registries only). */
1434
+ private unsubProviderRegistry?;
1405
1435
  private haloStartPublished;
1406
1436
  /** Fingerprint of the agents array used to build the current driver. Used by agentsChanged to skip spurious rebuilds. */
1407
1437
  private _driverAgentsKey?;
@@ -1629,6 +1659,8 @@ export declare class FoundationAiAssistant extends GenesisElement {
1629
1659
  cost?: number;
1630
1660
  externalCostUsd?: number;
1631
1661
  responseMeta?: ChatResponseMeta;
1662
+ model?: string;
1663
+ providerName?: string;
1632
1664
  kind: "message";
1633
1665
  } | {
1634
1666
  index: number;
@@ -247,6 +247,27 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
247
247
  private lastResolvedProviderName?;
248
248
  /** Last dispatched `provider-changed` name; avoids duplicate events on stable turns. */
249
249
  private lastDispatchedProviderName?;
250
+ /**
251
+ * Concrete model id of the provider resolved for the current turn, read from
252
+ * its `getStatus()` and stamped onto the resulting assistant message so the
253
+ * debug log carries per-message model lineage. Re-resolved each turn in
254
+ * `resolveProviderForTurn`.
255
+ */
256
+ private lastResolvedModel?;
257
+ /**
258
+ * Memoised `name → model id` lookups, so the per-turn model resolution doesn't
259
+ * re-await `getStatus()` every turn. Cleared (with `resolvedProviderCache`) on
260
+ * an observable-registry change, so a model swapped behind a stable name is
261
+ * picked up on the next turn.
262
+ */
263
+ private resolvedModelCache;
264
+ /**
265
+ * Unsubscribe handle for the provider-registry change listener (only set when
266
+ * the injected registry is observable). Called in `dispose` so the long-lived
267
+ * registry doesn't retain this driver — see the constructor and the sub-agent
268
+ * teardown in `runSubAgent`.
269
+ */
270
+ private unsubscribeRegistry?;
250
271
  constructor(providerRegistry: AIProviderRegistry, toolHandlers?: ToolHandlersInput, toolDefinitions?: ToolDefinitionsInput, systemPrompt?: SystemPromptInput, primerHistory?: ChatMessage[], maxToolIterations?: number, maxFoldOperations?: number, maxTurnSnapshots?: number,
251
272
  /** Session identity used to file meta events onto the shared debug-log timeline. */
252
273
  sessionKey?: string);
@@ -310,6 +331,13 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
310
331
  * differs from the last dispatched value.
311
332
  */
312
333
  private resolveProviderForTurn;
334
+ /**
335
+ * Resolve the concrete model id for a provider via its optional `getStatus()`,
336
+ * memoised by registry name. Used to stamp `model` onto outgoing messages.
337
+ * Best-effort: a provider without `getStatus`, a null status, or a throw all
338
+ * yield `undefined` — model attribution is diagnostic, never load-bearing.
339
+ */
340
+ private resolveModelForProvider;
313
341
  /**
314
342
  * Resolve a per-turn config input that is either a static value or a function
315
343
  * of the turn context — the value-or-resolver shape shared by `provider`,
@@ -1 +1 @@
1
- {"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"flowing-waves-indicator.d.ts","sourceRoot":"","sources":["../../../src/components/flowing-waves-indicator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,cAAc,EAAQ,MAAM,uBAAuB,CAAC;AA6GjF;;;;;;;;;;;;;;;GAeG;AACH,qBAwDa,uBAAwB,SAAQ,cAAc;IAKzD,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAmB;IAErC,iBAAiB;IAQjB,oBAAoB;IAQpB,OAAO,CAAC,IAAI;IAmBZ;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,SAAS;CA6BzB"}
1
+ {"version":3,"file":"flowing-waves-indicator.d.ts","sourceRoot":"","sources":["../../../src/components/flowing-waves-indicator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,cAAc,EAAQ,MAAM,uBAAuB,CAAC;AAiHjF;;;;;;;;;;;;;;;GAeG;AACH,qBA0Da,uBAAwB,SAAQ,cAAc;IAKzD,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAmB;IAErC,iBAAiB;IAQjB,oBAAoB;IAQpB,OAAO,CAAC,IAAI;IAmBZ;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,SAAS;CA6BzB"}
@@ -210,6 +210,8 @@ export declare class FoundationAiAssistant extends GenesisElement {
210
210
  private driverCleanup?;
211
211
  private loadingTimer;
212
212
  private unsubBus?;
213
+ /** Unsubscribe handle for the provider-registry change listener (observable registries only). */
214
+ private unsubProviderRegistry?;
213
215
  private haloStartPublished;
214
216
  /** Fingerprint of the agents array used to build the current driver. Used by agentsChanged to skip spurious rebuilds. */
215
217
  private _driverAgentsKey?;
@@ -437,6 +439,8 @@ export declare class FoundationAiAssistant extends GenesisElement {
437
439
  cost?: number;
438
440
  externalCostUsd?: number;
439
441
  responseMeta?: import("@genesislcap/foundation-ai").ChatResponseMeta;
442
+ model?: string;
443
+ providerName?: string;
440
444
  kind: "message";
441
445
  } | {
442
446
  index: number;
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/main/main.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.styles.d.ts","sourceRoot":"","sources":["../../../src/main/main.styles.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,MAAM,iDA83BlB,CAAC"}
1
+ {"version":3,"file":"main.styles.d.ts","sourceRoot":"","sources":["../../../src/main/main.styles.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,MAAM,iDAq4BlB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { __awaiter } from "tslib";
2
- import { MalformedFunctionCallError } from '@genesislcap/foundation-ai';
2
+ import { isObservableAIProviderRegistry, MalformedFunctionCallError, } from '@genesislcap/foundation-ai';
3
3
  import { agenticActivityBus } from '../../channel/ai-activity-bus';
4
4
  import { resolveChatProvider } from '../../config/validate-providers';
5
5
  import { recordMetaEvent, recordTurnError, recordTurnRetry } from '../../state/debug-event-log';
@@ -138,6 +138,13 @@ export class ChatDriver extends EventTarget {
138
138
  * validated fresh.
139
139
  */
140
140
  this.resolvedProviderCache = new Map();
141
+ /**
142
+ * Memoised `name → model id` lookups, so the per-turn model resolution doesn't
143
+ * re-await `getStatus()` every turn. Cleared (with `resolvedProviderCache`) on
144
+ * an observable-registry change, so a model swapped behind a stable name is
145
+ * picked up on the next turn.
146
+ */
147
+ this.resolvedModelCache = new Map();
141
148
  if (typeof toolHandlers === 'function') {
142
149
  this.toolHandlersFactory = toolHandlers;
143
150
  this.toolHandlers = {};
@@ -158,6 +165,24 @@ export class ChatDriver extends EventTarget {
158
165
  this.primerHistory = primerHistory;
159
166
  this.maxFoldOperations = maxFoldOperations;
160
167
  this.maxTurnSnapshots = maxTurnSnapshots;
168
+ // Runtime provider switching: when the host registered an observable
169
+ // registry, drop our memoised provider lookups whenever its mapping/default
170
+ // changes so the next turn re-resolves against the new providers. Resetting
171
+ // `lastDispatchedProviderName` forces the next `resolveProviderForTurn` to
172
+ // re-emit `provider-changed` even when the resolved *name* is unchanged
173
+ // (e.g. a tier name like 'high' kept, but the vendor underneath swapped) —
174
+ // that's what lets status UI refresh on a same-name switch. Feature-detect
175
+ // means immutable/empty registries are a no-op. Child sub-agent drivers get
176
+ // the same registry and so subscribe here too; each cleans up in `dispose`.
177
+ if (isObservableAIProviderRegistry(this.providerRegistry)) {
178
+ this.unsubscribeRegistry = this.providerRegistry.subscribe(() => {
179
+ this.resolvedProviderCache.clear();
180
+ this.resolvedModelCache.clear();
181
+ this.lastResolvedProviderName = undefined;
182
+ this.lastResolvedModel = undefined;
183
+ this.lastDispatchedProviderName = undefined;
184
+ });
185
+ }
161
186
  }
162
187
  /**
163
188
  * Tear down the driver: aborts the lifecycle signal so any in-flight provider
@@ -175,7 +200,13 @@ export class ChatDriver extends EventTarget {
175
200
  * timeout), which also stamps history and reverts the chat-input override.
176
201
  */
177
202
  dispose() {
203
+ var _a;
178
204
  this.lifecycleController.abort(new DOMException('AI assistant driver disposed', 'AbortError'));
205
+ // Detach from the provider registry so the long-lived registry doesn't pin
206
+ // this driver (and its closure) after teardown. Guard-cleared so a second
207
+ // dispose is a no-op, matching this method's idempotent contract.
208
+ (_a = this.unsubscribeRegistry) === null || _a === void 0 ? void 0 : _a.call(this);
209
+ this.unsubscribeRegistry = undefined;
179
210
  }
180
211
  /**
181
212
  * Stop the current turn (user "stop" button). Aborts the in-flight provider
@@ -328,6 +359,7 @@ export class ChatDriver extends EventTarget {
328
359
  resolvedName = name;
329
360
  }
330
361
  this.lastResolvedProviderName = resolvedName;
362
+ this.lastResolvedModel = yield this.resolveModelForProvider(resolvedName, provider);
331
363
  if (resolvedName !== this.lastDispatchedProviderName) {
332
364
  this.lastDispatchedProviderName = resolvedName;
333
365
  recordMetaEvent(this.sessionKey, 'provider.selected', {
@@ -339,6 +371,28 @@ export class ChatDriver extends EventTarget {
339
371
  return provider;
340
372
  });
341
373
  }
374
+ /**
375
+ * Resolve the concrete model id for a provider via its optional `getStatus()`,
376
+ * memoised by registry name. Used to stamp `model` onto outgoing messages.
377
+ * Best-effort: a provider without `getStatus`, a null status, or a throw all
378
+ * yield `undefined` — model attribution is diagnostic, never load-bearing.
379
+ */
380
+ resolveModelForProvider(name, provider) {
381
+ return __awaiter(this, void 0, void 0, function* () {
382
+ var _a, _b;
383
+ if (this.resolvedModelCache.has(name))
384
+ return this.resolvedModelCache.get(name);
385
+ let model;
386
+ try {
387
+ model = (_b = (yield ((_a = provider.getStatus) === null || _a === void 0 ? void 0 : _a.call(provider)))) === null || _b === void 0 ? void 0 : _b.model;
388
+ }
389
+ catch (_c) {
390
+ model = undefined;
391
+ }
392
+ this.resolvedModelCache.set(name, model);
393
+ return model;
394
+ });
395
+ }
342
396
  /**
343
397
  * Resolve a per-turn config input that is either a static value or a function
344
398
  * of the turn context — the value-or-resolver shape shared by `provider`,
@@ -957,6 +1011,13 @@ export class ChatDriver extends EventTarget {
957
1011
  this.lifecycleController.signal.removeEventListener('abort', disposeChild);
958
1012
  child.removeEventListener('history-updated', forwardTrace);
959
1013
  child.removeEventListener('provider-changed', forwardProviderChanged);
1014
+ // Tear the child down on every exit path, not just timeout/parent-abort.
1015
+ // A child that completes normally is otherwise never disposed, so its
1016
+ // provider-registry subscription (wired in the ChatDriver constructor)
1017
+ // would leak — the long-lived registry would retain every completed
1018
+ // sub-agent driver. dispose() is idempotent and only aborts the (already
1019
+ // settled) lifecycle, so the snapshot/completion reads below still work.
1020
+ child.dispose();
960
1021
  this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: lifecycleDetail }));
961
1022
  }
962
1023
  const trace = child.getHistory();
@@ -1408,6 +1469,22 @@ export class ChatDriver extends EventTarget {
1408
1469
  }
1409
1470
  throw e;
1410
1471
  }
1472
+ // Attribute the response to the concrete model + registry slot that
1473
+ // produced it (resolved for this turn in `resolveProviderForTurn`). Carried
1474
+ // on the assistant message so the debug-log timeline shows per-message
1475
+ // model lineage — and thus per-tool-call, since tool calls ride on the
1476
+ // assistant message that requests them. Harmless on a response later
1477
+ // discarded as empty/retried; only kept copies reach history.
1478
+ //
1479
+ // Attach each key only when resolved: a provider with no `getStatus` (etc.)
1480
+ // leaves the key off entirely rather than carrying it as `undefined`.
1481
+ // JSON.stringify already drops undefined from the exported log, so this is
1482
+ // chiefly about keeping the in-memory message shape honest.
1483
+ if (this.lastResolvedModel !== undefined)
1484
+ response.model = this.lastResolvedModel;
1485
+ if (this.lastResolvedProviderName !== undefined) {
1486
+ response.providerName = this.lastResolvedProviderName;
1487
+ }
1411
1488
  const isThinkingStep = response.content && ((_c = response.toolCalls) === null || _c === void 0 ? void 0 : _c.length);
1412
1489
  const isEmptyResponse = !((_d = response.content) === null || _d === void 0 ? void 0 : _d.trim()) && !((_e = response.toolCalls) === null || _e === void 0 ? void 0 : _e.length);
1413
1490
  if (isEmptyResponse) {
@@ -863,3 +863,180 @@ interactionCost('leaves externalCostUsd unset for a missing, zero, or negative c
863
863
  assert.not.ok(driver.getHistory().some((m) => m.externalCostUsd != null));
864
864
  }));
865
865
  interactionCost.run();
866
+ /**
867
+ * A single-name (`'high'`) observable registry. `get`/`default` always return
868
+ * the current provider, so swapping it mid-session models a same-name vendor
869
+ * switch (the tier name stays `'high'`, the provider underneath changes).
870
+ */
871
+ const makeObservableRegistry = (initial) => {
872
+ let current = initial;
873
+ const listeners = new Set();
874
+ return {
875
+ get: () => current,
876
+ default: () => current,
877
+ defaultName: () => 'high',
878
+ names: () => ['high'],
879
+ getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
880
+ listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
881
+ subscribe(listener) {
882
+ listeners.add(listener);
883
+ return () => {
884
+ listeners.delete(listener);
885
+ };
886
+ },
887
+ swap(provider) {
888
+ current = provider;
889
+ for (const l of Array.from(listeners))
890
+ l();
891
+ },
892
+ listenerCount: () => listeners.size,
893
+ };
894
+ };
895
+ const makeDriverWithRegistry = (config, registry) => {
896
+ const driver = new ChatDriver(registry, {}, [], undefined, undefined, 50, 5, undefined, '');
897
+ driver.applyAgent(config);
898
+ return driver;
899
+ };
900
+ const observable = createLogicSuite('ChatDriver observable provider registry');
901
+ observable.after(() => {
902
+ agenticActivityBus.close();
903
+ });
904
+ observable('a registry change clears the resolved-provider cache so the next turn uses the new provider', () => __awaiter(void 0, void 0, void 0, function* () {
905
+ const providerA = scriptedProvider([{ role: 'assistant', content: 'A' }]);
906
+ const providerB = scriptedProvider([{ role: 'assistant', content: 'B' }]);
907
+ const registry = makeObservableRegistry(providerA);
908
+ // A static provider name means lookups go through `resolvedProviderCache` —
909
+ // the cache that must self-invalidate on a registry change.
910
+ const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
911
+ yield driver.sendMessage('first');
912
+ assert.is(providerA.advertisedPerCall.length, 1, 'turn 1 resolves the original provider');
913
+ assert.is(providerB.advertisedPerCall.length, 0);
914
+ registry.swap(providerB); // notify → cache cleared
915
+ yield driver.sendMessage('second');
916
+ assert.is(providerB.advertisedPerCall.length, 1, 'turn 2 resolves the swapped-in provider');
917
+ assert.is(providerA.advertisedPerCall.length, 1, 'the stale provider is not reused');
918
+ driver.dispose();
919
+ }));
920
+ observable('re-emits provider-changed on a same-name swap', () => __awaiter(void 0, void 0, void 0, function* () {
921
+ const providerA = scriptedProvider([{ role: 'assistant', content: 'A' }]);
922
+ const providerB = scriptedProvider([{ role: 'assistant', content: 'B' }]);
923
+ const registry = makeObservableRegistry(providerA);
924
+ const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
925
+ const names = [];
926
+ driver.addEventListener('provider-changed', (e) => {
927
+ names.push(e.detail.name);
928
+ });
929
+ yield driver.sendMessage('first');
930
+ registry.swap(providerB);
931
+ yield driver.sendMessage('second');
932
+ // The resolved name ('high') never changes, but the swap resets the
933
+ // last-dispatched name so the cog can refresh — two events, not one.
934
+ assert.equal(names, ['high', 'high']);
935
+ driver.dispose();
936
+ }));
937
+ observable('a non-observable registry is a no-op — turn runs, dispose does not throw', () => __awaiter(void 0, void 0, void 0, function* () {
938
+ const provider = scriptedProvider([{ role: 'assistant', content: 'hi' }]);
939
+ const driver = makeDriverWithRegistry(agent({ name: 'plain' }), makeRegistry(provider));
940
+ yield driver.sendMessage('go');
941
+ assert.is(provider.advertisedPerCall.length, 1);
942
+ driver.dispose(); // no subscription was wired — must still be safe
943
+ }));
944
+ observable('a child sub-agent driver unsubscribes on completion — no listener leak', () => __awaiter(void 0, void 0, void 0, function* () {
945
+ const provider = scriptedProvider([callsTool('delegate', 'd1'), callsTool('finish', 'f1')]);
946
+ const registry = makeObservableRegistry(provider);
947
+ const parent = delegatingParent(completingWorker({ ok: true }), () => { });
948
+ const driver = makeDriverWithRegistry(parent, registry);
949
+ assert.is(registry.listenerCount(), 1, 'the parent driver subscribed on construction');
950
+ yield driver.sendMessage('go');
951
+ // The child subscribed during the run; if it didn't clean up on its (normal)
952
+ // completion the registry would now hold two listeners.
953
+ assert.is(registry.listenerCount(), 1, 'the completed child unsubscribed');
954
+ driver.dispose();
955
+ assert.is(registry.listenerCount(), 0, 'the parent unsubscribed on dispose');
956
+ }));
957
+ observable.run();
958
+ // ---------------------------------------------------------------------------
959
+ // per-message model attribution (GENC-1346)
960
+ //
961
+ // Each model-produced assistant message carries `model` (the concrete model id
962
+ // the active provider's getStatus reports) and `providerName` (the registry slot
963
+ // it resolved under), so the exported debug log shows which model produced each
964
+ // message — and, since tool calls ride on the assistant message, each tool call.
965
+ // ---------------------------------------------------------------------------
966
+ /** A provider that replays scripted replies and reports `model` via getStatus
967
+ * (omitted entirely when `model` is undefined, to model a provider with no
968
+ * status). */
969
+ const modelProvider = (model, responses) => {
970
+ const queue = [...responses];
971
+ const provider = {
972
+ chat: () => __awaiter(void 0, void 0, void 0, function* () { var _a; return (_a = queue.shift()) !== null && _a !== void 0 ? _a : { role: 'assistant', content: 'done' }; }),
973
+ };
974
+ if (model !== undefined) {
975
+ provider.getStatus = () => __awaiter(void 0, void 0, void 0, function* () { return ({ provider: 'gemini', model }); });
976
+ }
977
+ return provider;
978
+ };
979
+ const modelAttr = createLogicSuite('ChatDriver per-message model attribution');
980
+ modelAttr.after(() => {
981
+ agenticActivityBus.close();
982
+ });
983
+ modelAttr('stamps the resolved model and registry name onto an assistant reply', () => __awaiter(void 0, void 0, void 0, function* () {
984
+ const provider = modelProvider('gemini-2.5-flash-lite', [
985
+ { role: 'assistant', content: 'hi there' },
986
+ ]);
987
+ const driver = makeDriver(agent({ name: 'plain' }), provider);
988
+ yield driver.sendMessage('hello');
989
+ const reply = driver.getHistory().find((m) => m.role === 'assistant');
990
+ assert.ok(reply, 'assistant reply present');
991
+ assert.is(reply.model, 'gemini-2.5-flash-lite', 'model id read from provider getStatus');
992
+ // makeRegistry registers a single provider under the name 'test'.
993
+ assert.is(reply.providerName, 'test', 'registry slot the turn resolved under');
994
+ driver.dispose();
995
+ }));
996
+ modelAttr('attributes a tool-calling assistant message (and so its tool calls) to the model', () => __awaiter(void 0, void 0, void 0, function* () {
997
+ const provider = modelProvider('claude-haiku-4-5-20251001', [
998
+ callsTool('noop', 't1'),
999
+ { role: 'assistant', content: 'finished' },
1000
+ ]);
1001
+ const config = agent({
1002
+ name: 'withTool',
1003
+ toolDefinitions: [def('noop')],
1004
+ toolHandlers: { noop: () => __awaiter(void 0, void 0, void 0, function* () { return 'ok'; }) },
1005
+ });
1006
+ const driver = makeDriver(config, provider);
1007
+ yield driver.sendMessage('go');
1008
+ const toolCallMsg = driver.getHistory().find((m) => { var _a, _b; return ((_b = (_a = m.toolCalls) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0; });
1009
+ assert.ok(toolCallMsg, 'an assistant message with tool calls is present');
1010
+ assert.is(toolCallMsg.model, 'claude-haiku-4-5-20251001');
1011
+ assert.is(toolCallMsg.providerName, 'test');
1012
+ driver.dispose();
1013
+ }));
1014
+ modelAttr('picks up a model swapped behind a stable name', () => __awaiter(void 0, void 0, void 0, function* () {
1015
+ var _a, _b, _c;
1016
+ const before = modelProvider('gemini-2.5-flash-lite', [{ role: 'assistant', content: 'A' }]);
1017
+ const after = modelProvider('gemini-2.5-pro', [{ role: 'assistant', content: 'B' }]);
1018
+ const registry = makeObservableRegistry(before);
1019
+ const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
1020
+ yield driver.sendMessage('first');
1021
+ registry.swap(after); // notify → model cache cleared, next turn re-resolves
1022
+ yield driver.sendMessage('second');
1023
+ const replies = driver.getHistory().filter((m) => m.role === 'assistant');
1024
+ assert.is((_a = replies.at(-2)) === null || _a === void 0 ? void 0 : _a.model, 'gemini-2.5-flash-lite', 'first turn keeps the original model');
1025
+ assert.is((_b = replies.at(-1)) === null || _b === void 0 ? void 0 : _b.model, 'gemini-2.5-pro', 'after the swap the new model is stamped');
1026
+ // The tier name ('high') never changed across the swap — only the model behind it.
1027
+ assert.is((_c = replies.at(-1)) === null || _c === void 0 ? void 0 : _c.providerName, 'high');
1028
+ driver.dispose();
1029
+ }));
1030
+ modelAttr('omits the model key entirely when the provider reports no status', () => __awaiter(void 0, void 0, void 0, function* () {
1031
+ const provider = modelProvider(undefined, [{ role: 'assistant', content: 'no status here' }]);
1032
+ const driver = makeDriver(agent({ name: 'plain' }), provider);
1033
+ yield driver.sendMessage('hi');
1034
+ const reply = driver.getHistory().find((m) => m.role === 'assistant');
1035
+ assert.ok(reply, 'assistant reply present');
1036
+ // No getStatus → no model. The key is left off, not set to undefined, so the
1037
+ // exported log carries no dead `model` line for this message.
1038
+ assert.not.ok('model' in reply, 'model key is absent, not present-as-undefined');
1039
+ assert.is(reply.providerName, 'test', 'provider name is still recorded');
1040
+ driver.dispose();
1041
+ }));
1042
+ modelAttr.run();
@@ -2,9 +2,15 @@ var AiFlowingWavesIndicator_1;
2
2
  import { __decorate } from "tslib";
3
3
  import { css, customElement, GenesisElement, html } from '@genesislcap/web-core';
4
4
  import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
5
- /** SVG coordinate space the bands are drawn in (aspect matches the host box). */
5
+ /**
6
+ * SVG coordinate space the bands are drawn in. The bands stretch to fill the
7
+ * host box (the band SVGs use `preserveAspectRatio="none"`), so this aspect need
8
+ * not match the host's. `VIEW_H` is kept just tall enough to bound the crests,
9
+ * leaving minimal dead space above/below the line — otherwise the waves float in
10
+ * the vertical middle of the host and read as sitting too low beside the avatar.
11
+ */
6
12
  const VIEW_W = 225;
7
- const VIEW_H = 90;
13
+ const VIEW_H = 50;
8
14
  const CENTRE_Y = VIEW_H / 2;
9
15
  /** Number of samples traced across each band. */
10
16
  const POINTS = 90;
@@ -64,9 +70,7 @@ const BANDS = [
64
70
  // `mix-blend-mode: screen`, which composites reliably between HTML-level layers
65
71
  // but NOT between sibling SVG <path>/<g> elements in many browsers — hence one
66
72
  // svg per band rather than four paths in a single svg.
67
- const bandLayersMarkup = BANDS.map((_b, i) => `<svg class="layer band-layer" viewBox="0 0 ${VIEW_W} ${VIEW_H}">` +
68
- `<path class="band b${i + 1}" data-band="${i}" />` +
69
- `</svg>`).join('');
73
+ const bandLayersMarkup = BANDS.map((_b, i) => `<svg class="layer band-layer" viewBox="0 0 ${VIEW_W} ${VIEW_H}" preserveAspectRatio="none"><path class="band b${i + 1}" data-band="${i}" /></svg>`).join('');
70
74
  /**
71
75
  * Animated "flowing waves" loading indicator — coloured organic waves (in the
72
76
  * four brand colours) whose bumps swell, shrink, merge and split across a line,
@@ -171,7 +175,9 @@ AiFlowingWavesIndicator = AiFlowingWavesIndicator_1 = __decorate([
171
175
  :host {
172
176
  display: inline-block;
173
177
  width: 180px;
174
- height: 72px;
178
+ /* Height matches the message avatar (32px) so the wave line sits level
179
+ with it. The bands stretch to fill via preserveAspectRatio="none". */
180
+ height: 32px;
175
181
  }
176
182
 
177
183
  /* Transparent — sits directly on the chat surface (no backdrop). 'isolation:
@@ -22,7 +22,7 @@
22
22
  // =============================================================================
23
23
  var FoundationAiAssistant_1;
24
24
  import { __awaiter, __decorate, __rest } from "tslib";
25
- import { AIProviderRegistry } from '@genesislcap/foundation-ai';
25
+ import { AIProviderRegistry, isObservableAIProviderRegistry } from '@genesislcap/foundation-ai';
26
26
  import { avoidTreeShaking } from '@genesislcap/foundation-utils';
27
27
  import { customElement, html, GenesisElement, observable, volatile, attr, } from '@genesislcap/web-core';
28
28
  import DOMPurify from 'dompurify';
@@ -900,7 +900,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
900
900
  this.driverCleanup = undefined;
901
901
  }
902
902
  connectedCallback() {
903
- var _a, _b, _c, _d, _e, _f, _j, _k, _l;
903
+ var _a, _b, _c, _d, _e, _f, _j, _k, _l, _m;
904
904
  // Initialise the store reference BEFORE super.connectedCallback() so that
905
905
  // the first FAST render has access to the store. The store Proxy calls
906
906
  // Observable.track(observableStore, sliceName) whenever a slice is read,
@@ -969,6 +969,21 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
969
969
  this.fetchSuggestions();
970
970
  void this.resolveContextLimit();
971
971
  void this.loadProviderStatuses();
972
+ // When the host registered an observable registry (runtime provider
973
+ // switching), refresh the displayed model/limit and the provider list the
974
+ // instant its contents change — so the header reflects the new provider
975
+ // immediately on switch, not only when the next turn re-emits
976
+ // `provider-changed`. Feature-detected: a no-op for immutable registries.
977
+ // Re-subscribed per connect (docking/popout remounts); balanced in
978
+ // disconnectedCallback.
979
+ (_k = this.unsubProviderRegistry) === null || _k === void 0 ? void 0 : _k.call(this);
980
+ this.unsubProviderRegistry = undefined;
981
+ if (isObservableAIProviderRegistry(this.providerRegistry)) {
982
+ this.unsubProviderRegistry = this.providerRegistry.subscribe(() => {
983
+ void this.resolveContextLimit();
984
+ void this.loadProviderStatuses();
985
+ });
986
+ }
972
987
  if (this.messagesEl) {
973
988
  this._scrollListener = () => {
974
989
  this._userScrolledAway =
@@ -992,18 +1007,20 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
992
1007
  restoredMessages: this.messages.length,
993
1008
  driver: driverExisted ? 'reused' : 'created',
994
1009
  driverKind: this.driver instanceof OrchestratingDriver ? 'orchestrating' : 'chat',
995
- driverBusy: (_l = (_k = this.driver) === null || _k === void 0 ? void 0 : _k.isBusy()) !== null && _l !== void 0 ? _l : false,
1010
+ driverBusy: (_m = (_l = this.driver) === null || _l === void 0 ? void 0 : _l.isBusy()) !== null && _m !== void 0 ? _m : false,
996
1011
  });
997
1012
  }
998
1013
  disconnectedCallback() {
999
- var _a, _b, _c, _d;
1014
+ var _a, _b, _c, _d, _e;
1000
1015
  super.disconnectedCallback();
1001
1016
  this.stopLoadingTimer();
1002
1017
  this.state = 'idle';
1003
1018
  this.unwireDriver();
1004
1019
  (_a = this.unsubBus) === null || _a === void 0 ? void 0 : _a.call(this);
1005
1020
  this.unsubBus = undefined;
1006
- (_b = this._executionCompletionUnsub) === null || _b === void 0 ? void 0 : _b.call(this);
1021
+ (_b = this.unsubProviderRegistry) === null || _b === void 0 ? void 0 : _b.call(this);
1022
+ this.unsubProviderRegistry = undefined;
1023
+ (_c = this._executionCompletionUnsub) === null || _c === void 0 ? void 0 : _c.call(this);
1007
1024
  this._executionCompletionUnsub = undefined;
1008
1025
  if (this.messagesEl && this._scrollListener) {
1009
1026
  this.messagesEl.removeEventListener('scroll', this._scrollListener);
@@ -1019,7 +1036,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
1019
1036
  this._agentPickerToggle.finalize();
1020
1037
  this._settingsToggle.finalize();
1021
1038
  // Capture before clearing — `wasBusy` reads the driver, which is dropped below.
1022
- this.logMeta('assistant.disconnected', { wasBusy: (_d = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.isBusy()) !== null && _d !== void 0 ? _d : false });
1039
+ this.logMeta('assistant.disconnected', { wasBusy: (_e = (_d = this.driver) === null || _d === void 0 ? void 0 : _d.isBusy()) !== null && _e !== void 0 ? _e : false });
1023
1040
  // Clear local references only — driver and store stay in their registries.
1024
1041
  this.driver = undefined;
1025
1042
  this._sessionRef = undefined;
@@ -851,6 +851,13 @@ export const styles = css `
851
851
  padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
852
852
  }
853
853
 
854
+ /* The flowing-waves host is the avatar's height (32px), so drop the top padding
855
+ the shared rule adds — that keeps the wave line level with the avatar instead
856
+ of dropping it toward the row's vertical centre. */
857
+ .thinking-flowing-waves {
858
+ padding-top: 0;
859
+ }
860
+
854
861
  .thinking-caption {
855
862
  font-size: var(--type-ramp-minus-1-font-size, 12px);
856
863
  line-height: var(--type-ramp-minus-1-line-height, 16px);
@@ -138,7 +138,7 @@ export const DEBUG_LOG_README = [
138
138
  'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',
139
139
  '`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
140
140
  '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.',
141
- "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, and `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). 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.",
141
+ "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, and `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.",
142
142
  "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.",
143
143
  "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.",
144
144
  "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'.",
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.461.0",
4
+ "version": "14.461.2-GENC-1346.1",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -64,24 +64,24 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
- "@genesislcap/foundation-testing": "14.461.0",
68
- "@genesislcap/genx": "14.461.0",
69
- "@genesislcap/rollup-builder": "14.461.0",
70
- "@genesislcap/ts-builder": "14.461.0",
71
- "@genesislcap/uvu-playwright-builder": "14.461.0",
72
- "@genesislcap/vite-builder": "14.461.0",
73
- "@genesislcap/webpack-builder": "14.461.0",
67
+ "@genesislcap/foundation-testing": "14.461.2-GENC-1346.1",
68
+ "@genesislcap/genx": "14.461.2-GENC-1346.1",
69
+ "@genesislcap/rollup-builder": "14.461.2-GENC-1346.1",
70
+ "@genesislcap/ts-builder": "14.461.2-GENC-1346.1",
71
+ "@genesislcap/uvu-playwright-builder": "14.461.2-GENC-1346.1",
72
+ "@genesislcap/vite-builder": "14.461.2-GENC-1346.1",
73
+ "@genesislcap/webpack-builder": "14.461.2-GENC-1346.1",
74
74
  "@types/dompurify": "^3.0.5",
75
75
  "@types/marked": "^5.0.2"
76
76
  },
77
77
  "dependencies": {
78
- "@genesislcap/foundation-ai": "14.461.0",
79
- "@genesislcap/foundation-logger": "14.461.0",
80
- "@genesislcap/foundation-redux": "14.461.0",
81
- "@genesislcap/foundation-ui": "14.461.0",
82
- "@genesislcap/foundation-utils": "14.461.0",
83
- "@genesislcap/rapid-design-system": "14.461.0",
84
- "@genesislcap/web-core": "14.461.0",
78
+ "@genesislcap/foundation-ai": "14.461.2-GENC-1346.1",
79
+ "@genesislcap/foundation-logger": "14.461.2-GENC-1346.1",
80
+ "@genesislcap/foundation-redux": "14.461.2-GENC-1346.1",
81
+ "@genesislcap/foundation-ui": "14.461.2-GENC-1346.1",
82
+ "@genesislcap/foundation-utils": "14.461.2-GENC-1346.1",
83
+ "@genesislcap/rapid-design-system": "14.461.2-GENC-1346.1",
84
+ "@genesislcap/web-core": "14.461.2-GENC-1346.1",
85
85
  "dompurify": "^3.3.1",
86
86
  "marked": "^17.0.3"
87
87
  },
@@ -93,5 +93,5 @@
93
93
  "publishConfig": {
94
94
  "access": "public"
95
95
  },
96
- "gitHead": "d87c27bf9e91f6f5d1cf51992e7892df9434c9be"
96
+ "gitHead": "0729266a735c8d8d74192119552f0774c6038436"
97
97
  }
@@ -1187,3 +1187,243 @@ interactionCost(
1187
1187
  );
1188
1188
 
1189
1189
  interactionCost.run();
1190
+
1191
+ // ---------------------------------------------------------------------------
1192
+ // observable provider registry — runtime provider switching (GENC-1346)
1193
+ //
1194
+ // When the host registers an observable registry and swaps providers at
1195
+ // runtime, the driver must drop its memoised lookups so the next turn resolves
1196
+ // the new provider, re-emit `provider-changed` even when the resolved *name* is
1197
+ // unchanged, and (critically) never leak its subscription — including for child
1198
+ // sub-agent drivers that complete normally.
1199
+ // ---------------------------------------------------------------------------
1200
+
1201
+ interface ObservableTestRegistry extends AIProviderRegistry {
1202
+ subscribe(listener: () => void): () => void;
1203
+ /** Swap the provider returned by `get`/`default` and notify subscribers. */
1204
+ swap(provider: AIProvider): void;
1205
+ /** Live subscriber count — lets tests assert there's no listener leak. */
1206
+ listenerCount(): number;
1207
+ }
1208
+
1209
+ /**
1210
+ * A single-name (`'high'`) observable registry. `get`/`default` always return
1211
+ * the current provider, so swapping it mid-session models a same-name vendor
1212
+ * switch (the tier name stays `'high'`, the provider underneath changes).
1213
+ */
1214
+ const makeObservableRegistry = (initial: AIProvider): ObservableTestRegistry => {
1215
+ let current = initial;
1216
+ const listeners = new Set<() => void>();
1217
+ return {
1218
+ get: () => current,
1219
+ default: () => current,
1220
+ defaultName: () => 'high',
1221
+ names: () => ['high'],
1222
+ getStatus: async () => null,
1223
+ listStatuses: async () => [],
1224
+ subscribe(listener: () => void) {
1225
+ listeners.add(listener);
1226
+ return () => {
1227
+ listeners.delete(listener);
1228
+ };
1229
+ },
1230
+ swap(provider: AIProvider) {
1231
+ current = provider;
1232
+ for (const l of Array.from(listeners)) l();
1233
+ },
1234
+ listenerCount: () => listeners.size,
1235
+ };
1236
+ };
1237
+
1238
+ const makeDriverWithRegistry = (config: AgentConfig, registry: AIProviderRegistry): ChatDriver => {
1239
+ const driver = new ChatDriver(registry, {}, [], undefined, undefined, 50, 5, undefined, '');
1240
+ driver.applyAgent(config);
1241
+ return driver;
1242
+ };
1243
+
1244
+ const observable = createLogicSuite('ChatDriver observable provider registry');
1245
+
1246
+ observable.after(() => {
1247
+ agenticActivityBus.close();
1248
+ });
1249
+
1250
+ observable(
1251
+ 'a registry change clears the resolved-provider cache so the next turn uses the new provider',
1252
+ async () => {
1253
+ const providerA = scriptedProvider([{ role: 'assistant', content: 'A' }]);
1254
+ const providerB = scriptedProvider([{ role: 'assistant', content: 'B' }]);
1255
+ const registry = makeObservableRegistry(providerA);
1256
+ // A static provider name means lookups go through `resolvedProviderCache` —
1257
+ // the cache that must self-invalidate on a registry change.
1258
+ const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
1259
+
1260
+ await driver.sendMessage('first');
1261
+ assert.is(providerA.advertisedPerCall.length, 1, 'turn 1 resolves the original provider');
1262
+ assert.is(providerB.advertisedPerCall.length, 0);
1263
+
1264
+ registry.swap(providerB); // notify → cache cleared
1265
+
1266
+ await driver.sendMessage('second');
1267
+ assert.is(providerB.advertisedPerCall.length, 1, 'turn 2 resolves the swapped-in provider');
1268
+ assert.is(providerA.advertisedPerCall.length, 1, 'the stale provider is not reused');
1269
+
1270
+ driver.dispose();
1271
+ },
1272
+ );
1273
+
1274
+ observable('re-emits provider-changed on a same-name swap', async () => {
1275
+ const providerA = scriptedProvider([{ role: 'assistant', content: 'A' }]);
1276
+ const providerB = scriptedProvider([{ role: 'assistant', content: 'B' }]);
1277
+ const registry = makeObservableRegistry(providerA);
1278
+ const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
1279
+
1280
+ const names: string[] = [];
1281
+ driver.addEventListener('provider-changed', (e) => {
1282
+ names.push((e as CustomEvent<{ name: string }>).detail.name);
1283
+ });
1284
+
1285
+ await driver.sendMessage('first');
1286
+ registry.swap(providerB);
1287
+ await driver.sendMessage('second');
1288
+
1289
+ // The resolved name ('high') never changes, but the swap resets the
1290
+ // last-dispatched name so the cog can refresh — two events, not one.
1291
+ assert.equal(names, ['high', 'high']);
1292
+
1293
+ driver.dispose();
1294
+ });
1295
+
1296
+ observable('a non-observable registry is a no-op — turn runs, dispose does not throw', async () => {
1297
+ const provider = scriptedProvider([{ role: 'assistant', content: 'hi' }]);
1298
+ const driver = makeDriverWithRegistry(agent({ name: 'plain' }), makeRegistry(provider));
1299
+ await driver.sendMessage('go');
1300
+ assert.is(provider.advertisedPerCall.length, 1);
1301
+ driver.dispose(); // no subscription was wired — must still be safe
1302
+ });
1303
+
1304
+ observable('a child sub-agent driver unsubscribes on completion — no listener leak', async () => {
1305
+ const provider = scriptedProvider([callsTool('delegate', 'd1'), callsTool('finish', 'f1')]);
1306
+ const registry = makeObservableRegistry(provider);
1307
+ const parent = delegatingParent(completingWorker({ ok: true }), () => {});
1308
+ const driver = makeDriverWithRegistry(parent, registry);
1309
+
1310
+ assert.is(registry.listenerCount(), 1, 'the parent driver subscribed on construction');
1311
+
1312
+ await driver.sendMessage('go');
1313
+ // The child subscribed during the run; if it didn't clean up on its (normal)
1314
+ // completion the registry would now hold two listeners.
1315
+ assert.is(registry.listenerCount(), 1, 'the completed child unsubscribed');
1316
+
1317
+ driver.dispose();
1318
+ assert.is(registry.listenerCount(), 0, 'the parent unsubscribed on dispose');
1319
+ });
1320
+
1321
+ observable.run();
1322
+
1323
+ // ---------------------------------------------------------------------------
1324
+ // per-message model attribution (GENC-1346)
1325
+ //
1326
+ // Each model-produced assistant message carries `model` (the concrete model id
1327
+ // the active provider's getStatus reports) and `providerName` (the registry slot
1328
+ // it resolved under), so the exported debug log shows which model produced each
1329
+ // message — and, since tool calls ride on the assistant message, each tool call.
1330
+ // ---------------------------------------------------------------------------
1331
+
1332
+ /** A provider that replays scripted replies and reports `model` via getStatus
1333
+ * (omitted entirely when `model` is undefined, to model a provider with no
1334
+ * status). */
1335
+ const modelProvider = (model: string | undefined, responses: ChatMessage[]): AIProvider => {
1336
+ const queue = [...responses];
1337
+ const provider: AIProvider = {
1338
+ chat: async (): Promise<ChatMessage> => queue.shift() ?? { role: 'assistant', content: 'done' },
1339
+ };
1340
+ if (model !== undefined) {
1341
+ provider.getStatus = async () => ({ provider: 'gemini', model });
1342
+ }
1343
+ return provider;
1344
+ };
1345
+
1346
+ const modelAttr = createLogicSuite('ChatDriver per-message model attribution');
1347
+
1348
+ modelAttr.after(() => {
1349
+ agenticActivityBus.close();
1350
+ });
1351
+
1352
+ modelAttr('stamps the resolved model and registry name onto an assistant reply', async () => {
1353
+ const provider = modelProvider('gemini-2.5-flash-lite', [
1354
+ { role: 'assistant', content: 'hi there' },
1355
+ ]);
1356
+ const driver = makeDriver(agent({ name: 'plain' }), provider);
1357
+
1358
+ await driver.sendMessage('hello');
1359
+
1360
+ const reply = driver.getHistory().find((m) => m.role === 'assistant');
1361
+ assert.ok(reply, 'assistant reply present');
1362
+ assert.is(reply!.model, 'gemini-2.5-flash-lite', 'model id read from provider getStatus');
1363
+ // makeRegistry registers a single provider under the name 'test'.
1364
+ assert.is(reply!.providerName, 'test', 'registry slot the turn resolved under');
1365
+
1366
+ driver.dispose();
1367
+ });
1368
+
1369
+ modelAttr(
1370
+ 'attributes a tool-calling assistant message (and so its tool calls) to the model',
1371
+ async () => {
1372
+ const provider = modelProvider('claude-haiku-4-5-20251001', [
1373
+ callsTool('noop', 't1'),
1374
+ { role: 'assistant', content: 'finished' },
1375
+ ]);
1376
+ const config = agent({
1377
+ name: 'withTool',
1378
+ toolDefinitions: [def('noop')],
1379
+ toolHandlers: { noop: async () => 'ok' },
1380
+ });
1381
+ const driver = makeDriver(config, provider);
1382
+
1383
+ await driver.sendMessage('go');
1384
+
1385
+ const toolCallMsg = driver.getHistory().find((m) => (m.toolCalls?.length ?? 0) > 0);
1386
+ assert.ok(toolCallMsg, 'an assistant message with tool calls is present');
1387
+ assert.is(toolCallMsg!.model, 'claude-haiku-4-5-20251001');
1388
+ assert.is(toolCallMsg!.providerName, 'test');
1389
+
1390
+ driver.dispose();
1391
+ },
1392
+ );
1393
+
1394
+ modelAttr('picks up a model swapped behind a stable name', async () => {
1395
+ const before = modelProvider('gemini-2.5-flash-lite', [{ role: 'assistant', content: 'A' }]);
1396
+ const after = modelProvider('gemini-2.5-pro', [{ role: 'assistant', content: 'B' }]);
1397
+ const registry = makeObservableRegistry(before);
1398
+ const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
1399
+
1400
+ await driver.sendMessage('first');
1401
+ registry.swap(after); // notify → model cache cleared, next turn re-resolves
1402
+ await driver.sendMessage('second');
1403
+
1404
+ const replies = driver.getHistory().filter((m) => m.role === 'assistant');
1405
+ assert.is(replies.at(-2)?.model, 'gemini-2.5-flash-lite', 'first turn keeps the original model');
1406
+ assert.is(replies.at(-1)?.model, 'gemini-2.5-pro', 'after the swap the new model is stamped');
1407
+ // The tier name ('high') never changed across the swap — only the model behind it.
1408
+ assert.is(replies.at(-1)?.providerName, 'high');
1409
+
1410
+ driver.dispose();
1411
+ });
1412
+
1413
+ modelAttr('omits the model key entirely when the provider reports no status', async () => {
1414
+ const provider = modelProvider(undefined, [{ role: 'assistant', content: 'no status here' }]);
1415
+ const driver = makeDriver(agent({ name: 'plain' }), provider);
1416
+
1417
+ await driver.sendMessage('hi');
1418
+
1419
+ const reply = driver.getHistory().find((m) => m.role === 'assistant');
1420
+ assert.ok(reply, 'assistant reply present');
1421
+ // No getStatus → no model. The key is left off, not set to undefined, so the
1422
+ // exported log carries no dead `model` line for this message.
1423
+ assert.not.ok('model' in reply!, 'model key is absent, not present-as-undefined');
1424
+ assert.is(reply!.providerName, 'test', 'provider name is still recorded');
1425
+
1426
+ driver.dispose();
1427
+ });
1428
+
1429
+ modelAttr.run();
@@ -14,7 +14,10 @@ import type {
14
14
  SubAgentFailureReason,
15
15
  SubAgentRequestOptions,
16
16
  } from '@genesislcap/foundation-ai';
17
- import { MalformedFunctionCallError } from '@genesislcap/foundation-ai';
17
+ import {
18
+ isObservableAIProviderRegistry,
19
+ MalformedFunctionCallError,
20
+ } from '@genesislcap/foundation-ai';
18
21
  import { agenticActivityBus } from '../../channel/ai-activity-bus';
19
22
  import type {
20
23
  AgentConfig,
@@ -339,6 +342,27 @@ export class ChatDriver extends EventTarget implements AiDriver {
339
342
  private lastResolvedProviderName?: string;
340
343
  /** Last dispatched `provider-changed` name; avoids duplicate events on stable turns. */
341
344
  private lastDispatchedProviderName?: string;
345
+ /**
346
+ * Concrete model id of the provider resolved for the current turn, read from
347
+ * its `getStatus()` and stamped onto the resulting assistant message so the
348
+ * debug log carries per-message model lineage. Re-resolved each turn in
349
+ * `resolveProviderForTurn`.
350
+ */
351
+ private lastResolvedModel?: string;
352
+ /**
353
+ * Memoised `name → model id` lookups, so the per-turn model resolution doesn't
354
+ * re-await `getStatus()` every turn. Cleared (with `resolvedProviderCache`) on
355
+ * an observable-registry change, so a model swapped behind a stable name is
356
+ * picked up on the next turn.
357
+ */
358
+ private resolvedModelCache = new Map<string, string | undefined>();
359
+ /**
360
+ * Unsubscribe handle for the provider-registry change listener (only set when
361
+ * the injected registry is observable). Called in `dispose` so the long-lived
362
+ * registry doesn't retain this driver — see the constructor and the sub-agent
363
+ * teardown in `runSubAgent`.
364
+ */
365
+ private unsubscribeRegistry?: () => void;
342
366
 
343
367
  constructor(
344
368
  private readonly providerRegistry: AIProviderRegistry,
@@ -371,6 +395,24 @@ export class ChatDriver extends EventTarget implements AiDriver {
371
395
  this.primerHistory = primerHistory;
372
396
  this.maxFoldOperations = maxFoldOperations;
373
397
  this.maxTurnSnapshots = maxTurnSnapshots;
398
+ // Runtime provider switching: when the host registered an observable
399
+ // registry, drop our memoised provider lookups whenever its mapping/default
400
+ // changes so the next turn re-resolves against the new providers. Resetting
401
+ // `lastDispatchedProviderName` forces the next `resolveProviderForTurn` to
402
+ // re-emit `provider-changed` even when the resolved *name* is unchanged
403
+ // (e.g. a tier name like 'high' kept, but the vendor underneath swapped) —
404
+ // that's what lets status UI refresh on a same-name switch. Feature-detect
405
+ // means immutable/empty registries are a no-op. Child sub-agent drivers get
406
+ // the same registry and so subscribe here too; each cleans up in `dispose`.
407
+ if (isObservableAIProviderRegistry(this.providerRegistry)) {
408
+ this.unsubscribeRegistry = this.providerRegistry.subscribe(() => {
409
+ this.resolvedProviderCache.clear();
410
+ this.resolvedModelCache.clear();
411
+ this.lastResolvedProviderName = undefined;
412
+ this.lastResolvedModel = undefined;
413
+ this.lastDispatchedProviderName = undefined;
414
+ });
415
+ }
374
416
  }
375
417
 
376
418
  /**
@@ -390,6 +432,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
390
432
  */
391
433
  dispose(): void {
392
434
  this.lifecycleController.abort(new DOMException('AI assistant driver disposed', 'AbortError'));
435
+ // Detach from the provider registry so the long-lived registry doesn't pin
436
+ // this driver (and its closure) after teardown. Guard-cleared so a second
437
+ // dispose is a no-op, matching this method's idempotent contract.
438
+ this.unsubscribeRegistry?.();
439
+ this.unsubscribeRegistry = undefined;
393
440
  }
394
441
 
395
442
  /**
@@ -540,6 +587,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
540
587
  resolvedName = name;
541
588
  }
542
589
  this.lastResolvedProviderName = resolvedName;
590
+ this.lastResolvedModel = await this.resolveModelForProvider(resolvedName, provider);
543
591
  if (resolvedName !== this.lastDispatchedProviderName) {
544
592
  this.lastDispatchedProviderName = resolvedName;
545
593
  recordMetaEvent(this.sessionKey, 'provider.selected', {
@@ -553,6 +601,27 @@ export class ChatDriver extends EventTarget implements AiDriver {
553
601
  return provider;
554
602
  }
555
603
 
604
+ /**
605
+ * Resolve the concrete model id for a provider via its optional `getStatus()`,
606
+ * memoised by registry name. Used to stamp `model` onto outgoing messages.
607
+ * Best-effort: a provider without `getStatus`, a null status, or a throw all
608
+ * yield `undefined` — model attribution is diagnostic, never load-bearing.
609
+ */
610
+ private async resolveModelForProvider(
611
+ name: string,
612
+ provider: AIProvider,
613
+ ): Promise<string | undefined> {
614
+ if (this.resolvedModelCache.has(name)) return this.resolvedModelCache.get(name);
615
+ let model: string | undefined;
616
+ try {
617
+ model = (await provider.getStatus?.())?.model;
618
+ } catch {
619
+ model = undefined;
620
+ }
621
+ this.resolvedModelCache.set(name, model);
622
+ return model;
623
+ }
624
+
556
625
  /**
557
626
  * Resolve a per-turn config input that is either a static value or a function
558
627
  * of the turn context — the value-or-resolver shape shared by `provider`,
@@ -1262,6 +1331,13 @@ export class ChatDriver extends EventTarget implements AiDriver {
1262
1331
  this.lifecycleController.signal.removeEventListener('abort', disposeChild);
1263
1332
  child.removeEventListener('history-updated', forwardTrace);
1264
1333
  child.removeEventListener('provider-changed', forwardProviderChanged);
1334
+ // Tear the child down on every exit path, not just timeout/parent-abort.
1335
+ // A child that completes normally is otherwise never disposed, so its
1336
+ // provider-registry subscription (wired in the ChatDriver constructor)
1337
+ // would leak — the long-lived registry would retain every completed
1338
+ // sub-agent driver. dispose() is idempotent and only aborts the (already
1339
+ // settled) lifecycle, so the snapshot/completion reads below still work.
1340
+ child.dispose();
1265
1341
  this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: lifecycleDetail }));
1266
1342
  }
1267
1343
 
@@ -1767,6 +1843,22 @@ export class ChatDriver extends EventTarget implements AiDriver {
1767
1843
  throw e;
1768
1844
  }
1769
1845
 
1846
+ // Attribute the response to the concrete model + registry slot that
1847
+ // produced it (resolved for this turn in `resolveProviderForTurn`). Carried
1848
+ // on the assistant message so the debug-log timeline shows per-message
1849
+ // model lineage — and thus per-tool-call, since tool calls ride on the
1850
+ // assistant message that requests them. Harmless on a response later
1851
+ // discarded as empty/retried; only kept copies reach history.
1852
+ //
1853
+ // Attach each key only when resolved: a provider with no `getStatus` (etc.)
1854
+ // leaves the key off entirely rather than carrying it as `undefined`.
1855
+ // JSON.stringify already drops undefined from the exported log, so this is
1856
+ // chiefly about keeping the in-memory message shape honest.
1857
+ if (this.lastResolvedModel !== undefined) response.model = this.lastResolvedModel;
1858
+ if (this.lastResolvedProviderName !== undefined) {
1859
+ response.providerName = this.lastResolvedProviderName;
1860
+ }
1861
+
1770
1862
  const isThinkingStep = response.content && response.toolCalls?.length;
1771
1863
  const isEmptyResponse = !response.content?.trim() && !response.toolCalls?.length;
1772
1864
 
@@ -6,9 +6,15 @@ import {
6
6
  AI_COLOUR_VIOLET,
7
7
  } from '../styles/ai-colours';
8
8
 
9
- /** SVG coordinate space the bands are drawn in (aspect matches the host box). */
9
+ /**
10
+ * SVG coordinate space the bands are drawn in. The bands stretch to fill the
11
+ * host box (the band SVGs use `preserveAspectRatio="none"`), so this aspect need
12
+ * not match the host's. `VIEW_H` is kept just tall enough to bound the crests,
13
+ * leaving minimal dead space above/below the line — otherwise the waves float in
14
+ * the vertical middle of the host and read as sitting too low beside the avatar.
15
+ */
10
16
  const VIEW_W = 225;
11
- const VIEW_H = 90;
17
+ const VIEW_H = 50;
12
18
  const CENTRE_Y = VIEW_H / 2;
13
19
  /** Number of samples traced across each band. */
14
20
  const POINTS = 90;
@@ -102,9 +108,7 @@ const BANDS: BandConfig[] = [
102
108
  // svg per band rather than four paths in a single svg.
103
109
  const bandLayersMarkup = BANDS.map(
104
110
  (_b, i) =>
105
- `<svg class="layer band-layer" viewBox="0 0 ${VIEW_W} ${VIEW_H}">` +
106
- `<path class="band b${i + 1}" data-band="${i}" />` +
107
- `</svg>`,
111
+ `<svg class="layer band-layer" viewBox="0 0 ${VIEW_W} ${VIEW_H}" preserveAspectRatio="none"><path class="band b${i + 1}" data-band="${i}" /></svg>`,
108
112
  ).join('');
109
113
 
110
114
  /**
@@ -132,7 +136,9 @@ const bandLayersMarkup = BANDS.map(
132
136
  :host {
133
137
  display: inline-block;
134
138
  width: 180px;
135
- height: 72px;
139
+ /* Height matches the message avatar (32px) so the wave line sits level
140
+ with it. The bands stretch to fill via preserveAspectRatio="none". */
141
+ height: 32px;
136
142
  }
137
143
 
138
144
  /* Transparent — sits directly on the chat surface (no backdrop). 'isolation:
@@ -857,6 +857,13 @@ export const styles = css`
857
857
  padding: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 3px);
858
858
  }
859
859
 
860
+ /* The flowing-waves host is the avatar's height (32px), so drop the top padding
861
+ the shared rule adds — that keeps the wave line level with the avatar instead
862
+ of dropping it toward the row's vertical centre. */
863
+ .thinking-flowing-waves {
864
+ padding-top: 0;
865
+ }
866
+
860
867
  .thinking-caption {
861
868
  font-size: var(--type-ramp-minus-1-font-size, 12px);
862
869
  line-height: var(--type-ramp-minus-1-line-height, 16px);
package/src/main/main.ts CHANGED
@@ -28,7 +28,7 @@ import type {
28
28
  ChatInputDuringExecutionMode,
29
29
  ChatMessage,
30
30
  } from '@genesislcap/foundation-ai';
31
- import { AIProviderRegistry } from '@genesislcap/foundation-ai';
31
+ import { AIProviderRegistry, isObservableAIProviderRegistry } from '@genesislcap/foundation-ai';
32
32
  import { avoidTreeShaking } from '@genesislcap/foundation-utils';
33
33
  import {
34
34
  customElement,
@@ -592,6 +592,8 @@ export class FoundationAiAssistant extends GenesisElement {
592
592
  private driverCleanup?: () => void;
593
593
  private loadingTimer: ReturnType<typeof setTimeout> | undefined;
594
594
  private unsubBus?: () => void;
595
+ /** Unsubscribe handle for the provider-registry change listener (observable registries only). */
596
+ private unsubProviderRegistry?: () => void;
595
597
  private haloStartPublished = false;
596
598
  /** Fingerprint of the agents array used to build the current driver. Used by agentsChanged to skip spurious rebuilds. */
597
599
  private _driverAgentsKey?: string;
@@ -1117,6 +1119,21 @@ export class FoundationAiAssistant extends GenesisElement {
1117
1119
  this.fetchSuggestions();
1118
1120
  void this.resolveContextLimit();
1119
1121
  void this.loadProviderStatuses();
1122
+ // When the host registered an observable registry (runtime provider
1123
+ // switching), refresh the displayed model/limit and the provider list the
1124
+ // instant its contents change — so the header reflects the new provider
1125
+ // immediately on switch, not only when the next turn re-emits
1126
+ // `provider-changed`. Feature-detected: a no-op for immutable registries.
1127
+ // Re-subscribed per connect (docking/popout remounts); balanced in
1128
+ // disconnectedCallback.
1129
+ this.unsubProviderRegistry?.();
1130
+ this.unsubProviderRegistry = undefined;
1131
+ if (isObservableAIProviderRegistry(this.providerRegistry)) {
1132
+ this.unsubProviderRegistry = this.providerRegistry.subscribe(() => {
1133
+ void this.resolveContextLimit();
1134
+ void this.loadProviderStatuses();
1135
+ });
1136
+ }
1120
1137
  if (this.messagesEl) {
1121
1138
  this._scrollListener = () => {
1122
1139
  this._userScrolledAway =
@@ -1150,6 +1167,8 @@ export class FoundationAiAssistant extends GenesisElement {
1150
1167
  this.unwireDriver();
1151
1168
  this.unsubBus?.();
1152
1169
  this.unsubBus = undefined;
1170
+ this.unsubProviderRegistry?.();
1171
+ this.unsubProviderRegistry = undefined;
1153
1172
  this._executionCompletionUnsub?.();
1154
1173
  this._executionCompletionUnsub = undefined;
1155
1174
  if (this.messagesEl && this._scrollListener) {
@@ -247,7 +247,7 @@ export const DEBUG_LOG_README: readonly string[] = [
247
247
  'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',
248
248
  '`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
249
249
  '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.',
250
- "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, and `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). 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.",
250
+ "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, and `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.",
251
251
  "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.",
252
252
  "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.",
253
253
  "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'.",