@threadplane/chat 0.0.56 → 0.0.57

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, ViewEncapsulation, ElementRef, ViewContainerRef, DestroyRef, viewChild, model, contentChild, untracked, makeEnvironmentProviders, Injector, runInInjectionContext, SecurityContext } from '@angular/core';
2
+ import { InjectionToken, input, inject, TemplateRef, Directive, contentChildren, computed, ChangeDetectionStrategy, Component, signal, Injectable, ContentChild, effect, output, DOCUMENT, ViewEncapsulation, ElementRef, ViewContainerRef, DestroyRef, isDevMode, viewChild, model, contentChild, untracked, makeEnvironmentProviders, Injector, runInInjectionContext, SecurityContext } from '@angular/core';
3
3
  import { NgTemplateOutlet, NgComponentOutlet, KeyValuePipe } from '@angular/common';
4
4
  import { createPartialMarkdownParser, materialize } from '@cacheplane/partial-markdown';
5
5
  import { views, RenderSpecComponent, toRenderRegistry, signalStateStore, withViews, RenderElementComponent, injectRenderHost } from '@threadplane/render';
@@ -228,6 +228,21 @@ function isSystemMessage(m) {
228
228
  return m.role === 'system';
229
229
  }
230
230
 
231
+ // SPDX-License-Identifier: MIT
232
+ /** Creates the active delivery state for one response-attempt generation. */
233
+ function streamingDelivery(generation) {
234
+ return { generation, phase: 'streaming' };
235
+ }
236
+ /** Creates a terminal delivery state for an existing response-attempt generation. */
237
+ function completeDelivery(generation, outcome) {
238
+ const delivery = { generation, phase: 'complete', outcome };
239
+ return delivery;
240
+ }
241
+ /** Creates a successful terminal delivery state for an already-complete message. */
242
+ function staticDelivery(messageId) {
243
+ return completeDelivery(messageId, 'success');
244
+ }
245
+
231
246
  // SPDX-License-Identifier: MIT
232
247
  /**
233
248
  * Create a typed agent handle.
@@ -307,9 +322,24 @@ const LIGHT_TOKENS = `
307
322
  --a2ui-elevation-5: 0 16px 32px rgba(0, 0, 0, 0.18);
308
323
 
309
324
  /* --tplane-chat-citation-* — inline markers, preview card, sources panel */
310
- --tplane-chat-citation-accent: #2f6fe0;
325
+ --tplane-chat-citation-accent: #1d4ed8;
311
326
  --tplane-chat-citation-accent-soft: #eaf1fd;
312
327
  --tplane-chat-citation-accent-border: #c9def8;
328
+ --tplane-chat-citation-type-web-fg: var(--tplane-chat-citation-accent);
329
+ --tplane-chat-citation-type-web-bg: var(--tplane-chat-citation-accent-soft);
330
+ --tplane-chat-citation-type-web-border: var(--tplane-chat-citation-accent-border);
331
+ --tplane-chat-citation-type-file-fg: #2f684c;
332
+ --tplane-chat-citation-type-file-bg: #edf7f1;
333
+ --tplane-chat-citation-type-file-border: #c8e4d2;
334
+ --tplane-chat-citation-type-app-fg: #7a4d12;
335
+ --tplane-chat-citation-type-app-bg: #fff5e3;
336
+ --tplane-chat-citation-type-app-border: #f0d69f;
337
+ --tplane-chat-citation-type-memory-fg: #67508f;
338
+ --tplane-chat-citation-type-memory-bg: #f3effb;
339
+ --tplane-chat-citation-type-memory-border: #ddd2f1;
340
+ --tplane-chat-citation-type-generic-fg: #526071;
341
+ --tplane-chat-citation-type-generic-bg: #f4f6f8;
342
+ --tplane-chat-citation-type-generic-border: #dfe5ec;
313
343
  --tplane-chat-citation-marker-bg: #f1f2f4;
314
344
  --tplane-chat-citation-marker-border: var(--tplane-chat-separator);
315
345
  --tplane-chat-citation-marker-fg: #4b5563;
@@ -358,6 +388,21 @@ const DARK_TOKENS = `
358
388
  --tplane-chat-citation-accent: #6ea8ff;
359
389
  --tplane-chat-citation-accent-soft: rgba(79, 141, 245, 0.16);
360
390
  --tplane-chat-citation-accent-border: rgba(79, 141, 245, 0.38);
391
+ --tplane-chat-citation-type-web-fg: var(--tplane-chat-citation-accent);
392
+ --tplane-chat-citation-type-web-bg: var(--tplane-chat-citation-accent-soft);
393
+ --tplane-chat-citation-type-web-border: var(--tplane-chat-citation-accent-border);
394
+ --tplane-chat-citation-type-file-fg: #8bd3a9;
395
+ --tplane-chat-citation-type-file-bg: rgba(47, 104, 76, 0.20);
396
+ --tplane-chat-citation-type-file-border: rgba(139, 211, 169, 0.32);
397
+ --tplane-chat-citation-type-app-fg: #f2c36f;
398
+ --tplane-chat-citation-type-app-bg: rgba(122, 77, 18, 0.22);
399
+ --tplane-chat-citation-type-app-border: rgba(242, 195, 111, 0.34);
400
+ --tplane-chat-citation-type-memory-fg: #c4b5fd;
401
+ --tplane-chat-citation-type-memory-bg: rgba(103, 80, 143, 0.26);
402
+ --tplane-chat-citation-type-memory-border: rgba(196, 181, 253, 0.32);
403
+ --tplane-chat-citation-type-generic-fg: #c9ccd1;
404
+ --tplane-chat-citation-type-generic-bg: rgba(255,255,255,0.08);
405
+ --tplane-chat-citation-type-generic-border: rgba(255,255,255,0.14);
361
406
  --tplane-chat-citation-marker-bg: rgba(255, 255, 255, 0.08);
362
407
  --tplane-chat-citation-marker-border: var(--tplane-chat-separator);
363
408
  --tplane-chat-citation-marker-fg: #c9ccd1;
@@ -854,6 +899,13 @@ const CHAT_MESSAGE_STYLES = `
854
899
  .chat-message__control-btn svg { width: 16px; height: 16px; pointer-events: none; }
855
900
  `;
856
901
 
902
+ const KNOWN_TYPE_LABELS = {
903
+ web: 'Web',
904
+ file: 'File',
905
+ app: 'App',
906
+ memory: 'Memory',
907
+ generic: 'Generic',
908
+ };
857
909
  /** Hostname of `url` with a leading `www.` removed; null if absent/malformed. */
858
910
  function deriveDomain(url) {
859
911
  if (!url)
@@ -867,8 +919,9 @@ function deriveDomain(url) {
867
919
  }
868
920
  /** Explicit `sourceType`, else 'web' inferred from a url, else 'unknown'. */
869
921
  function deriveSourceType(c) {
870
- if (c.sourceType)
871
- return c.sourceType;
922
+ const explicit = c.sourceType?.trim();
923
+ if (explicit)
924
+ return explicit;
872
925
  return c.url ? 'web' : 'unknown';
873
926
  }
874
927
  /** Uppercased first letter of the domain (or title) for the monogram chip. */
@@ -899,16 +952,158 @@ function monogramColor(c) {
899
952
  const seed = deriveDomain(c.url) ?? c.title ?? '?';
900
953
  return `hsl(${monogramHue(seed)} 60% 45%)`;
901
954
  }
955
+ function isKnownType(type) {
956
+ return Object.prototype.hasOwnProperty.call(KNOWN_TYPE_LABELS, type);
957
+ }
958
+ function readableSourceTypeLabel(type) {
959
+ const words = type.replace(/[-_\s]+/g, ' ').trim();
960
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : null;
961
+ }
962
+ /**
963
+ * Derive normalized source-type metadata for badges, icons, and tone tokens.
964
+ */
965
+ function citationTypeMeta(c) {
966
+ const type = deriveSourceType(c);
967
+ const canonicalType = type.toLowerCase();
968
+ if (isKnownType(canonicalType)) {
969
+ return {
970
+ type: canonicalType,
971
+ label: KNOWN_TYPE_LABELS[canonicalType],
972
+ icon: canonicalType,
973
+ tone: canonicalType,
974
+ isKnown: true,
975
+ };
976
+ }
977
+ return {
978
+ type,
979
+ label: type === 'unknown' ? null : readableSourceTypeLabel(type),
980
+ icon: 'generic',
981
+ tone: 'generic',
982
+ isKnown: false,
983
+ };
984
+ }
985
+ /**
986
+ * Choose the visual source marker for a citation: provider image, type icon, or monogram.
987
+ */
988
+ function citationSourceVisual(c) {
989
+ const iconUrl = c.iconUrl?.trim();
990
+ if (iconUrl) {
991
+ return { kind: 'image', iconUrl };
992
+ }
993
+ const meta = citationTypeMeta(c);
994
+ if (meta.icon !== 'web') {
995
+ return {
996
+ kind: 'type-icon',
997
+ icon: meta.icon,
998
+ tone: meta.tone,
999
+ label: meta.label,
1000
+ };
1001
+ }
1002
+ return {
1003
+ kind: 'monogram',
1004
+ monogram: deriveMonogram(c),
1005
+ color: monogramColor(c),
1006
+ };
1007
+ }
902
1008
  /** Human label for the source-type badge; null when type is 'unknown'. */
903
1009
  function citationTypeLabel(c) {
904
- const t = deriveSourceType(c);
905
- if (t === 'unknown')
906
- return null;
907
- return t === 'web' ? 'Web' : t.charAt(0).toUpperCase() + t.slice(1);
1010
+ return citationTypeMeta(c).label;
908
1011
  }
909
1012
 
910
1013
  // libs/chat/src/lib/styles/chat-citations.styles.ts
911
1014
  // SPDX-License-Identifier: MIT
1015
+ const CHAT_CITATION_SOURCE_VISUAL_STYLES = `
1016
+ .chat-citation-source-icon {
1017
+ width: 18px;
1018
+ height: 18px;
1019
+ flex: 0 0 auto;
1020
+ box-sizing: border-box;
1021
+ display: inline-flex;
1022
+ align-items: center;
1023
+ justify-content: center;
1024
+ border-radius: 5px;
1025
+ border: 1px solid currentColor;
1026
+ }
1027
+ .chat-citation-source-icon svg {
1028
+ width: 12px;
1029
+ height: 12px;
1030
+ display: block;
1031
+ }
1032
+ .chat-citation-source-icon--sm {
1033
+ width: 14px;
1034
+ height: 14px;
1035
+ border-radius: 4px;
1036
+ }
1037
+ .chat-citation-source-icon--sm svg {
1038
+ width: 9px;
1039
+ height: 9px;
1040
+ }
1041
+ .chat-citation-source-icon--web {
1042
+ color: var(--tplane-chat-citation-type-web-fg);
1043
+ background: var(--tplane-chat-citation-type-web-bg);
1044
+ border-color: var(--tplane-chat-citation-type-web-border);
1045
+ }
1046
+ .chat-citation-source-icon--file {
1047
+ color: var(--tplane-chat-citation-type-file-fg);
1048
+ background: var(--tplane-chat-citation-type-file-bg);
1049
+ border-color: var(--tplane-chat-citation-type-file-border);
1050
+ }
1051
+ .chat-citation-source-icon--app {
1052
+ color: var(--tplane-chat-citation-type-app-fg);
1053
+ background: var(--tplane-chat-citation-type-app-bg);
1054
+ border-color: var(--tplane-chat-citation-type-app-border);
1055
+ }
1056
+ .chat-citation-source-icon--memory {
1057
+ color: var(--tplane-chat-citation-type-memory-fg);
1058
+ background: var(--tplane-chat-citation-type-memory-bg);
1059
+ border-color: var(--tplane-chat-citation-type-memory-border);
1060
+ }
1061
+ .chat-citation-source-icon--generic {
1062
+ color: var(--tplane-chat-citation-type-generic-fg);
1063
+ background: var(--tplane-chat-citation-type-generic-bg);
1064
+ border-color: var(--tplane-chat-citation-type-generic-border);
1065
+ }
1066
+ .chat-citation-type-badge {
1067
+ display: inline-flex;
1068
+ align-items: center;
1069
+ justify-content: center;
1070
+ min-width: 0;
1071
+ max-width: 120px;
1072
+ box-sizing: border-box;
1073
+ padding: 1px 6px;
1074
+ border: 1px solid currentColor;
1075
+ border-radius: 999px;
1076
+ overflow: hidden;
1077
+ text-overflow: ellipsis;
1078
+ white-space: nowrap;
1079
+ line-height: 1.35;
1080
+ }
1081
+ .chat-citation-type-badge--web {
1082
+ color: var(--tplane-chat-citation-type-web-fg);
1083
+ background: var(--tplane-chat-citation-type-web-bg);
1084
+ border-color: var(--tplane-chat-citation-type-web-border);
1085
+ }
1086
+ .chat-citation-type-badge--file {
1087
+ color: var(--tplane-chat-citation-type-file-fg);
1088
+ background: var(--tplane-chat-citation-type-file-bg);
1089
+ border-color: var(--tplane-chat-citation-type-file-border);
1090
+ }
1091
+ .chat-citation-type-badge--app {
1092
+ color: var(--tplane-chat-citation-type-app-fg);
1093
+ background: var(--tplane-chat-citation-type-app-bg);
1094
+ border-color: var(--tplane-chat-citation-type-app-border);
1095
+ }
1096
+ .chat-citation-type-badge--memory {
1097
+ color: var(--tplane-chat-citation-type-memory-fg);
1098
+ background: var(--tplane-chat-citation-type-memory-bg);
1099
+ border-color: var(--tplane-chat-citation-type-memory-border);
1100
+ }
1101
+ .chat-citation-type-badge--generic {
1102
+ color: var(--tplane-chat-citation-type-generic-fg);
1103
+ background: var(--tplane-chat-citation-type-generic-bg);
1104
+ border-color: var(--tplane-chat-citation-type-generic-border);
1105
+ }
1106
+ `;
912
1107
  /** Inline pill marker (chat-md-citation-reference). */
913
1108
  const CHAT_CITATION_MARKER_STYLES = `
914
1109
  :host { display: inline; }
@@ -960,6 +1155,7 @@ const CHAT_CITATION_MARKER_STYLES = `
960
1155
  `;
961
1156
  /** Provenance preview card (chat-citation-preview), portaled into the overlay pane. */
962
1157
  const CHAT_CITATION_PREVIEW_STYLES = `
1158
+ ${CHAT_CITATION_SOURCE_VISUAL_STYLES}
963
1159
  :host { display: block; }
964
1160
  .chat-citation-preview {
965
1161
  width: 320px;
@@ -997,7 +1193,6 @@ const CHAT_CITATION_PREVIEW_STYLES = `
997
1193
  .chat-citation-preview__type {
998
1194
  margin-left: auto;
999
1195
  font-size: 11px;
1000
- color: var(--tplane-chat-text-muted);
1001
1196
  flex: 0 0 auto;
1002
1197
  }
1003
1198
  .chat-citation-preview__title {
@@ -1026,6 +1221,7 @@ const CHAT_CITATION_PREVIEW_STYLES = `
1026
1221
  `;
1027
1222
  /** Sources panel (chat-citations) + detail card (chat-citations-card). */
1028
1223
  const CHAT_CITATIONS_PANEL_STYLES = `
1224
+ ${CHAT_CITATION_SOURCE_VISUAL_STYLES}
1029
1225
  :host { display: block; }
1030
1226
  .chat-citations {
1031
1227
  margin-top: var(--tplane-chat-space-5);
@@ -1062,6 +1258,16 @@ const CHAT_CITATIONS_PANEL_STYLES = `
1062
1258
  display: flex; align-items: center; justify-content: center;
1063
1259
  color: #fff; font-size: 9px; font-weight: 700;
1064
1260
  }
1261
+ .chat-citations__source-icon {
1262
+ width: 16px; height: 16px;
1263
+ border-radius: 4px;
1264
+ margin-left: -5px;
1265
+ border-width: 1.5px;
1266
+ }
1267
+ .chat-citations__source-icon:first-child { margin-left: 0; }
1268
+ .chat-citations__source-icon svg {
1269
+ width: 10px; height: 10px;
1270
+ }
1065
1271
  .chat-citations__chevron {
1066
1272
  margin-left: auto;
1067
1273
  color: var(--tplane-chat-text-muted);
@@ -1112,7 +1318,7 @@ const CHAT_CITATIONS_PANEL_STYLES = `
1112
1318
  color: #fff; font-size: 8px; font-weight: 700;
1113
1319
  }
1114
1320
  .chat-citations-card__domain { font-size: 11.5px; color: var(--tplane-chat-text-muted); }
1115
- .chat-citations-card__type { margin-left: auto; font-size: 10.5px; color: var(--tplane-chat-text-muted); }
1321
+ .chat-citations-card__type { margin-left: auto; font-size: 10.5px; }
1116
1322
  .chat-citations-card__title {
1117
1323
  font-size: 13.5px; font-weight: 600; line-height: 1.35;
1118
1324
  margin: 0 0 2px;
@@ -1135,11 +1341,31 @@ const CHAT_CITATIONS_PANEL_STYLES = `
1135
1341
  */
1136
1342
  class ChatCitationsCardComponent {
1137
1343
  citation = input.required(...(ngDevMode ? [{ debugName: "citation" }] : []));
1344
+ sourceVisual = computed(() => citationSourceVisual(this.citation()), ...(ngDevMode ? [{ debugName: "sourceVisual" }] : []));
1345
+ typeMeta = computed(() => citationTypeMeta(this.citation()), ...(ngDevMode ? [{ debugName: "typeMeta" }] : []));
1138
1346
  domain = computed(() => deriveDomain(this.citation().url), ...(ngDevMode ? [{ debugName: "domain" }] : []));
1139
1347
  title = computed(() => this.citation().title ?? this.citation().url ?? null, ...(ngDevMode ? [{ debugName: "title" }] : []));
1140
- monogram = computed(() => deriveMonogram(this.citation()), ...(ngDevMode ? [{ debugName: "monogram" }] : []));
1141
- monoColor = computed(() => monogramColor(this.citation()), ...(ngDevMode ? [{ debugName: "monoColor" }] : []));
1142
- typeLabel = computed(() => citationTypeLabel(this.citation()), ...(ngDevMode ? [{ debugName: "typeLabel" }] : []));
1348
+ sourceIconUrl = computed(() => {
1349
+ const visual = this.sourceVisual();
1350
+ return visual.kind === 'image' ? visual.iconUrl : null;
1351
+ }, ...(ngDevMode ? [{ debugName: "sourceIconUrl" }] : []));
1352
+ sourceIcon = computed(() => {
1353
+ const visual = this.sourceVisual();
1354
+ return visual.kind === 'type-icon' ? visual.icon : null;
1355
+ }, ...(ngDevMode ? [{ debugName: "sourceIcon" }] : []));
1356
+ sourceMonogram = computed(() => {
1357
+ const visual = this.sourceVisual();
1358
+ return visual.kind === 'monogram' ? visual.monogram : null;
1359
+ }, ...(ngDevMode ? [{ debugName: "sourceMonogram" }] : []));
1360
+ sourceMonoColor = computed(() => {
1361
+ const visual = this.sourceVisual();
1362
+ return visual.kind === 'monogram' ? visual.color : null;
1363
+ }, ...(ngDevMode ? [{ debugName: "sourceMonoColor" }] : []));
1364
+ typeLabel = computed(() => this.typeMeta().label, ...(ngDevMode ? [{ debugName: "typeLabel" }] : []));
1365
+ typeTone = computed(() => this.typeMeta().tone, ...(ngDevMode ? [{ debugName: "typeTone" }] : []));
1366
+ isTypeTone(tone) {
1367
+ return this.typeTone() === tone;
1368
+ }
1143
1369
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1144
1370
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatCitationsCardComponent, isStandalone: true, selector: "chat-citations-card", inputs: { citation: { classPropertyName: "citation", publicName: "citation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1145
1371
  @if (citation().url; as url) {
@@ -1157,14 +1383,54 @@ class ChatCitationsCardComponent {
1157
1383
  <span class="chat-citations-card__index">{{ citation().index }}</span>
1158
1384
  <span class="chat-citations-card__body">
1159
1385
  <span class="chat-citations-card__top">
1160
- @if (citation().iconUrl; as icon) {
1386
+ @if (sourceIconUrl(); as icon) {
1161
1387
  <img class="chat-citations-card__fav" [src]="icon" alt="" width="14" height="14" />
1388
+ } @else if (sourceIcon(); as icon) {
1389
+ <span class="chat-citation-source-icon chat-citation-source-icon--sm chat-citations-card__source-icon"
1390
+ aria-hidden="true"
1391
+ [class.chat-citation-source-icon--web]="icon === 'web'"
1392
+ [class.chat-citation-source-icon--file]="icon === 'file'"
1393
+ [class.chat-citation-source-icon--app]="icon === 'app'"
1394
+ [class.chat-citation-source-icon--memory]="icon === 'memory'"
1395
+ [class.chat-citation-source-icon--generic]="icon === 'generic'">
1396
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1397
+ @switch (icon) {
1398
+ @case ('file') {
1399
+ <path d="M7 3.5h6.5L18 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1400
+ <path d="M13.5 3.5V8H18M9.5 13h5M9.5 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1401
+ }
1402
+ @case ('app') {
1403
+ <rect x="4" y="5" width="16" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8" />
1404
+ <path d="M8 9h.01M11.5 9h.01M15 9h.01M8 12.5h8M8 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1405
+ }
1406
+ @case ('memory') {
1407
+ <path d="M8 7.5a4 4 0 0 1 8 0v9a4 4 0 0 1-8 0z" fill="none" stroke="currentColor" stroke-width="1.8" />
1408
+ <path d="M12 4v16M8 10h8M8 14h8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1409
+ }
1410
+ @case ('web') {
1411
+ <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.8" />
1412
+ <path d="M4.5 12h15M12 4a12 12 0 0 1 0 16M12 4a12 12 0 0 0 0 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1413
+ }
1414
+ @default {
1415
+ <path d="M6 5.5h12v13H6z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1416
+ <path d="M9 9h6M9 12h6M9 15h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1417
+ }
1418
+ }
1419
+ </svg>
1420
+ </span>
1162
1421
  } @else {
1163
1422
  <span class="chat-citations-card__fav chat-citations-card__fav--mono"
1164
- [style.background]="monoColor()">{{ monogram() }}</span>
1423
+ [style.background]="sourceMonoColor()">{{ sourceMonogram() }}</span>
1165
1424
  }
1166
1425
  @if (domain(); as d) { <span class="chat-citations-card__domain">{{ d }}</span> }
1167
- @if (typeLabel(); as t) { <span class="chat-citations-card__type">{{ t }}</span> }
1426
+ @if (typeLabel(); as t) {
1427
+ <span class="chat-citations-card__type chat-citation-type-badge"
1428
+ [class.chat-citation-type-badge--web]="isTypeTone('web')"
1429
+ [class.chat-citation-type-badge--file]="isTypeTone('file')"
1430
+ [class.chat-citation-type-badge--app]="isTypeTone('app')"
1431
+ [class.chat-citation-type-badge--memory]="isTypeTone('memory')"
1432
+ [class.chat-citation-type-badge--generic]="isTypeTone('generic')">{{ t }}</span>
1433
+ }
1168
1434
  </span>
1169
1435
  @if (title(); as t) {
1170
1436
  <span class="chat-citations-card__title">{{ t }}</span>
@@ -1174,7 +1440,7 @@ class ChatCitationsCardComponent {
1174
1440
  }
1175
1441
  </span>
1176
1442
  </ng-template>
1177
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1443
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ".chat-citation-source-icon{width:18px;height:18px;flex:0 0 auto;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border-radius:5px;border:1px solid currentColor}.chat-citation-source-icon svg{width:12px;height:12px;display:block}.chat-citation-source-icon--sm{width:14px;height:14px;border-radius:4px}.chat-citation-source-icon--sm svg{width:9px;height:9px}.chat-citation-source-icon--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-source-icon--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-source-icon--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-source-icon--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-source-icon--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}.chat-citation-type-badge{display:inline-flex;align-items:center;justify-content:center;min-width:0;max-width:120px;box-sizing:border-box;padding:1px 6px;border:1px solid currentColor;border-radius:999px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.chat-citation-type-badge--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-type-badge--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-type-badge--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-type-badge--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-type-badge--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}:host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__source-icon{width:16px;height:16px;border-radius:4px;margin-left:-5px;border-width:1.5px}.chat-citations__source-icon:first-child{margin-left:0}.chat-citations__source-icon svg{width:10px;height:10px}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1178
1444
  }
1179
1445
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsCardComponent, decorators: [{
1180
1446
  type: Component,
@@ -1194,14 +1460,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
1194
1460
  <span class="chat-citations-card__index">{{ citation().index }}</span>
1195
1461
  <span class="chat-citations-card__body">
1196
1462
  <span class="chat-citations-card__top">
1197
- @if (citation().iconUrl; as icon) {
1463
+ @if (sourceIconUrl(); as icon) {
1198
1464
  <img class="chat-citations-card__fav" [src]="icon" alt="" width="14" height="14" />
1465
+ } @else if (sourceIcon(); as icon) {
1466
+ <span class="chat-citation-source-icon chat-citation-source-icon--sm chat-citations-card__source-icon"
1467
+ aria-hidden="true"
1468
+ [class.chat-citation-source-icon--web]="icon === 'web'"
1469
+ [class.chat-citation-source-icon--file]="icon === 'file'"
1470
+ [class.chat-citation-source-icon--app]="icon === 'app'"
1471
+ [class.chat-citation-source-icon--memory]="icon === 'memory'"
1472
+ [class.chat-citation-source-icon--generic]="icon === 'generic'">
1473
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1474
+ @switch (icon) {
1475
+ @case ('file') {
1476
+ <path d="M7 3.5h6.5L18 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1477
+ <path d="M13.5 3.5V8H18M9.5 13h5M9.5 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1478
+ }
1479
+ @case ('app') {
1480
+ <rect x="4" y="5" width="16" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8" />
1481
+ <path d="M8 9h.01M11.5 9h.01M15 9h.01M8 12.5h8M8 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1482
+ }
1483
+ @case ('memory') {
1484
+ <path d="M8 7.5a4 4 0 0 1 8 0v9a4 4 0 0 1-8 0z" fill="none" stroke="currentColor" stroke-width="1.8" />
1485
+ <path d="M12 4v16M8 10h8M8 14h8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1486
+ }
1487
+ @case ('web') {
1488
+ <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.8" />
1489
+ <path d="M4.5 12h15M12 4a12 12 0 0 1 0 16M12 4a12 12 0 0 0 0 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1490
+ }
1491
+ @default {
1492
+ <path d="M6 5.5h12v13H6z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1493
+ <path d="M9 9h6M9 12h6M9 15h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1494
+ }
1495
+ }
1496
+ </svg>
1497
+ </span>
1199
1498
  } @else {
1200
1499
  <span class="chat-citations-card__fav chat-citations-card__fav--mono"
1201
- [style.background]="monoColor()">{{ monogram() }}</span>
1500
+ [style.background]="sourceMonoColor()">{{ sourceMonogram() }}</span>
1202
1501
  }
1203
1502
  @if (domain(); as d) { <span class="chat-citations-card__domain">{{ d }}</span> }
1204
- @if (typeLabel(); as t) { <span class="chat-citations-card__type">{{ t }}</span> }
1503
+ @if (typeLabel(); as t) {
1504
+ <span class="chat-citations-card__type chat-citation-type-badge"
1505
+ [class.chat-citation-type-badge--web]="isTypeTone('web')"
1506
+ [class.chat-citation-type-badge--file]="isTypeTone('file')"
1507
+ [class.chat-citation-type-badge--app]="isTypeTone('app')"
1508
+ [class.chat-citation-type-badge--memory]="isTypeTone('memory')"
1509
+ [class.chat-citation-type-badge--generic]="isTypeTone('generic')">{{ t }}</span>
1510
+ }
1205
1511
  </span>
1206
1512
  @if (title(); as t) {
1207
1513
  <span class="chat-citations-card__title">{{ t }}</span>
@@ -1211,7 +1517,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
1211
1517
  }
1212
1518
  </span>
1213
1519
  </ng-template>
1214
- `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
1520
+ `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ".chat-citation-source-icon{width:18px;height:18px;flex:0 0 auto;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border-radius:5px;border:1px solid currentColor}.chat-citation-source-icon svg{width:12px;height:12px;display:block}.chat-citation-source-icon--sm{width:14px;height:14px;border-radius:4px}.chat-citation-source-icon--sm svg{width:9px;height:9px}.chat-citation-source-icon--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-source-icon--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-source-icon--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-source-icon--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-source-icon--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}.chat-citation-type-badge{display:inline-flex;align-items:center;justify-content:center;min-width:0;max-width:120px;box-sizing:border-box;padding:1px 6px;border:1px solid currentColor;border-radius:999px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.chat-citation-type-badge--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-type-badge--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-type-badge--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-type-badge--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-type-badge--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}:host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__source-icon{width:16px;height:16px;border-radius:4px;margin-left:-5px;border-width:1.5px}.chat-citations__source-icon:first-child{margin-left:0}.chat-citations__source-icon svg{width:10px;height:10px}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
1215
1521
  }], propDecorators: { citation: [{ type: i0.Input, args: [{ isSignal: true, alias: "citation", required: true }] }] } });
1216
1522
 
1217
1523
  // libs/chat/src/lib/markdown/citations-resolver.service.ts
@@ -1308,9 +1614,7 @@ class ChatCitationsComponent {
1308
1614
  return [...fromMessage, ...fromMarkdown].sort((a, b) => a.index - b.index);
1309
1615
  }, ...(ngDevMode ? [{ debugName: "citations" }] : []));
1310
1616
  /** First 3 sources, mapped to favicon/monogram chips for the header preview. */
1311
- favstack = computed(() => this.citations().slice(0, 3).map((c) => ({
1312
- id: c.id, iconUrl: c.iconUrl, mono: deriveMonogram(c), color: monogramColor(c),
1313
- })), ...(ngDevMode ? [{ debugName: "favstack" }] : []));
1617
+ favstack = computed(() => this.citations().slice(0, 3).map((c) => ({ id: c.id, ...citationSourceVisual(c) })), ...(ngDevMode ? [{ debugName: "favstack" }] : []));
1314
1618
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1315
1619
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatCitationsComponent, isStandalone: true, selector: "chat-citations", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "cardTpl", first: true, predicate: ChatCitationCardTemplateDirective, descendants: true }], ngImport: i0, template: `
1316
1620
  @if (citations().length > 0) {
@@ -1326,11 +1630,48 @@ class ChatCitationsComponent {
1326
1630
  <span class="chat-citations__count">{{ citations().length }}</span>
1327
1631
  <span class="chat-citations__favstack" aria-hidden="true">
1328
1632
  @for (f of favstack(); track f.id) {
1329
- @if (f.iconUrl) {
1633
+ @if (f.kind === 'image' && f.iconUrl) {
1330
1634
  <img class="chat-citations__fav" [src]="f.iconUrl" alt="" width="16" height="16" />
1635
+ } @else if (f.kind === 'type-icon' && f.icon) {
1636
+ <span class="chat-citation-source-icon chat-citations__source-icon"
1637
+ [class.chat-citation-source-icon--web]="f.tone === 'web'"
1638
+ [class.chat-citation-source-icon--file]="f.tone === 'file'"
1639
+ [class.chat-citation-source-icon--app]="f.tone === 'app'"
1640
+ [class.chat-citation-source-icon--memory]="f.tone === 'memory'"
1641
+ [class.chat-citation-source-icon--generic]="f.tone === 'generic'"
1642
+ [class.chat-citations__source-icon--web]="f.icon === 'web'"
1643
+ [class.chat-citations__source-icon--file]="f.icon === 'file'"
1644
+ [class.chat-citations__source-icon--app]="f.icon === 'app'"
1645
+ [class.chat-citations__source-icon--memory]="f.icon === 'memory'"
1646
+ [class.chat-citations__source-icon--generic]="f.icon === 'generic'">
1647
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1648
+ @switch (f.icon) {
1649
+ @case ('file') {
1650
+ <path d="M7 3.5h6.5L18 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1651
+ <path d="M13.5 3.5V8H18M9.5 13h5M9.5 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1652
+ }
1653
+ @case ('app') {
1654
+ <rect x="4" y="5" width="16" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8" />
1655
+ <path d="M8 9h.01M11.5 9h.01M15 9h.01M8 12.5h8M8 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1656
+ }
1657
+ @case ('memory') {
1658
+ <path d="M8 7.5a4 4 0 0 1 8 0v9a4 4 0 0 1-8 0z" fill="none" stroke="currentColor" stroke-width="1.8" />
1659
+ <path d="M12 4v16M8 10h8M8 14h8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1660
+ }
1661
+ @case ('web') {
1662
+ <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.8" />
1663
+ <path d="M4.5 12h15M12 4a12 12 0 0 1 0 16M12 4a12 12 0 0 0 0 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1664
+ }
1665
+ @default {
1666
+ <path d="M6 5.5h12v13H6z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1667
+ <path d="M9 9h6M9 12h6M9 15h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1668
+ }
1669
+ }
1670
+ </svg>
1671
+ </span>
1331
1672
  } @else {
1332
1673
  <span class="chat-citations__fav chat-citations__fav--mono"
1333
- [style.background]="f.color">{{ f.mono }}</span>
1674
+ [style.background]="f.color">{{ f.monogram }}</span>
1334
1675
  }
1335
1676
  }
1336
1677
  </span>
@@ -1354,7 +1695,7 @@ class ChatCitationsComponent {
1354
1695
  }
1355
1696
  </section>
1356
1697
  }
1357
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatCitationsCardComponent, selector: "chat-citations-card", inputs: ["citation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1698
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ".chat-citation-source-icon{width:18px;height:18px;flex:0 0 auto;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border-radius:5px;border:1px solid currentColor}.chat-citation-source-icon svg{width:12px;height:12px;display:block}.chat-citation-source-icon--sm{width:14px;height:14px;border-radius:4px}.chat-citation-source-icon--sm svg{width:9px;height:9px}.chat-citation-source-icon--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-source-icon--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-source-icon--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-source-icon--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-source-icon--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}.chat-citation-type-badge{display:inline-flex;align-items:center;justify-content:center;min-width:0;max-width:120px;box-sizing:border-box;padding:1px 6px;border:1px solid currentColor;border-radius:999px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.chat-citation-type-badge--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-type-badge--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-type-badge--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-type-badge--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-type-badge--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}:host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__source-icon{width:16px;height:16px;border-radius:4px;margin-left:-5px;border-width:1.5px}.chat-citations__source-icon:first-child{margin-left:0}.chat-citations__source-icon svg{width:10px;height:10px}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChatCitationsCardComponent, selector: "chat-citations-card", inputs: ["citation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1358
1699
  }
1359
1700
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationsComponent, decorators: [{
1360
1701
  type: Component,
@@ -1372,11 +1713,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
1372
1713
  <span class="chat-citations__count">{{ citations().length }}</span>
1373
1714
  <span class="chat-citations__favstack" aria-hidden="true">
1374
1715
  @for (f of favstack(); track f.id) {
1375
- @if (f.iconUrl) {
1716
+ @if (f.kind === 'image' && f.iconUrl) {
1376
1717
  <img class="chat-citations__fav" [src]="f.iconUrl" alt="" width="16" height="16" />
1718
+ } @else if (f.kind === 'type-icon' && f.icon) {
1719
+ <span class="chat-citation-source-icon chat-citations__source-icon"
1720
+ [class.chat-citation-source-icon--web]="f.tone === 'web'"
1721
+ [class.chat-citation-source-icon--file]="f.tone === 'file'"
1722
+ [class.chat-citation-source-icon--app]="f.tone === 'app'"
1723
+ [class.chat-citation-source-icon--memory]="f.tone === 'memory'"
1724
+ [class.chat-citation-source-icon--generic]="f.tone === 'generic'"
1725
+ [class.chat-citations__source-icon--web]="f.icon === 'web'"
1726
+ [class.chat-citations__source-icon--file]="f.icon === 'file'"
1727
+ [class.chat-citations__source-icon--app]="f.icon === 'app'"
1728
+ [class.chat-citations__source-icon--memory]="f.icon === 'memory'"
1729
+ [class.chat-citations__source-icon--generic]="f.icon === 'generic'">
1730
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1731
+ @switch (f.icon) {
1732
+ @case ('file') {
1733
+ <path d="M7 3.5h6.5L18 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1734
+ <path d="M13.5 3.5V8H18M9.5 13h5M9.5 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1735
+ }
1736
+ @case ('app') {
1737
+ <rect x="4" y="5" width="16" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8" />
1738
+ <path d="M8 9h.01M11.5 9h.01M15 9h.01M8 12.5h8M8 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1739
+ }
1740
+ @case ('memory') {
1741
+ <path d="M8 7.5a4 4 0 0 1 8 0v9a4 4 0 0 1-8 0z" fill="none" stroke="currentColor" stroke-width="1.8" />
1742
+ <path d="M12 4v16M8 10h8M8 14h8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1743
+ }
1744
+ @case ('web') {
1745
+ <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.8" />
1746
+ <path d="M4.5 12h15M12 4a12 12 0 0 1 0 16M12 4a12 12 0 0 0 0 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1747
+ }
1748
+ @default {
1749
+ <path d="M6 5.5h12v13H6z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
1750
+ <path d="M9 9h6M9 12h6M9 15h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
1751
+ }
1752
+ }
1753
+ </svg>
1754
+ </span>
1377
1755
  } @else {
1378
1756
  <span class="chat-citations__fav chat-citations__fav--mono"
1379
- [style.background]="f.color">{{ f.mono }}</span>
1757
+ [style.background]="f.color">{{ f.monogram }}</span>
1380
1758
  }
1381
1759
  }
1382
1760
  </span>
@@ -1400,7 +1778,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
1400
1778
  }
1401
1779
  </section>
1402
1780
  }
1403
- `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
1781
+ `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ".chat-citation-source-icon{width:18px;height:18px;flex:0 0 auto;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border-radius:5px;border:1px solid currentColor}.chat-citation-source-icon svg{width:12px;height:12px;display:block}.chat-citation-source-icon--sm{width:14px;height:14px;border-radius:4px}.chat-citation-source-icon--sm svg{width:9px;height:9px}.chat-citation-source-icon--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-source-icon--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-source-icon--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-source-icon--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-source-icon--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}.chat-citation-type-badge{display:inline-flex;align-items:center;justify-content:center;min-width:0;max-width:120px;box-sizing:border-box;padding:1px 6px;border:1px solid currentColor;border-radius:999px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.chat-citation-type-badge--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-type-badge--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-type-badge--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-type-badge--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-type-badge--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}:host{display:block}.chat-citations{margin-top:var(--tplane-chat-space-5);padding-top:var(--tplane-chat-space-4);border-top:1px solid var(--tplane-chat-separator)}.chat-citations__header{display:flex;align-items:center;gap:9px;width:100%;padding:0 0 11px;background:none;border:0;font:inherit;color:inherit;cursor:pointer;text-align:left}.chat-citations__heading{font-size:13px;font-weight:600}.chat-citations__count{font-size:11px;font-weight:600;color:var(--tplane-chat-text-muted);background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:20px;padding:1px 7px}.chat-citations__favstack{display:flex;margin-left:2px}.chat-citations__fav{width:16px;height:16px;border-radius:4px;margin-left:-5px;border:1.5px solid var(--tplane-chat-surface);object-fit:cover}.chat-citations__fav:first-child{margin-left:0}.chat-citations__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700}.chat-citations__source-icon{width:16px;height:16px;border-radius:4px;margin-left:-5px;border-width:1.5px}.chat-citations__source-icon:first-child{margin-left:0}.chat-citations__source-icon svg{width:10px;height:10px}.chat-citations__chevron{margin-left:auto;color:var(--tplane-chat-text-muted);transition:transform .12s ease}.chat-citations__chevron.is-open{transform:rotate(180deg)}.chat-citations__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.chat-citations__item{margin:0}.chat-citations-card{display:flex;gap:10px;padding:10px 11px;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);text-decoration:none;color:inherit;cursor:pointer;transition:border-color .12s ease,background .12s ease}.chat-citations-card:hover,.chat-citations-card:focus-visible{border-color:var(--tplane-chat-citation-accent-border);outline:none}.chat-citations-card__index{flex:0 0 auto;width:18px;height:18px;border-radius:5px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted);font-size:11px;font-weight:600;display:flex;align-items:center;justify-content:center;margin-top:1px}.chat-citations-card__body{min-width:0;flex:1}.chat-citations-card__top{display:flex;align-items:center;gap:6px;margin-bottom:3px}.chat-citations-card__fav{width:14px;height:14px;border-radius:3px;flex:0 0 auto;object-fit:cover}.chat-citations-card__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:700}.chat-citations-card__domain{font-size:11.5px;color:var(--tplane-chat-text-muted)}.chat-citations-card__type{margin-left:auto;font-size:10.5px}.chat-citations-card__title{font-size:13.5px;font-weight:600;line-height:1.35;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citations-card:hover .chat-citations-card__title{color:var(--tplane-chat-citation-accent)}.chat-citations-card__snippet{font-size:12px;color:var(--tplane-chat-text-muted);line-height:1.45;margin:0;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
1404
1782
  }], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], cardTpl: [{
1405
1783
  type: ContentChild,
1406
1784
  args: [ChatCitationCardTemplateDirective]
@@ -1994,184 +2372,6 @@ const CHAT_REASONING_STYLES = `
1994
2372
  .chat-reasoning__body chat-streaming-md { font-size: 0.95em; }
1995
2373
  `;
1996
2374
 
1997
- // libs/chat/src/lib/styles/chat-markdown.styles.ts
1998
- // SPDX-License-Identifier: MIT
1999
- //
2000
- // Scoped to the `chat-streaming-md` element selector (not `:host`) because
2001
- // the component renders markdown via innerHTML and uses
2002
- // `ViewEncapsulation.None` — emulated encapsulation would skip these rules
2003
- // since innerHTML-injected nodes don't carry `_ngcontent-xxx` attributes.
2004
- // Keeping selectors prefixed with the element name preserves locality so
2005
- // these rules don't leak to other markup on the page.
2006
- //
2007
- // Covers the CommonMark + GFM surface: headings, paragraphs, links, lists
2008
- // (bullet/ordered/task), code (inline + fenced), blockquotes, horizontal
2009
- // rules, tables, images, bold (`strong`), italic (`em`), strikethrough
2010
- // (`del`/`s`).
2011
- const CHAT_MARKDOWN_STYLES = `
2012
- chat-streaming-md { display: block; color: var(--tplane-chat-text); line-height: var(--tplane-chat-line-height); }
2013
-
2014
- /* Headings */
2015
- chat-streaming-md h1, chat-streaming-md h2, chat-streaming-md h3, chat-streaming-md h4, chat-streaming-md h5, chat-streaming-md h6 {
2016
- font-weight: 600;
2017
- line-height: 1.25;
2018
- margin: 1.25rem 0 0.75rem;
2019
- }
2020
- chat-streaming-md h1:first-child, chat-streaming-md h2:first-child, chat-streaming-md h3:first-child,
2021
- chat-streaming-md h4:first-child, chat-streaming-md h5:first-child, chat-streaming-md h6:first-child { margin-top: 0; }
2022
- chat-streaming-md h1 { font-size: 1.5em; font-weight: 700; }
2023
- chat-streaming-md h2 { font-size: 1.25em; }
2024
- chat-streaming-md h3 { font-size: 1.1em; }
2025
- chat-streaming-md h4 { font-size: 1em; }
2026
- chat-streaming-md h5, chat-streaming-md h6 { font-size: 0.95em; color: var(--tplane-chat-text-muted); }
2027
-
2028
- /* Paragraphs and inline emphasis */
2029
- chat-streaming-md p { margin: 0 0 0.75rem; line-height: 1.6; font-size: var(--tplane-chat-font-size); }
2030
- chat-streaming-md p:last-child { margin-bottom: 0; }
2031
- chat-streaming-md strong, chat-streaming-md b { font-weight: 700; }
2032
- chat-streaming-md em, chat-streaming-md i { font-style: italic; }
2033
- chat-streaming-md del, chat-streaming-md s { text-decoration: line-through; color: var(--tplane-chat-text-muted); }
2034
- chat-streaming-md mark { background: var(--tplane-chat-surface-alt); padding: 0 2px; border-radius: 2px; }
2035
- chat-streaming-md sub { font-size: 0.75em; vertical-align: sub; }
2036
- chat-streaming-md sup { font-size: 0.75em; vertical-align: super; }
2037
-
2038
- /* Links */
2039
- chat-streaming-md a { color: var(--tplane-chat-primary); text-decoration: underline; text-underline-offset: 2px; }
2040
- chat-streaming-md a:hover { text-decoration-thickness: 2px; }
2041
-
2042
- /* Lists (CommonMark + GFM task lists) */
2043
- chat-streaming-md ul, chat-streaming-md ol { margin: 0 0 0.75rem; padding-left: 1.5rem; }
2044
- chat-streaming-md ul { list-style: disc outside; }
2045
- chat-streaming-md ol { list-style: decimal outside; }
2046
- chat-streaming-md ul ul { list-style: circle outside; }
2047
- chat-streaming-md ul ul ul { list-style: square outside; }
2048
- chat-streaming-md li { margin: 0.2rem 0; }
2049
- chat-streaming-md li::marker { color: var(--tplane-chat-text-muted); }
2050
- chat-streaming-md li > p { margin: 0 0 0.25rem; }
2051
- chat-streaming-md li > ul, chat-streaming-md li > ol { margin: 0.25rem 0 0; }
2052
- /* GFM task lists: marked emits <li><input type="checkbox" disabled> ... */
2053
- chat-streaming-md li:has(> input[type="checkbox"]) { list-style: none; margin-left: -1.25rem; }
2054
- chat-streaming-md li > input[type="checkbox"] { margin-right: 0.5rem; vertical-align: middle; }
2055
-
2056
- /* Code (inline + fenced) */
2057
- chat-streaming-md code {
2058
- background: var(--tplane-chat-surface-alt);
2059
- color: var(--tplane-chat-text);
2060
- padding: 1px 5px;
2061
- border-radius: 4px;
2062
- font-family: var(--tplane-chat-font-mono);
2063
- font-size: 0.9em;
2064
- }
2065
- chat-streaming-md pre {
2066
- background: var(--tplane-chat-surface-alt);
2067
- color: var(--tplane-chat-text);
2068
- padding: 12px 14px;
2069
- border-radius: var(--tplane-chat-radius-card);
2070
- overflow-x: auto;
2071
- font-family: var(--tplane-chat-font-mono);
2072
- font-size: var(--tplane-chat-font-size-sm);
2073
- line-height: 1.5;
2074
- margin: 0 0 0.75rem;
2075
- }
2076
- chat-streaming-md pre code { background: transparent; padding: 0; border-radius: 0; font-size: inherit; }
2077
-
2078
- /* Blockquote */
2079
- chat-streaming-md blockquote {
2080
- border-left: 3px solid var(--tplane-chat-separator);
2081
- padding: 0.25rem 0 0.25rem 12px;
2082
- margin: 0 0 0.75rem;
2083
- color: var(--tplane-chat-text-muted);
2084
- }
2085
- chat-streaming-md blockquote > :last-child { margin-bottom: 0; }
2086
-
2087
- /* Horizontal rule */
2088
- chat-streaming-md hr {
2089
- border: none;
2090
- border-top: 1px solid var(--tplane-chat-separator);
2091
- margin: 1rem 0;
2092
- }
2093
-
2094
- /* Tables (GFM) */
2095
- chat-streaming-md table {
2096
- border-collapse: collapse;
2097
- margin: 0 0 0.75rem;
2098
- width: 100%;
2099
- font-size: 0.95em;
2100
- }
2101
- chat-streaming-md thead { background: var(--tplane-chat-surface-alt); }
2102
- chat-streaming-md th, chat-streaming-md td {
2103
- border: 1px solid var(--tplane-chat-separator);
2104
- padding: 6px 10px;
2105
- text-align: left;
2106
- vertical-align: top;
2107
- }
2108
- chat-streaming-md th { font-weight: 600; }
2109
- /* Component-rendered table: chat-md-table becomes a horizontally-scrollable
2110
- wrapper for the inner <table>; row/cell elements stay layout-transparent
2111
- so the browser's table layout takes over. Without this overflow wrapper,
2112
- wide tables push their parent container past the viewport horizontally. */
2113
- chat-streaming-md chat-md-table {
2114
- display: block;
2115
- overflow-x: auto;
2116
- max-width: 100%;
2117
- margin: 0 0 0.75rem;
2118
- }
2119
- chat-streaming-md chat-md-table-row { display: contents; }
2120
- chat-streaming-md chat-md-table-cell { display: contents; }
2121
- chat-streaming-md chat-md-table > table { margin: 0; }
2122
- /* Task-list items: checkbox + first paragraph render inline; subsequent
2123
- blocks (sub-lists, multi-paragraph items) flow normally below. */
2124
- chat-streaming-md li.chat-md-list-item--task {
2125
- list-style: none;
2126
- margin-left: -1.25rem;
2127
- display: flex;
2128
- flex-wrap: wrap;
2129
- align-items: baseline;
2130
- gap: 0.5rem;
2131
- }
2132
- chat-streaming-md li.chat-md-list-item--task > input[type="checkbox"] {
2133
- margin: 0;
2134
- flex: 0 0 auto;
2135
- transform: translateY(2px);
2136
- }
2137
- /* The chat-md-children wrapper around list-item content takes remaining width */
2138
- chat-streaming-md li.chat-md-list-item--task > chat-md-children {
2139
- flex: 1 1 auto;
2140
- min-width: 0;
2141
- }
2142
- /* Tight task items: only the FIRST paragraph aligns inline with the
2143
- checkbox (margin collapsed). Subsequent paragraphs/blocks keep their
2144
- normal vertical spacing so multi-block items render readably. */
2145
- chat-streaming-md li.chat-md-list-item--task > chat-md-children > chat-md-paragraph:first-child > p {
2146
- margin: 0;
2147
- }
2148
-
2149
- /* Media */
2150
- chat-streaming-md img { max-width: 100%; height: auto; border-radius: 6px; }
2151
- /* Broken-image fallback: muted pill showing alt text + icon. Triggered
2152
- when <img> fires (error). Caught by live browser smoke — prior impl
2153
- showed only the browser's broken-image icon with no readable alt. */
2154
- chat-streaming-md .chat-md-image--broken {
2155
- display: inline-flex;
2156
- align-items: center;
2157
- gap: 0.4rem;
2158
- padding: 0.25rem 0.5rem;
2159
- background: var(--tplane-chat-surface-alt);
2160
- border: 1px dashed var(--tplane-chat-separator);
2161
- border-radius: 6px;
2162
- font-size: 0.9em;
2163
- color: var(--tplane-chat-text-muted, currentColor);
2164
- opacity: 0.85;
2165
- }
2166
- chat-streaming-md .chat-md-image__icon { font-size: 1em; line-height: 1; }
2167
- chat-streaming-md .chat-md-image__alt { font-style: italic; }
2168
- /* Math (KaTeX). Display math is a centered block that scrolls horizontally
2169
- on overflow; the raw fallback (KaTeX missing or invalid LaTeX) reads as
2170
- monospace so the source delimiters look intentional. */
2171
- chat-streaming-md .chat-md-math--display { display: block; margin: 0.5em 0; overflow-x: auto; }
2172
- chat-streaming-md .chat-md-math--raw { font-family: var(--tplane-chat-font-mono, ui-monospace, monospace); }
2173
- `;
2174
-
2175
2375
  // libs/chat/src/lib/markdown/markdown-view-registry.ts
2176
2376
  // SPDX-License-Identifier: MIT
2177
2377
  /**
@@ -3053,23 +3253,83 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
3053
3253
  */
3054
3254
  class ChatCitationPreviewComponent {
3055
3255
  citation = input.required(...(ngDevMode ? [{ debugName: "citation" }] : []));
3256
+ sourceVisual = computed(() => citationSourceVisual(this.citation()), ...(ngDevMode ? [{ debugName: "sourceVisual" }] : []));
3257
+ typeMeta = computed(() => citationTypeMeta(this.citation()), ...(ngDevMode ? [{ debugName: "typeMeta" }] : []));
3056
3258
  domain = computed(() => deriveDomain(this.citation().url), ...(ngDevMode ? [{ debugName: "domain" }] : []));
3057
- monogram = computed(() => deriveMonogram(this.citation()), ...(ngDevMode ? [{ debugName: "monogram" }] : []));
3058
- monoColor = computed(() => monogramColor(this.citation()), ...(ngDevMode ? [{ debugName: "monoColor" }] : []));
3059
- typeLabel = computed(() => citationTypeLabel(this.citation()), ...(ngDevMode ? [{ debugName: "typeLabel" }] : []));
3259
+ sourceIconUrl = computed(() => {
3260
+ const visual = this.sourceVisual();
3261
+ return visual.kind === 'image' ? visual.iconUrl : null;
3262
+ }, ...(ngDevMode ? [{ debugName: "sourceIconUrl" }] : []));
3263
+ sourceIcon = computed(() => {
3264
+ const visual = this.sourceVisual();
3265
+ return visual.kind === 'type-icon' ? visual.icon : null;
3266
+ }, ...(ngDevMode ? [{ debugName: "sourceIcon" }] : []));
3267
+ sourceMonogram = computed(() => {
3268
+ const visual = this.sourceVisual();
3269
+ return visual.kind === 'monogram' ? visual.monogram : null;
3270
+ }, ...(ngDevMode ? [{ debugName: "sourceMonogram" }] : []));
3271
+ sourceMonoColor = computed(() => {
3272
+ const visual = this.sourceVisual();
3273
+ return visual.kind === 'monogram' ? visual.color : null;
3274
+ }, ...(ngDevMode ? [{ debugName: "sourceMonoColor" }] : []));
3275
+ typeLabel = computed(() => this.typeMeta().label, ...(ngDevMode ? [{ debugName: "typeLabel" }] : []));
3276
+ typeTone = computed(() => this.typeMeta().tone, ...(ngDevMode ? [{ debugName: "typeTone" }] : []));
3060
3277
  published = computed(() => formatPublished(this.citation().publishedAt), ...(ngDevMode ? [{ debugName: "published" }] : []));
3278
+ isTypeTone(tone) {
3279
+ return this.typeTone() === tone;
3280
+ }
3061
3281
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3062
3282
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatCitationPreviewComponent, isStandalone: true, selector: "chat-citation-preview", inputs: { citation: { classPropertyName: "citation", publicName: "citation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
3063
3283
  <div class="chat-citation-preview" role="group" [attr.aria-label]="'Source ' + citation().index">
3064
3284
  <div class="chat-citation-preview__head">
3065
- @if (citation().iconUrl; as icon) {
3285
+ @if (sourceIconUrl(); as icon) {
3066
3286
  <img class="chat-citation-preview__fav" [src]="icon" alt="" width="16" height="16" />
3287
+ } @else if (sourceIcon(); as icon) {
3288
+ <span class="chat-citation-source-icon chat-citation-preview__source-icon"
3289
+ aria-hidden="true"
3290
+ [class.chat-citation-source-icon--web]="icon === 'web'"
3291
+ [class.chat-citation-source-icon--file]="icon === 'file'"
3292
+ [class.chat-citation-source-icon--app]="icon === 'app'"
3293
+ [class.chat-citation-source-icon--memory]="icon === 'memory'"
3294
+ [class.chat-citation-source-icon--generic]="icon === 'generic'">
3295
+ <svg viewBox="0 0 24 24" aria-hidden="true">
3296
+ @switch (icon) {
3297
+ @case ('file') {
3298
+ <path d="M7 3.5h6.5L18 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
3299
+ <path d="M13.5 3.5V8H18M9.5 13h5M9.5 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3300
+ }
3301
+ @case ('app') {
3302
+ <rect x="4" y="5" width="16" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8" />
3303
+ <path d="M8 9h.01M11.5 9h.01M15 9h.01M8 12.5h8M8 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3304
+ }
3305
+ @case ('memory') {
3306
+ <path d="M8 7.5a4 4 0 0 1 8 0v9a4 4 0 0 1-8 0z" fill="none" stroke="currentColor" stroke-width="1.8" />
3307
+ <path d="M12 4v16M8 10h8M8 14h8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3308
+ }
3309
+ @case ('web') {
3310
+ <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.8" />
3311
+ <path d="M4.5 12h15M12 4a12 12 0 0 1 0 16M12 4a12 12 0 0 0 0 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3312
+ }
3313
+ @default {
3314
+ <path d="M6 5.5h12v13H6z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
3315
+ <path d="M9 9h6M9 12h6M9 15h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3316
+ }
3317
+ }
3318
+ </svg>
3319
+ </span>
3067
3320
  } @else {
3068
3321
  <span class="chat-citation-preview__fav chat-citation-preview__fav--mono"
3069
- [style.background]="monoColor()">{{ monogram() }}</span>
3322
+ [style.background]="sourceMonoColor()">{{ sourceMonogram() }}</span>
3070
3323
  }
3071
3324
  @if (domain(); as d) { <span class="chat-citation-preview__domain">{{ d }}</span> }
3072
- @if (typeLabel(); as t) { <span class="chat-citation-preview__type">{{ t }}</span> }
3325
+ @if (typeLabel(); as t) {
3326
+ <span class="chat-citation-preview__type chat-citation-type-badge"
3327
+ [class.chat-citation-type-badge--web]="isTypeTone('web')"
3328
+ [class.chat-citation-type-badge--file]="isTypeTone('file')"
3329
+ [class.chat-citation-type-badge--app]="isTypeTone('app')"
3330
+ [class.chat-citation-type-badge--memory]="isTypeTone('memory')"
3331
+ [class.chat-citation-type-badge--generic]="isTypeTone('generic')">{{ t }}</span>
3332
+ }
3073
3333
  </div>
3074
3334
  @if (citation().title; as title) {
3075
3335
  <p class="chat-citation-preview__title">{{ title }}</p>
@@ -3089,21 +3349,61 @@ class ChatCitationPreviewComponent {
3089
3349
  </div>
3090
3350
  }
3091
3351
  </div>
3092
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citation-preview{width:320px;max-width:calc(100vw - 24px);box-sizing:border-box;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);box-shadow:var(--tplane-chat-shadow-md);padding:12px 13px;text-align:left;color:var(--tplane-chat-text)}.chat-citation-preview__head{display:flex;align-items:center;gap:7px;margin-bottom:7px}.chat-citation-preview__fav{width:16px;height:16px;border-radius:4px;flex:0 0 auto;object-fit:cover}.chat-citation-preview__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:700}.chat-citation-preview__domain{font-size:12px;color:var(--tplane-chat-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citation-preview__type{margin-left:auto;font-size:11px;color:var(--tplane-chat-text-muted);flex:0 0 auto}.chat-citation-preview__title{font-size:14px;font-weight:600;line-height:1.35;margin:0 0 5px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__snippet{font-size:12.5px;color:var(--tplane-chat-text-muted);line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__foot{display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:9px;border-top:1px solid var(--tplane-chat-separator)}.chat-citation-preview__open{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--tplane-chat-citation-accent);text-decoration:none}.chat-citation-preview__open:hover{text-decoration:underline}.chat-citation-preview__meta{font-size:11px;color:var(--tplane-chat-text-muted)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3352
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ".chat-citation-source-icon{width:18px;height:18px;flex:0 0 auto;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border-radius:5px;border:1px solid currentColor}.chat-citation-source-icon svg{width:12px;height:12px;display:block}.chat-citation-source-icon--sm{width:14px;height:14px;border-radius:4px}.chat-citation-source-icon--sm svg{width:9px;height:9px}.chat-citation-source-icon--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-source-icon--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-source-icon--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-source-icon--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-source-icon--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}.chat-citation-type-badge{display:inline-flex;align-items:center;justify-content:center;min-width:0;max-width:120px;box-sizing:border-box;padding:1px 6px;border:1px solid currentColor;border-radius:999px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.chat-citation-type-badge--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-type-badge--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-type-badge--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-type-badge--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-type-badge--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}:host{display:block}.chat-citation-preview{width:320px;max-width:calc(100vw - 24px);box-sizing:border-box;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);box-shadow:var(--tplane-chat-shadow-md);padding:12px 13px;text-align:left;color:var(--tplane-chat-text)}.chat-citation-preview__head{display:flex;align-items:center;gap:7px;margin-bottom:7px}.chat-citation-preview__fav{width:16px;height:16px;border-radius:4px;flex:0 0 auto;object-fit:cover}.chat-citation-preview__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:700}.chat-citation-preview__domain{font-size:12px;color:var(--tplane-chat-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citation-preview__type{margin-left:auto;font-size:11px;flex:0 0 auto}.chat-citation-preview__title{font-size:14px;font-weight:600;line-height:1.35;margin:0 0 5px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__snippet{font-size:12.5px;color:var(--tplane-chat-text-muted);line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__foot{display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:9px;border-top:1px solid var(--tplane-chat-separator)}.chat-citation-preview__open{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--tplane-chat-citation-accent);text-decoration:none}.chat-citation-preview__open:hover{text-decoration:underline}.chat-citation-preview__meta{font-size:11px;color:var(--tplane-chat-text-muted)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3093
3353
  }
3094
3354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatCitationPreviewComponent, decorators: [{
3095
3355
  type: Component,
3096
3356
  args: [{ selector: 'chat-citation-preview', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
3097
3357
  <div class="chat-citation-preview" role="group" [attr.aria-label]="'Source ' + citation().index">
3098
3358
  <div class="chat-citation-preview__head">
3099
- @if (citation().iconUrl; as icon) {
3359
+ @if (sourceIconUrl(); as icon) {
3100
3360
  <img class="chat-citation-preview__fav" [src]="icon" alt="" width="16" height="16" />
3361
+ } @else if (sourceIcon(); as icon) {
3362
+ <span class="chat-citation-source-icon chat-citation-preview__source-icon"
3363
+ aria-hidden="true"
3364
+ [class.chat-citation-source-icon--web]="icon === 'web'"
3365
+ [class.chat-citation-source-icon--file]="icon === 'file'"
3366
+ [class.chat-citation-source-icon--app]="icon === 'app'"
3367
+ [class.chat-citation-source-icon--memory]="icon === 'memory'"
3368
+ [class.chat-citation-source-icon--generic]="icon === 'generic'">
3369
+ <svg viewBox="0 0 24 24" aria-hidden="true">
3370
+ @switch (icon) {
3371
+ @case ('file') {
3372
+ <path d="M7 3.5h6.5L18 8v12.5H7z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
3373
+ <path d="M13.5 3.5V8H18M9.5 13h5M9.5 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3374
+ }
3375
+ @case ('app') {
3376
+ <rect x="4" y="5" width="16" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8" />
3377
+ <path d="M8 9h.01M11.5 9h.01M15 9h.01M8 12.5h8M8 16h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3378
+ }
3379
+ @case ('memory') {
3380
+ <path d="M8 7.5a4 4 0 0 1 8 0v9a4 4 0 0 1-8 0z" fill="none" stroke="currentColor" stroke-width="1.8" />
3381
+ <path d="M12 4v16M8 10h8M8 14h8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3382
+ }
3383
+ @case ('web') {
3384
+ <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.8" />
3385
+ <path d="M4.5 12h15M12 4a12 12 0 0 1 0 16M12 4a12 12 0 0 0 0 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3386
+ }
3387
+ @default {
3388
+ <path d="M6 5.5h12v13H6z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
3389
+ <path d="M9 9h6M9 12h6M9 15h3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
3390
+ }
3391
+ }
3392
+ </svg>
3393
+ </span>
3101
3394
  } @else {
3102
3395
  <span class="chat-citation-preview__fav chat-citation-preview__fav--mono"
3103
- [style.background]="monoColor()">{{ monogram() }}</span>
3396
+ [style.background]="sourceMonoColor()">{{ sourceMonogram() }}</span>
3104
3397
  }
3105
3398
  @if (domain(); as d) { <span class="chat-citation-preview__domain">{{ d }}</span> }
3106
- @if (typeLabel(); as t) { <span class="chat-citation-preview__type">{{ t }}</span> }
3399
+ @if (typeLabel(); as t) {
3400
+ <span class="chat-citation-preview__type chat-citation-type-badge"
3401
+ [class.chat-citation-type-badge--web]="isTypeTone('web')"
3402
+ [class.chat-citation-type-badge--file]="isTypeTone('file')"
3403
+ [class.chat-citation-type-badge--app]="isTypeTone('app')"
3404
+ [class.chat-citation-type-badge--memory]="isTypeTone('memory')"
3405
+ [class.chat-citation-type-badge--generic]="isTypeTone('generic')">{{ t }}</span>
3406
+ }
3107
3407
  </div>
3108
3408
  @if (citation().title; as title) {
3109
3409
  <p class="chat-citation-preview__title">{{ title }}</p>
@@ -3123,7 +3423,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
3123
3423
  </div>
3124
3424
  }
3125
3425
  </div>
3126
- `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.chat-citation-preview{width:320px;max-width:calc(100vw - 24px);box-sizing:border-box;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);box-shadow:var(--tplane-chat-shadow-md);padding:12px 13px;text-align:left;color:var(--tplane-chat-text)}.chat-citation-preview__head{display:flex;align-items:center;gap:7px;margin-bottom:7px}.chat-citation-preview__fav{width:16px;height:16px;border-radius:4px;flex:0 0 auto;object-fit:cover}.chat-citation-preview__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:700}.chat-citation-preview__domain{font-size:12px;color:var(--tplane-chat-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citation-preview__type{margin-left:auto;font-size:11px;color:var(--tplane-chat-text-muted);flex:0 0 auto}.chat-citation-preview__title{font-size:14px;font-weight:600;line-height:1.35;margin:0 0 5px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__snippet{font-size:12.5px;color:var(--tplane-chat-text-muted);line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__foot{display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:9px;border-top:1px solid var(--tplane-chat-separator)}.chat-citation-preview__open{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--tplane-chat-citation-accent);text-decoration:none}.chat-citation-preview__open:hover{text-decoration:underline}.chat-citation-preview__meta{font-size:11px;color:var(--tplane-chat-text-muted)}\n"] }]
3426
+ `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ".chat-citation-source-icon{width:18px;height:18px;flex:0 0 auto;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;border-radius:5px;border:1px solid currentColor}.chat-citation-source-icon svg{width:12px;height:12px;display:block}.chat-citation-source-icon--sm{width:14px;height:14px;border-radius:4px}.chat-citation-source-icon--sm svg{width:9px;height:9px}.chat-citation-source-icon--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-source-icon--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-source-icon--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-source-icon--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-source-icon--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}.chat-citation-type-badge{display:inline-flex;align-items:center;justify-content:center;min-width:0;max-width:120px;box-sizing:border-box;padding:1px 6px;border:1px solid currentColor;border-radius:999px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.chat-citation-type-badge--web{color:var(--tplane-chat-citation-type-web-fg);background:var(--tplane-chat-citation-type-web-bg);border-color:var(--tplane-chat-citation-type-web-border)}.chat-citation-type-badge--file{color:var(--tplane-chat-citation-type-file-fg);background:var(--tplane-chat-citation-type-file-bg);border-color:var(--tplane-chat-citation-type-file-border)}.chat-citation-type-badge--app{color:var(--tplane-chat-citation-type-app-fg);background:var(--tplane-chat-citation-type-app-bg);border-color:var(--tplane-chat-citation-type-app-border)}.chat-citation-type-badge--memory{color:var(--tplane-chat-citation-type-memory-fg);background:var(--tplane-chat-citation-type-memory-bg);border-color:var(--tplane-chat-citation-type-memory-border)}.chat-citation-type-badge--generic{color:var(--tplane-chat-citation-type-generic-fg);background:var(--tplane-chat-citation-type-generic-bg);border-color:var(--tplane-chat-citation-type-generic-border)}:host{display:block}.chat-citation-preview{width:320px;max-width:calc(100vw - 24px);box-sizing:border-box;background:var(--tplane-chat-surface);border:1px solid var(--tplane-chat-separator);border-radius:var(--tplane-chat-radius-card);box-shadow:var(--tplane-chat-shadow-md);padding:12px 13px;text-align:left;color:var(--tplane-chat-text)}.chat-citation-preview__head{display:flex;align-items:center;gap:7px;margin-bottom:7px}.chat-citation-preview__fav{width:16px;height:16px;border-radius:4px;flex:0 0 auto;object-fit:cover}.chat-citation-preview__fav--mono{display:flex;align-items:center;justify-content:center;color:#fff;font-size:10px;font-weight:700}.chat-citation-preview__domain{font-size:12px;color:var(--tplane-chat-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-citation-preview__type{margin-left:auto;font-size:11px;flex:0 0 auto}.chat-citation-preview__title{font-size:14px;font-weight:600;line-height:1.35;margin:0 0 5px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__snippet{font-size:12.5px;color:var(--tplane-chat-text-muted);line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-citation-preview__foot{display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:9px;border-top:1px solid var(--tplane-chat-separator)}.chat-citation-preview__open{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--tplane-chat-citation-accent);text-decoration:none}.chat-citation-preview__open:hover{text-decoration:underline}.chat-citation-preview__meta{font-size:11px;color:var(--tplane-chat-text-muted)}\n"] }]
3127
3427
  }], propDecorators: { citation: [{ type: i0.Input, args: [{ isSignal: true, alias: "citation", required: true }] }] } });
3128
3428
 
3129
3429
  // libs/chat/src/lib/markdown/views/markdown-citation-reference.component.ts
@@ -3555,163 +3855,328 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
3555
3855
  }]
3556
3856
  }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
3557
3857
 
3558
- // libs/chat/src/lib/markdown/cacheplane-markdown-views.ts
3559
- // SPDX-License-Identifier: MIT
3560
- /**
3561
- * Default view registry consumed by <chat-streaming-md>. Maps every
3562
- * MarkdownNode.type emitted by @cacheplane/partial-markdown@0.2 to its
3563
- * corresponding Angular component.
3564
- *
3565
- * Override per-node-type via `withViews(cacheplaneMarkdownViews, { … })`.
3566
- */
3567
- const cacheplaneMarkdownViews = views({
3568
- 'document': MarkdownDocumentComponent,
3569
- 'paragraph': MarkdownParagraphComponent,
3570
- 'heading': MarkdownHeadingComponent,
3571
- 'blockquote': MarkdownBlockquoteComponent,
3572
- 'list': MarkdownListComponent,
3573
- 'list-item': MarkdownListItemComponent,
3574
- 'code-block': MarkdownCodeBlockComponent,
3575
- 'thematic-break': MarkdownThematicBreakComponent,
3576
- 'text': MarkdownTextComponent,
3577
- 'emphasis': MarkdownEmphasisComponent,
3578
- 'strong': MarkdownStrongComponent,
3579
- 'strikethrough': MarkdownStrikethroughComponent,
3580
- 'inline-code': MarkdownInlineCodeComponent,
3581
- 'math-inline': MarkdownMathComponent,
3582
- 'math-display': MarkdownMathComponent,
3583
- 'link': MarkdownLinkComponent,
3584
- 'autolink': MarkdownAutolinkComponent,
3585
- 'image': MarkdownImageComponent,
3586
- 'soft-break': MarkdownSoftBreakComponent,
3587
- 'hard-break': MarkdownHardBreakComponent,
3588
- 'citation-reference': MarkdownCitationReferenceComponent,
3589
- 'table': MarkdownTableComponent,
3590
- 'table-row': MarkdownTableRowComponent,
3591
- 'table-cell': MarkdownTableCellComponent,
3592
- // Raw HTML (added by partial-markdown 0.4.x) renders as escaped literal text
3593
- // — XSS-safe, and matches the pre-0.4 behavior where HTML was plain text.
3594
- 'html-block': MarkdownHtmlComponent,
3595
- 'html-inline': MarkdownHtmlComponent,
3596
- });
3858
+ // libs/chat/src/lib/markdown/cacheplane-markdown-views.ts
3859
+ // SPDX-License-Identifier: MIT
3860
+ /**
3861
+ * Default view registry consumed by <chat-streaming-md>. Maps every
3862
+ * MarkdownNode.type emitted by @cacheplane/partial-markdown@0.2 to its
3863
+ * corresponding Angular component.
3864
+ *
3865
+ * Override per-node-type via `withViews(cacheplaneMarkdownViews, { … })`.
3866
+ */
3867
+ const cacheplaneMarkdownViews = views({
3868
+ 'document': MarkdownDocumentComponent,
3869
+ 'paragraph': MarkdownParagraphComponent,
3870
+ 'heading': MarkdownHeadingComponent,
3871
+ 'blockquote': MarkdownBlockquoteComponent,
3872
+ 'list': MarkdownListComponent,
3873
+ 'list-item': MarkdownListItemComponent,
3874
+ 'code-block': MarkdownCodeBlockComponent,
3875
+ 'thematic-break': MarkdownThematicBreakComponent,
3876
+ 'text': MarkdownTextComponent,
3877
+ 'emphasis': MarkdownEmphasisComponent,
3878
+ 'strong': MarkdownStrongComponent,
3879
+ 'strikethrough': MarkdownStrikethroughComponent,
3880
+ 'inline-code': MarkdownInlineCodeComponent,
3881
+ 'math-inline': MarkdownMathComponent,
3882
+ 'math-display': MarkdownMathComponent,
3883
+ 'link': MarkdownLinkComponent,
3884
+ 'autolink': MarkdownAutolinkComponent,
3885
+ 'image': MarkdownImageComponent,
3886
+ 'soft-break': MarkdownSoftBreakComponent,
3887
+ 'hard-break': MarkdownHardBreakComponent,
3888
+ 'citation-reference': MarkdownCitationReferenceComponent,
3889
+ 'table': MarkdownTableComponent,
3890
+ 'table-row': MarkdownTableRowComponent,
3891
+ 'table-cell': MarkdownTableCellComponent,
3892
+ // Raw HTML (added by partial-markdown 0.4.x) renders as escaped literal text
3893
+ // — XSS-safe, and matches the pre-0.4 behavior where HTML was plain text.
3894
+ 'html-block': MarkdownHtmlComponent,
3895
+ 'html-inline': MarkdownHtmlComponent,
3896
+ });
3897
+
3898
+ // libs/chat/src/lib/styles/chat-markdown.styles.ts
3899
+ // SPDX-License-Identifier: MIT
3900
+ //
3901
+ // Scoped to the `chat-streaming-md` element selector (not `:host`) because
3902
+ // the component renders markdown via innerHTML and uses
3903
+ // `ViewEncapsulation.None` — emulated encapsulation would skip these rules
3904
+ // since innerHTML-injected nodes don't carry `_ngcontent-xxx` attributes.
3905
+ // Keeping selectors prefixed with the element name preserves locality so
3906
+ // these rules don't leak to other markup on the page.
3907
+ //
3908
+ // Covers the CommonMark + GFM surface: headings, paragraphs, links, lists
3909
+ // (bullet/ordered/task), code (inline + fenced), blockquotes, horizontal
3910
+ // rules, tables, images, bold (`strong`), italic (`em`), strikethrough
3911
+ // (`del`/`s`).
3912
+ const CHAT_MARKDOWN_STYLES = `
3913
+ chat-streaming-md { display: block; color: var(--tplane-chat-text); line-height: var(--tplane-chat-line-height); }
3914
+
3915
+ /* Headings */
3916
+ chat-streaming-md h1, chat-streaming-md h2, chat-streaming-md h3, chat-streaming-md h4, chat-streaming-md h5, chat-streaming-md h6 {
3917
+ font-weight: 600;
3918
+ line-height: 1.25;
3919
+ margin: 1.25rem 0 0.75rem;
3920
+ }
3921
+ chat-streaming-md h1:first-child, chat-streaming-md h2:first-child, chat-streaming-md h3:first-child,
3922
+ chat-streaming-md h4:first-child, chat-streaming-md h5:first-child, chat-streaming-md h6:first-child { margin-top: 0; }
3923
+ chat-streaming-md h1 { font-size: 1.5em; font-weight: 700; }
3924
+ chat-streaming-md h2 { font-size: 1.25em; }
3925
+ chat-streaming-md h3 { font-size: 1.1em; }
3926
+ chat-streaming-md h4 { font-size: 1em; }
3927
+ chat-streaming-md h5, chat-streaming-md h6 { font-size: 0.95em; color: var(--tplane-chat-text-muted); }
3928
+
3929
+ /* Paragraphs and inline emphasis */
3930
+ chat-streaming-md p { margin: 0 0 0.75rem; line-height: 1.6; font-size: var(--tplane-chat-font-size); }
3931
+ chat-streaming-md p:last-child { margin-bottom: 0; }
3932
+ chat-streaming-md strong, chat-streaming-md b { font-weight: 700; }
3933
+ chat-streaming-md em, chat-streaming-md i { font-style: italic; }
3934
+ chat-streaming-md del, chat-streaming-md s { text-decoration: line-through; color: var(--tplane-chat-text-muted); }
3935
+ chat-streaming-md mark { background: var(--tplane-chat-surface-alt); padding: 0 2px; border-radius: 2px; }
3936
+ chat-streaming-md sub { font-size: 0.75em; vertical-align: sub; }
3937
+ chat-streaming-md sup { font-size: 0.75em; vertical-align: super; }
3938
+
3939
+ /* Links */
3940
+ chat-streaming-md a { color: var(--tplane-chat-primary); text-decoration: underline; text-underline-offset: 2px; }
3941
+ chat-streaming-md a:hover { text-decoration-thickness: 2px; }
3942
+
3943
+ /* Lists (CommonMark + GFM task lists) */
3944
+ chat-streaming-md ul, chat-streaming-md ol { margin: 0 0 0.75rem; padding-left: 1.5rem; }
3945
+ chat-streaming-md ul { list-style: disc outside; }
3946
+ chat-streaming-md ol { list-style: decimal outside; }
3947
+ chat-streaming-md ul ul { list-style: circle outside; }
3948
+ chat-streaming-md ul ul ul { list-style: square outside; }
3949
+ chat-streaming-md li { margin: 0.2rem 0; }
3950
+ chat-streaming-md li::marker { color: var(--tplane-chat-text-muted); }
3951
+ chat-streaming-md li > p { margin: 0 0 0.25rem; }
3952
+ chat-streaming-md li > ul, chat-streaming-md li > ol { margin: 0.25rem 0 0; }
3953
+ /* GFM task lists: marked emits <li><input type="checkbox" disabled> ... */
3954
+ chat-streaming-md li:has(> input[type="checkbox"]) { list-style: none; margin-left: -1.25rem; }
3955
+ chat-streaming-md li > input[type="checkbox"] { margin-right: 0.5rem; vertical-align: middle; }
3956
+
3957
+ /* Code (inline + fenced) */
3958
+ chat-streaming-md code {
3959
+ background: var(--tplane-chat-surface-alt);
3960
+ color: var(--tplane-chat-text);
3961
+ padding: 1px 5px;
3962
+ border-radius: 4px;
3963
+ font-family: var(--tplane-chat-font-mono);
3964
+ font-size: 0.9em;
3965
+ }
3966
+ chat-streaming-md pre {
3967
+ background: var(--tplane-chat-surface-alt);
3968
+ color: var(--tplane-chat-text);
3969
+ padding: 12px 14px;
3970
+ border-radius: var(--tplane-chat-radius-card);
3971
+ overflow-x: auto;
3972
+ font-family: var(--tplane-chat-font-mono);
3973
+ font-size: var(--tplane-chat-font-size-sm);
3974
+ line-height: 1.5;
3975
+ margin: 0 0 0.75rem;
3976
+ }
3977
+ chat-streaming-md pre code { background: transparent; padding: 0; border-radius: 0; font-size: inherit; }
3978
+
3979
+ /* Blockquote */
3980
+ chat-streaming-md blockquote {
3981
+ border-left: 3px solid var(--tplane-chat-separator);
3982
+ padding: 0.25rem 0 0.25rem 12px;
3983
+ margin: 0 0 0.75rem;
3984
+ color: var(--tplane-chat-text-muted);
3985
+ }
3986
+ chat-streaming-md blockquote > :last-child { margin-bottom: 0; }
3987
+
3988
+ /* Horizontal rule */
3989
+ chat-streaming-md hr {
3990
+ border: none;
3991
+ border-top: 1px solid var(--tplane-chat-separator);
3992
+ margin: 1rem 0;
3993
+ }
3994
+
3995
+ /* Tables (GFM) */
3996
+ chat-streaming-md table {
3997
+ border-collapse: collapse;
3998
+ margin: 0 0 0.75rem;
3999
+ width: 100%;
4000
+ font-size: 0.95em;
4001
+ }
4002
+ chat-streaming-md thead { background: var(--tplane-chat-surface-alt); }
4003
+ chat-streaming-md th, chat-streaming-md td {
4004
+ border: 1px solid var(--tplane-chat-separator);
4005
+ padding: 6px 10px;
4006
+ text-align: left;
4007
+ vertical-align: top;
4008
+ }
4009
+ chat-streaming-md th { font-weight: 600; }
4010
+ /* Component-rendered table: chat-md-table becomes a horizontally-scrollable
4011
+ wrapper for the inner <table>; row/cell elements stay layout-transparent
4012
+ so the browser's table layout takes over. Without this overflow wrapper,
4013
+ wide tables push their parent container past the viewport horizontally. */
4014
+ chat-streaming-md chat-md-table {
4015
+ display: block;
4016
+ overflow-x: auto;
4017
+ max-width: 100%;
4018
+ margin: 0 0 0.75rem;
4019
+ }
4020
+ chat-streaming-md chat-md-table-row { display: contents; }
4021
+ chat-streaming-md chat-md-table-cell { display: contents; }
4022
+ chat-streaming-md chat-md-table > table { margin: 0; }
4023
+ /* Task-list items: checkbox + first paragraph render inline; subsequent
4024
+ blocks (sub-lists, multi-paragraph items) flow normally below. */
4025
+ chat-streaming-md li.chat-md-list-item--task {
4026
+ list-style: none;
4027
+ margin-left: -1.25rem;
4028
+ display: flex;
4029
+ flex-wrap: wrap;
4030
+ align-items: baseline;
4031
+ gap: 0.5rem;
4032
+ }
4033
+ chat-streaming-md li.chat-md-list-item--task > input[type="checkbox"] {
4034
+ margin: 0;
4035
+ flex: 0 0 auto;
4036
+ transform: translateY(2px);
4037
+ }
4038
+ /* The chat-md-children wrapper around list-item content takes remaining width */
4039
+ chat-streaming-md li.chat-md-list-item--task > chat-md-children {
4040
+ flex: 1 1 auto;
4041
+ min-width: 0;
4042
+ }
4043
+ /* Tight task items: only the FIRST paragraph aligns inline with the
4044
+ checkbox (margin collapsed). Subsequent paragraphs/blocks keep their
4045
+ normal vertical spacing so multi-block items render readably. */
4046
+ chat-streaming-md li.chat-md-list-item--task > chat-md-children > chat-md-paragraph:first-child > p {
4047
+ margin: 0;
4048
+ }
4049
+
4050
+ /* Media */
4051
+ chat-streaming-md img { max-width: 100%; height: auto; border-radius: 6px; }
4052
+ /* Broken-image fallback: muted pill showing alt text + icon. Triggered
4053
+ when <img> fires (error). Caught by live browser smoke — prior impl
4054
+ showed only the browser's broken-image icon with no readable alt. */
4055
+ chat-streaming-md .chat-md-image--broken {
4056
+ display: inline-flex;
4057
+ align-items: center;
4058
+ gap: 0.4rem;
4059
+ padding: 0.25rem 0.5rem;
4060
+ background: var(--tplane-chat-surface-alt);
4061
+ border: 1px dashed var(--tplane-chat-separator);
4062
+ border-radius: 6px;
4063
+ font-size: 0.9em;
4064
+ color: var(--tplane-chat-text-muted, currentColor);
4065
+ opacity: 0.85;
4066
+ }
4067
+ chat-streaming-md .chat-md-image__icon { font-size: 1em; line-height: 1; }
4068
+ chat-streaming-md .chat-md-image__alt { font-style: italic; }
4069
+ /* Math (KaTeX). Display math is a centered block that scrolls horizontally
4070
+ on overflow; the raw fallback (KaTeX missing or invalid LaTeX) reads as
4071
+ monospace so the source delimiters look intentional. */
4072
+ chat-streaming-md .chat-md-math--display { display: block; margin: 0.5em 0; overflow-x: auto; }
4073
+ chat-streaming-md .chat-md-math--raw { font-family: var(--tplane-chat-font-mono, ui-monospace, monospace); }
4074
+ `;
3597
4075
 
3598
4076
  // libs/chat/src/lib/streaming/streaming-markdown.component.ts
3599
4077
  // SPDX-License-Identifier: MIT
3600
- // How long streaming must be false AND content stable before we finalize the
3601
- // parser. finish() is only needed to mark final node status (not used visually)
3602
- // and to revert a genuinely-truncated trailing construct to CommonMark — the
3603
- // live `parser.root` projection already renders everything during streaming, so
3604
- // this delay has NO visual cost. It must comfortably exceed real inter-chunk
3605
- // gaps (e.g. the pause between a table's header row and its delimiter row) and
3606
- // any `streaming` flag flap, so finalize never fires mid-stream and reverts an
3607
- // in-progress table to raw "| a | b |" text.
3608
- const FINALIZE_DEBOUNCE_MS = 600;
4078
+ /** Creates the atomic document consumed by the streaming Markdown renderer. */
4079
+ function markdownDocument(content, delivery, suffix = '') {
4080
+ return {
4081
+ generation: delivery.generation + suffix,
4082
+ phase: delivery.phase,
4083
+ content,
4084
+ };
4085
+ }
4086
+ const STREAMING_MARKDOWN_CONTRACT_VIOLATION_POLICY = new InjectionToken('STREAMING_MARKDOWN_CONTRACT_VIOLATION_POLICY', {
4087
+ providedIn: 'root',
4088
+ factory: () => (isDevMode() ? 'throw' : 'rebuild'),
4089
+ });
4090
+ /** @internal Test seam for verifying parser lifecycle ordering. */
4091
+ const STREAMING_MARKDOWN_PARSER_FACTORY = new InjectionToken('STREAMING_MARKDOWN_PARSER_FACTORY', {
4092
+ providedIn: 'root',
4093
+ factory: () => createPartialMarkdownParser,
4094
+ });
4095
+ function contractViolation(prior, supplied, reason) {
4096
+ return new Error(`Streaming markdown document contract violation: ${reason}; ` +
4097
+ `generation ${JSON.stringify(supplied.generation)} cannot transition ` +
4098
+ `from ${prior.phase} content of length ${prior.content.length} ` +
4099
+ `to ${supplied.phase} content of length ${supplied.content.length}.`);
4100
+ }
4101
+ function contractViolationReason(prior, supplied) {
4102
+ if (prior.phase === 'complete') {
4103
+ return supplied.phase === 'streaming'
4104
+ ? 'complete-to-streaming'
4105
+ : 'post-completion-content-mutation';
4106
+ }
4107
+ if (supplied.content.length < prior.content.length)
4108
+ return 'content-shrink';
4109
+ if (!supplied.content.startsWith(prior.content))
4110
+ return 'content-divergence';
4111
+ return null;
4112
+ }
3609
4113
  /**
3610
- * Renders streaming markdown by walking a @cacheplane/partial-markdown AST
3611
- * through @threadplane/render's view registry.
3612
- *
3613
- * Reactivity model: the live `parser.root` keeps a stable JS reference
3614
- * across pushes (partial-markdown's identity guarantee). To make Angular
3615
- * signals propagate downstream when the underlying tree changes, we surface
3616
- * a materialized snapshot via `materialize()`. The snapshot shares
3617
- * structurally — unchanged subtrees keep the SAME reference, and any
3618
- * descendant change yields a NEW root reference. This lets Angular's
3619
- * `Object.is` equality check both detect changes (root reference differs)
3620
- * and short-circuit unchanged subtrees (per-node references stable).
3621
- *
3622
- * Override per-node-type renderers via the `[viewRegistry]` input or by
3623
- * supplying a different `MARKDOWN_VIEW_REGISTRY` provider in the injector
3624
- * tree.
4114
+ * Renders one explicitly-versioned markdown document through the shared view
4115
+ * registry. A generation owns one parser session; append-only updates preserve
4116
+ * parser and subtree identity, while generation changes replace the session.
3625
4117
  */
3626
4118
  class ChatStreamingMdComponent {
3627
- content = input('', ...(ngDevMode ? [{ debugName: "content" }] : []));
3628
- streaming = input(false, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
4119
+ document = input.required(...(ngDevMode ? [{ debugName: "document" }] : []));
3629
4120
  viewRegistry = input(undefined, ...(ngDevMode ? [{ debugName: "viewRegistry" }] : []));
3630
4121
  resolvedRegistry = computed(() => this.viewRegistry() ?? cacheplaneMarkdownViews, ...(ngDevMode ? [{ debugName: "resolvedRegistry" }] : []));
3631
- resolver = inject(CitationsResolverService, { optional: true });
4122
+ resolver = inject(CitationsResolverService, {
4123
+ optional: true,
4124
+ });
4125
+ violationPolicy = inject(STREAMING_MARKDOWN_CONTRACT_VIOLATION_POLICY);
4126
+ createParser = inject(STREAMING_MARKDOWN_PARSER_FACTORY);
4127
+ parser = null;
4128
+ prior = null;
4129
+ materializedRoot = null;
4130
+ root = computed(() => {
4131
+ this.process(this.document());
4132
+ return this.materializedRoot;
4133
+ }, ...(ngDevMode ? [{ debugName: "root" }] : []));
3632
4134
  constructor() {
3633
4135
  effect(() => {
3634
- const r = this.root();
3635
- if (this.resolver && r) {
3636
- this.resolver.markdownDefs.set(r.citations ?? new Map());
3637
- }
3638
- });
3639
- // Debounced finalization. `finish()` is terminal and DESTRUCTIVE: it reverts
3640
- // an incomplete trailing construct to its CommonMark fallback — e.g. a table
3641
- // header before its delimiter row becomes raw "| a | b |" paragraph text. We
3642
- // must therefore never finalize while tokens are still arriving. The
3643
- // `streaming` input is not a reliable "still arriving" signal — it can flap
3644
- // false mid-stream, and at cold start it can read false for an entire live
3645
- // stream — so we finalize only once streaming is false AND no new content
3646
- // has arrived for a short, imperceptible window. Any new content or a
3647
- // streaming=true flap re-arms the timer, so finalize fires exactly once,
3648
- // after the stream truly stops. Until then the live `parser.root` projection
3649
- // (0.5.x) renders the in-progress content, including streaming tables.
3650
- let timer = null;
3651
- effect((onCleanup) => {
3652
- const isStreaming = this.streaming();
3653
- this.content(); // re-arm whenever new content arrives
3654
- if (timer) {
3655
- clearTimeout(timer);
3656
- timer = null;
4136
+ const root = this.root();
4137
+ if (this.resolver) {
4138
+ this.resolver.markdownDefs.set(root?.citations ?? new Map());
3657
4139
  }
3658
- if (isStreaming || this.finished)
3659
- return;
3660
- timer = setTimeout(() => {
3661
- timer = null;
3662
- if (this.streaming() || this.finished)
3663
- return;
3664
- if (!this.prior.endsWith('\n'))
3665
- this.parser.push('\n');
3666
- this.parser.finish();
3667
- this.finished = true;
3668
- this.finalizeTick.update((v) => v + 1);
3669
- }, FINALIZE_DEBOUNCE_MS);
3670
- onCleanup(() => {
3671
- if (timer) {
3672
- clearTimeout(timer);
3673
- timer = null;
3674
- }
3675
- });
3676
4140
  });
3677
4141
  }
3678
- // Parser instance is rebuilt only when content diverges from the prior
3679
- // prefix (rare). For the common streaming case where content extends the
3680
- // prior content, we push the delta and reuse the existing parser tree.
3681
- parser = createPartialMarkdownParser();
3682
- prior = '';
3683
- finished = false;
3684
- // Bumped by the debounced finalizer so the `root` computed re-materializes
3685
- // the now-finished parser tree.
3686
- finalizeTick = signal(0, ...(ngDevMode ? [{ debugName: "finalizeTick" }] : []));
3687
- root = computed(() => {
3688
- const c = this.content();
3689
- this.finalizeTick(); // re-materialize after a debounced finalize
3690
- if (c !== this.prior) {
3691
- // Re-parse from scratch when the content diverged from the prior prefix,
3692
- // OR when the parser was already finalized — finish() is terminal, so
3693
- // pushing further deltas into a finished parser corrupts its state. A
3694
- // transient `streaming=false` mid-stream that finalized early thus
3695
- // recovers here: new content rebuilds an open, projecting parser.
3696
- if (c.startsWith(this.prior) && !this.finished) {
3697
- this.parser.push(c.slice(this.prior.length));
4142
+ process(supplied) {
4143
+ const prior = this.prior;
4144
+ if (!prior || supplied.generation !== prior.generation) {
4145
+ this.replaceFrom(supplied);
4146
+ return;
4147
+ }
4148
+ if (supplied.phase === prior.phase && supplied.content === prior.content) {
4149
+ return;
4150
+ }
4151
+ const violationReason = contractViolationReason(prior, supplied);
4152
+ if (violationReason) {
4153
+ if (this.violationPolicy === 'throw') {
4154
+ throw contractViolation(prior, supplied, violationReason);
3698
4155
  }
3699
- else {
3700
- this.parser = createPartialMarkdownParser();
3701
- this.finished = false;
3702
- if (c.length > 0)
3703
- this.parser.push(c);
3704
- }
3705
- this.prior = c;
3706
- }
3707
- // Materialize for Angular reactivity: produces a NEW root reference when
3708
- // any descendant subtree changed; same reference when nothing changed
3709
- // (structural sharing). This is what makes signal-based CD propagate
3710
- // downstream changes despite the parser preserving identity.
3711
- return materialize(this.parser.root);
3712
- }, ...(ngDevMode ? [{ debugName: "root" }] : []));
4156
+ this.replaceFrom(supplied);
4157
+ return;
4158
+ }
4159
+ const parser = this.parser;
4160
+ const delta = supplied.content.slice(prior.content.length);
4161
+ if (delta.length > 0)
4162
+ parser.push(delta);
4163
+ if (supplied.phase === 'complete')
4164
+ parser.finish();
4165
+ this.materializedRoot = materialize(parser.root);
4166
+ this.prior = { ...supplied };
4167
+ }
4168
+ replaceFrom(supplied) {
4169
+ const parser = this.createParser();
4170
+ parser.push(supplied.content);
4171
+ if (supplied.phase === 'complete')
4172
+ parser.finish();
4173
+ const root = materialize(parser.root);
4174
+ this.parser = parser;
4175
+ this.prior = { ...supplied };
4176
+ this.materializedRoot = root;
4177
+ }
3713
4178
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatStreamingMdComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3714
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatStreamingMdComponent, isStandalone: true, selector: "chat-streaming-md", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, viewRegistry: { classPropertyName: "viewRegistry", publicName: "viewRegistry", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
4179
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatStreamingMdComponent, isStandalone: true, selector: "chat-streaming-md", inputs: { document: { classPropertyName: "document", publicName: "document", isSignal: true, isRequired: true, transformFunction: null }, viewRegistry: { classPropertyName: "viewRegistry", publicName: "viewRegistry", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
3715
4180
  {
3716
4181
  provide: MARKDOWN_VIEW_REGISTRY,
3717
4182
  useFactory: (host) => host.resolvedRegistry(),
@@ -3736,7 +4201,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
3736
4201
  deps: [ChatStreamingMdComponent],
3737
4202
  },
3738
4203
  ], styles: ["chat-streaming-md{display:block;color:var(--tplane-chat-text);line-height:var(--tplane-chat-line-height)}chat-streaming-md h1,chat-streaming-md h2,chat-streaming-md h3,chat-streaming-md h4,chat-streaming-md h5,chat-streaming-md h6{font-weight:600;line-height:1.25;margin:1.25rem 0 .75rem}chat-streaming-md h1:first-child,chat-streaming-md h2:first-child,chat-streaming-md h3:first-child,chat-streaming-md h4:first-child,chat-streaming-md h5:first-child,chat-streaming-md h6:first-child{margin-top:0}chat-streaming-md h1{font-size:1.5em;font-weight:700}chat-streaming-md h2{font-size:1.25em}chat-streaming-md h3{font-size:1.1em}chat-streaming-md h4{font-size:1em}chat-streaming-md h5,chat-streaming-md h6{font-size:.95em;color:var(--tplane-chat-text-muted)}chat-streaming-md p{margin:0 0 .75rem;line-height:1.6;font-size:var(--tplane-chat-font-size)}chat-streaming-md p:last-child{margin-bottom:0}chat-streaming-md strong,chat-streaming-md b{font-weight:700}chat-streaming-md em,chat-streaming-md i{font-style:italic}chat-streaming-md del,chat-streaming-md s{text-decoration:line-through;color:var(--tplane-chat-text-muted)}chat-streaming-md mark{background:var(--tplane-chat-surface-alt);padding:0 2px;border-radius:2px}chat-streaming-md sub{font-size:.75em;vertical-align:sub}chat-streaming-md sup{font-size:.75em;vertical-align:super}chat-streaming-md a{color:var(--tplane-chat-primary);text-decoration:underline;text-underline-offset:2px}chat-streaming-md a:hover{text-decoration-thickness:2px}chat-streaming-md ul,chat-streaming-md ol{margin:0 0 .75rem;padding-left:1.5rem}chat-streaming-md ul{list-style:disc outside}chat-streaming-md ol{list-style:decimal outside}chat-streaming-md ul ul{list-style:circle outside}chat-streaming-md ul ul ul{list-style:square outside}chat-streaming-md li{margin:.2rem 0}chat-streaming-md li::marker{color:var(--tplane-chat-text-muted)}chat-streaming-md li>p{margin:0 0 .25rem}chat-streaming-md li>ul,chat-streaming-md li>ol{margin:.25rem 0 0}chat-streaming-md li:has(>input[type=checkbox]){list-style:none;margin-left:-1.25rem}chat-streaming-md li>input[type=checkbox]{margin-right:.5rem;vertical-align:middle}chat-streaming-md code{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text);padding:1px 5px;border-radius:4px;font-family:var(--tplane-chat-font-mono);font-size:.9em}chat-streaming-md pre{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text);padding:12px 14px;border-radius:var(--tplane-chat-radius-card);overflow-x:auto;font-family:var(--tplane-chat-font-mono);font-size:var(--tplane-chat-font-size-sm);line-height:1.5;margin:0 0 .75rem}chat-streaming-md pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}chat-streaming-md blockquote{border-left:3px solid var(--tplane-chat-separator);padding:.25rem 0 .25rem 12px;margin:0 0 .75rem;color:var(--tplane-chat-text-muted)}chat-streaming-md blockquote>:last-child{margin-bottom:0}chat-streaming-md hr{border:none;border-top:1px solid var(--tplane-chat-separator);margin:1rem 0}chat-streaming-md table{border-collapse:collapse;margin:0 0 .75rem;width:100%;font-size:.95em}chat-streaming-md thead{background:var(--tplane-chat-surface-alt)}chat-streaming-md th,chat-streaming-md td{border:1px solid var(--tplane-chat-separator);padding:6px 10px;text-align:left;vertical-align:top}chat-streaming-md th{font-weight:600}chat-streaming-md chat-md-table{display:block;overflow-x:auto;max-width:100%;margin:0 0 .75rem}chat-streaming-md chat-md-table-row{display:contents}chat-streaming-md chat-md-table-cell{display:contents}chat-streaming-md chat-md-table>table{margin:0}chat-streaming-md li.chat-md-list-item--task{list-style:none;margin-left:-1.25rem;display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}chat-streaming-md li.chat-md-list-item--task>input[type=checkbox]{margin:0;flex:0 0 auto;transform:translateY(2px)}chat-streaming-md li.chat-md-list-item--task>chat-md-children{flex:1 1 auto;min-width:0}chat-streaming-md li.chat-md-list-item--task>chat-md-children>chat-md-paragraph:first-child>p{margin:0}chat-streaming-md img{max-width:100%;height:auto;border-radius:6px}chat-streaming-md .chat-md-image--broken{display:inline-flex;align-items:center;gap:.4rem;padding:.25rem .5rem;background:var(--tplane-chat-surface-alt);border:1px dashed var(--tplane-chat-separator);border-radius:6px;font-size:.9em;color:var(--tplane-chat-text-muted, currentColor);opacity:.85}chat-streaming-md .chat-md-image__icon{font-size:1em;line-height:1}chat-streaming-md .chat-md-image__alt{font-style:italic}chat-streaming-md .chat-md-math--display{display:block;margin:.5em 0;overflow-x:auto}chat-streaming-md .chat-md-math--raw{font-family:var(--tplane-chat-font-mono, ui-monospace, monospace)}\n"] }]
3739
- }], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], viewRegistry: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewRegistry", required: false }] }] } });
4204
+ }], ctorParameters: () => [], propDecorators: { document: [{ type: i0.Input, args: [{ isSignal: true, alias: "document", required: true }] }], viewRegistry: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewRegistry", required: false }] }] } });
3740
4205
 
3741
4206
  // SPDX-License-Identifier: MIT
3742
4207
  /**
@@ -3778,17 +4243,19 @@ function formatDuration(ms) {
3778
4243
  * step labels often appear in reasoning output).
3779
4244
  *
3780
4245
  * Internal state: a tristate "expanded" — null means follow auto state-
3781
- * driven logic (force-expand on isStreaming, otherwise honor
4246
+ * driven logic (force-expand while delivery is streaming, otherwise honor
3782
4247
  * defaultExpanded), boolean is a manual user choice that wins for the
3783
4248
  * lifetime of the instance.
3784
4249
  */
3785
4250
  class ChatReasoningComponent {
3786
4251
  content = input('', ...(ngDevMode ? [{ debugName: "content" }] : []));
3787
- isStreaming = input(false, ...(ngDevMode ? [{ debugName: "isStreaming" }] : []));
4252
+ delivery = input.required(...(ngDevMode ? [{ debugName: "delivery" }] : []));
3788
4253
  durationMs = input(undefined, ...(ngDevMode ? [{ debugName: "durationMs" }] : []));
3789
4254
  label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : []));
3790
4255
  defaultExpanded = input(false, ...(ngDevMode ? [{ debugName: "defaultExpanded" }] : []));
3791
4256
  hasContent = computed(() => (this.content() ?? '').length > 0, ...(ngDevMode ? [{ debugName: "hasContent" }] : []));
4257
+ isStreaming = computed(() => this.delivery().phase === 'streaming', ...(ngDevMode ? [{ debugName: "isStreaming" }] : []));
4258
+ document = computed(() => markdownDocument(this.content(), this.delivery(), ':reasoning'), ...(ngDevMode ? [{ debugName: "document" }] : []));
3792
4259
  /** null = follow auto logic (streaming → expanded, else defaultExpanded). */
3793
4260
  _expandedOverride = signal(null, ...(ngDevMode ? [{ debugName: "_expandedOverride" }] : []));
3794
4261
  expanded = computed(() => {
@@ -3828,7 +4295,7 @@ class ChatReasoningComponent {
3828
4295
  this._expandedOverride.set(!this.expanded());
3829
4296
  }
3830
4297
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatReasoningComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3831
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatReasoningComponent, isStandalone: true, selector: "chat-reasoning", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, isStreaming: { classPropertyName: "isStreaming", publicName: "isStreaming", isSignal: true, isRequired: false, transformFunction: null }, durationMs: { classPropertyName: "durationMs", publicName: "durationMs", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, defaultExpanded: { classPropertyName: "defaultExpanded", publicName: "defaultExpanded", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-has-content": "hasContent()", "attr.data-expanded": "expandedStr()", "attr.data-streaming": "isStreaming()" } }, ngImport: i0, template: `
4298
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatReasoningComponent, isStandalone: true, selector: "chat-reasoning", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, delivery: { classPropertyName: "delivery", publicName: "delivery", isSignal: true, isRequired: true, transformFunction: null }, durationMs: { classPropertyName: "durationMs", publicName: "durationMs", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, defaultExpanded: { classPropertyName: "defaultExpanded", publicName: "defaultExpanded", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-has-content": "hasContent()", "attr.data-expanded": "expandedStr()", "attr.data-streaming": "isStreaming()" } }, ngImport: i0, template: `
3832
4299
  <button
3833
4300
  type="button"
3834
4301
  class="chat-reasoning__header"
@@ -3845,10 +4312,10 @@ class ChatReasoningComponent {
3845
4312
  </button>
3846
4313
  @if (expanded()) {
3847
4314
  <div class="chat-reasoning__body">
3848
- <chat-streaming-md [content]="content()" [streaming]="isStreaming()" />
4315
+ <chat-streaming-md [document]="document()" />
3849
4316
  </div>
3850
4317
  }
3851
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;margin:0 0 .5rem}:host([data-has-content=\"false\"]){display:none}.chat-reasoning__header{display:inline-flex;align-items:center;gap:.5rem;padding:4px 10px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:9999px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-xs);font-family:inherit;cursor:pointer;line-height:1.2}.chat-reasoning__header:hover{color:var(--tplane-chat-text)}.chat-reasoning__chevron{width:10px;height:10px;transition:transform .12s ease}:host([data-expanded=\"true\"]) .chat-reasoning__chevron{transform:rotate(90deg)}.chat-reasoning__pulse{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--tplane-chat-text-muted);animation:chat-reasoning-pulse 1.2s ease-in-out infinite}@keyframes chat-reasoning-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-reasoning__body{margin-top:.5rem;padding-left:12px;border-left:2px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted)}.chat-reasoning__body chat-streaming-md{font-size:.95em}\n"], dependencies: [{ kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4318
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;margin:0 0 .5rem}:host([data-has-content=\"false\"]){display:none}.chat-reasoning__header{display:inline-flex;align-items:center;gap:.5rem;padding:4px 10px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:9999px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-xs);font-family:inherit;cursor:pointer;line-height:1.2}.chat-reasoning__header:hover{color:var(--tplane-chat-text)}.chat-reasoning__chevron{width:10px;height:10px;transition:transform .12s ease}:host([data-expanded=\"true\"]) .chat-reasoning__chevron{transform:rotate(90deg)}.chat-reasoning__pulse{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--tplane-chat-text-muted);animation:chat-reasoning-pulse 1.2s ease-in-out infinite}@keyframes chat-reasoning-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-reasoning__body{margin-top:.5rem;padding-left:12px;border-left:2px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted)}.chat-reasoning__body chat-streaming-md{font-size:.95em}\n"], dependencies: [{ kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["document", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3852
4319
  }
3853
4320
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatReasoningComponent, decorators: [{
3854
4321
  type: Component,
@@ -3873,11 +4340,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
3873
4340
  </button>
3874
4341
  @if (expanded()) {
3875
4342
  <div class="chat-reasoning__body">
3876
- <chat-streaming-md [content]="content()" [streaming]="isStreaming()" />
4343
+ <chat-streaming-md [document]="document()" />
3877
4344
  </div>
3878
4345
  }
3879
4346
  `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;margin:0 0 .5rem}:host([data-has-content=\"false\"]){display:none}.chat-reasoning__header{display:inline-flex;align-items:center;gap:.5rem;padding:4px 10px;background:var(--tplane-chat-surface-alt);border:1px solid var(--tplane-chat-separator);border-radius:9999px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-xs);font-family:inherit;cursor:pointer;line-height:1.2}.chat-reasoning__header:hover{color:var(--tplane-chat-text)}.chat-reasoning__chevron{width:10px;height:10px;transition:transform .12s ease}:host([data-expanded=\"true\"]) .chat-reasoning__chevron{transform:rotate(90deg)}.chat-reasoning__pulse{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--tplane-chat-text-muted);animation:chat-reasoning-pulse 1.2s ease-in-out infinite}@keyframes chat-reasoning-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-reasoning__body{margin-top:.5rem;padding-left:12px;border-left:2px solid var(--tplane-chat-separator);color:var(--tplane-chat-text-muted)}.chat-reasoning__body chat-streaming-md{font-size:.95em}\n"] }]
3880
- }], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], isStreaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "isStreaming", required: false }] }], durationMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "durationMs", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], defaultExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultExpanded", required: false }] }] } });
4347
+ }], ctorParameters: () => [], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], delivery: [{ type: i0.Input, args: [{ isSignal: true, alias: "delivery", required: true }] }], durationMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "durationMs", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], defaultExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultExpanded", required: false }] }] } });
3881
4348
 
3882
4349
  // libs/chat/src/lib/styles/chat-launcher-button.styles.ts
3883
4350
  // SPDX-License-Identifier: MIT
@@ -5514,6 +5981,34 @@ function statusToTraceState(s) {
5514
5981
  class ChatSubagentCardComponent {
5515
5982
  subagent = input.required(...(ngDevMode ? [{ debugName: "subagent" }] : []));
5516
5983
  state = computed(() => statusToTraceState(this.subagent().status()), ...(ngDevMode ? [{ debugName: "state" }] : []));
5984
+ markdownDocuments = new Map();
5985
+ constructor() {
5986
+ effect(() => {
5987
+ let liveIds;
5988
+ try {
5989
+ liveIds = new Set(this.subagent().messages().map((message) => message.id));
5990
+ }
5991
+ catch {
5992
+ return;
5993
+ }
5994
+ for (const id of [...this.markdownDocuments.keys()]) {
5995
+ if (!liveIds.has(id))
5996
+ this.markdownDocuments.delete(id);
5997
+ }
5998
+ });
5999
+ }
6000
+ markdownDocumentFor(content, message) {
6001
+ const prior = this.markdownDocuments.get(message.id);
6002
+ const delivery = message.delivery;
6003
+ if (prior?.generation === delivery.generation &&
6004
+ prior.phase === delivery.phase &&
6005
+ prior.content === content) {
6006
+ return prior;
6007
+ }
6008
+ const document = markdownDocument(content, delivery);
6009
+ this.markdownDocuments.set(message.id, document);
6010
+ return document;
6011
+ }
5517
6012
  textOf(m) {
5518
6013
  const c = m.content;
5519
6014
  return typeof c === 'string' ? c : '';
@@ -5543,7 +6038,7 @@ class ChatSubagentCardComponent {
5543
6038
  <div class="sac__reasoning">{{ m.reasoning }}</div>
5544
6039
  }
5545
6040
  @if (textOf(m); as t) {
5546
- <chat-streaming-md [content]="t" />
6041
+ <chat-streaming-md [document]="markdownDocumentFor(t, m)" />
5547
6042
  }
5548
6043
  @for (tc of toolCallsFor(m); track tc.id) {
5549
6044
  <chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
@@ -5551,7 +6046,7 @@ class ChatSubagentCardComponent {
5551
6046
  </div>
5552
6047
  }
5553
6048
  </chat-trace>
5554
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.sac__name{color:var(--tplane-chat-text);font-weight:500;font-size:var(--tplane-chat-font-size-sm)}.sac__id{font-family:var(--tplane-chat-font-mono);font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text-muted)}.sac__pill[data-status=running]{background:var(--tplane-chat-warning-bg);color:var(--tplane-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--tplane-chat-success)}.sac__pill[data-status=error]{background:var(--tplane-chat-error-bg);color:var(--tplane-chat-error-text)}.sac__count{font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--tplane-chat-separator)}.sac__reasoning{font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted);font-style:italic;margin-bottom:4px}\n"], dependencies: [{ kind: "component", type: ChatTraceComponent, selector: "chat-trace", inputs: ["state", "defaultExpanded"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6049
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.sac__name{color:var(--tplane-chat-text);font-weight:500;font-size:var(--tplane-chat-font-size-sm)}.sac__id{font-family:var(--tplane-chat-font-mono);font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text-muted)}.sac__pill[data-status=running]{background:var(--tplane-chat-warning-bg);color:var(--tplane-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--tplane-chat-success)}.sac__pill[data-status=error]{background:var(--tplane-chat-error-bg);color:var(--tplane-chat-error-text)}.sac__count{font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--tplane-chat-separator)}.sac__reasoning{font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted);font-style:italic;margin-bottom:4px}\n"], dependencies: [{ kind: "component", type: ChatTraceComponent, selector: "chat-trace", inputs: ["state", "defaultExpanded"] }, { kind: "component", type: ChatToolCallCardComponent, selector: "chat-tool-call-card", inputs: ["toolCall", "defaultCollapsed"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["document", "viewRegistry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5555
6050
  }
5556
6051
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSubagentCardComponent, decorators: [{
5557
6052
  type: Component,
@@ -5569,7 +6064,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
5569
6064
  <div class="sac__reasoning">{{ m.reasoning }}</div>
5570
6065
  }
5571
6066
  @if (textOf(m); as t) {
5572
- <chat-streaming-md [content]="t" />
6067
+ <chat-streaming-md [document]="markdownDocumentFor(t, m)" />
5573
6068
  }
5574
6069
  @for (tc of toolCallsFor(m); track tc.id) {
5575
6070
  <chat-tool-call-card [toolCall]="toToolCallInfo(tc)" />
@@ -5578,7 +6073,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
5578
6073
  }
5579
6074
  </chat-trace>
5580
6075
  `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block}.sac__name{color:var(--tplane-chat-text);font-weight:500;font-size:var(--tplane-chat-font-size-sm)}.sac__id{font-family:var(--tplane-chat-font-mono);font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted);margin-left:4px}.sac__pill{padding:1px 8px;border-radius:9999px;font-size:11px;font-weight:500;margin-left:4px}.sac__pill[data-status=pending]{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text-muted)}.sac__pill[data-status=running]{background:var(--tplane-chat-warning-bg);color:var(--tplane-chat-warning-text)}.sac__pill[data-status=complete]{color:var(--tplane-chat-success)}.sac__pill[data-status=error]{background:var(--tplane-chat-error-bg);color:var(--tplane-chat-error-text)}.sac__count{font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted)}.sac__msg{padding:6px 0}.sac__msg+.sac__msg{border-top:1px solid var(--tplane-chat-separator)}.sac__reasoning{font-size:var(--tplane-chat-font-size-xs);color:var(--tplane-chat-text-muted);font-style:italic;margin-bottom:4px}\n"] }]
5581
- }], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
6076
+ }], ctorParameters: () => [], propDecorators: { subagent: [{ type: i0.Input, args: [{ isSignal: true, alias: "subagent", required: true }] }] } });
5582
6077
 
5583
6078
  // libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-call-template.directive.ts
5584
6079
  // SPDX-License-Identifier: MIT
@@ -6064,11 +6559,23 @@ function toToolViewSpec(tc) {
6064
6559
  elements: {
6065
6560
  [tc.name]: {
6066
6561
  type: tc.name,
6067
- props: { ...args, ...result, status: tc.status },
6562
+ props: { ...args, ...result, status: tc.status, clientTool: toClientToolLifecycle(tc) },
6068
6563
  },
6069
6564
  },
6070
6565
  };
6071
6566
  }
6567
+ function toClientToolLifecycle(tc) {
6568
+ const hasResult = tc.result !== undefined;
6569
+ return {
6570
+ id: tc.id,
6571
+ name: tc.name,
6572
+ status: tc.status,
6573
+ phase: tc.status === 'error' ? 'error' : tc.status === 'complete' ? 'complete' : 'running',
6574
+ hasResult,
6575
+ ...(hasResult ? { result: tc.result } : {}),
6576
+ ...(tc.error !== undefined ? { error: tc.error } : {}),
6577
+ };
6578
+ }
6072
6579
  function isRecord$2(v) {
6073
6580
  return typeof v === 'object' && v !== null && !Array.isArray(v);
6074
6581
  }
@@ -8114,6 +8621,7 @@ function deriveJsonSchema(toolName, schema) {
8114
8621
  }
8115
8622
  }
8116
8623
 
8624
+ const defaultSignal = new AbortController().signal;
8117
8625
  /** Validate raw model args against a Standard Schema. */
8118
8626
  async function validateArgs(schema, args) {
8119
8627
  const res = await schema['~standard'].validate(args);
@@ -8126,12 +8634,12 @@ async function validateArgs(schema, args) {
8126
8634
  return { ok: true, value: res.value };
8127
8635
  }
8128
8636
  /** Validate args, run the handler, and normalize the outcome to a ClientToolResult. */
8129
- async function executeFunctionTool(def, rawArgs) {
8637
+ async function executeFunctionTool(def, rawArgs, context = { signal: defaultSignal }) {
8130
8638
  const v = await validateArgs(def.schema, rawArgs);
8131
8639
  if (!v.ok)
8132
8640
  return { ok: false, error: `invalid arguments: ${v.error}` };
8133
8641
  try {
8134
- const value = await def.handler(v.value);
8642
+ const value = await def.handler(v.value, context);
8135
8643
  return { ok: true, value };
8136
8644
  }
8137
8645
  catch (err) {
@@ -8139,18 +8647,102 @@ async function executeFunctionTool(def, rawArgs) {
8139
8647
  }
8140
8648
  }
8141
8649
 
8650
+ /** Return whether this function tool should claim before browser execution. */
8651
+ function shouldClaimBeforeExecute(def) {
8652
+ return def.idempotent !== true;
8653
+ }
8654
+ /** Default fail-closed result for a stale in-progress client-tool execution. */
8655
+ function defaultInterruptedClientToolResult(toolCallId) {
8656
+ return {
8657
+ ok: false,
8658
+ error: `client tool execution interrupted before completion: ${toolCallId}`,
8659
+ };
8660
+ }
8661
+ /** Result recorded when the user stops a run while a client tool is running. */
8662
+ function cancelledClientToolResult(toolCallId) {
8663
+ return {
8664
+ ok: false,
8665
+ error: `client tool execution cancelled before completion: ${toolCallId}`,
8666
+ };
8667
+ }
8668
+ /** Default fail-closed result when the execution guard itself cannot be reached. */
8669
+ function clientToolGuardFailureResult(toolCallId, error) {
8670
+ const message = error instanceof Error ? error.message : String(error);
8671
+ return {
8672
+ ok: false,
8673
+ error: `client tool execution guard failed for ${toolCallId}: ${message}`,
8674
+ };
8675
+ }
8676
+
8142
8677
  // SPDX-License-Identifier: MIT
8678
+ /** Agents whose stop() this module has already wrapped. */
8679
+ const patchedAgents = new WeakMap();
8143
8680
  /**
8144
8681
  * Watches the agent's pending client tool calls and auto-runs FUNCTION tools,
8145
8682
  * resolving each with its result. View/ask (component) tools are handled by the
8146
8683
  * rendering layer, not here. No-op if the agent lacks the clientTools
8147
8684
  * capability. MUST be called in an injection context (sets up an effect).
8148
8685
  */
8149
- function startClientToolExecutor(agent, registry) {
8686
+ function startClientToolExecutor(agent, registry, options = {}) {
8150
8687
  const cap = agent.clientTools;
8151
8688
  if (!cap)
8152
8689
  return;
8153
- const inFlight = new Set();
8690
+ const destroyRef = inject(DestroyRef);
8691
+ const inFlight = new Map();
8692
+ const abortAll = () => {
8693
+ for (const controller of inFlight.values()) {
8694
+ controller.abort();
8695
+ }
8696
+ };
8697
+ // The stop button lives in chat-input, which has no coordinator reference, so
8698
+ // wrapping agent.stop is the only interception seam. Wrap at most once per
8699
+ // agent, fan the stop out to EVERY live executor, and restore the original
8700
+ // once the last one is destroyed — agents often outlive the components that
8701
+ // start executors, so an unbounded stack of wrappers would leak.
8702
+ let patch = patchedAgents.get(agent);
8703
+ if (!patch) {
8704
+ const originalStop = agent.stop;
8705
+ const boundStop = originalStop.bind(agent);
8706
+ const aborts = new Set();
8707
+ const wrapper = async () => {
8708
+ for (const abort of aborts)
8709
+ abort();
8710
+ await boundStop();
8711
+ };
8712
+ patch = { aborts, originalStop, wrapper };
8713
+ patchedAgents.set(agent, patch);
8714
+ agent.stop = wrapper;
8715
+ }
8716
+ const registration = patch;
8717
+ registration.aborts.add(abortAll);
8718
+ destroyRef.onDestroy(() => {
8719
+ registration.aborts.delete(abortAll);
8720
+ if (registration.aborts.size === 0 && patchedAgents.get(agent) === registration) {
8721
+ // Only un-patch if OUR wrapper is still installed; something else may
8722
+ // have replaced agent.stop since, and clobbering it would be worse.
8723
+ if (agent.stop === registration.wrapper)
8724
+ agent.stop = registration.originalStop;
8725
+ patchedAgents.delete(agent);
8726
+ }
8727
+ });
8728
+ destroyRef.onDestroy(abortAll);
8729
+ const settleToolCall = options.settleToolCall ?? ((toolCall, result) => cap.resolve(toolCall.id, result));
8730
+ // `resolve()` contractually means "record AND continue" — both adapters
8731
+ // submit a new run from it. A cancelled call must therefore NEVER go through
8732
+ // it, or clicking Stop would immediately start another run and the model
8733
+ // could re-call the very tool it was stopped on. settle() + flush() is the
8734
+ // durable, run-free equivalent. If the capability has no settle(), we leave
8735
+ // the call dangling on purpose: a dangling tool call is repaired on the next
8736
+ // user turn, whereas resurrecting a stopped run is not repairable at all.
8737
+ const settleWithoutContinuing = options.settleWithoutContinuing ??
8738
+ ((toolCall, result) => {
8739
+ if (!cap.settle) {
8740
+ console.warn(`Client tool "${toolCall.name}" was cancelled but the agent capability does not implement settle(); the result cannot be recorded without starting a run.`);
8741
+ return;
8742
+ }
8743
+ cap.settle(toolCall.id, result);
8744
+ void Promise.resolve(cap.flush?.()).catch(() => undefined);
8745
+ });
8154
8746
  effect(() => {
8155
8747
  for (const tc of cap.pending()) {
8156
8748
  const def = registry[tc.name];
@@ -8163,16 +8755,148 @@ function startClientToolExecutor(agent, registry) {
8163
8755
  // double-dispatch within a render cycle.
8164
8756
  if (inFlight.has(tc.id))
8165
8757
  continue;
8166
- inFlight.add(tc.id);
8167
- void executeFunctionTool(def, tc.args).then((result) => {
8168
- cap.resolve(tc.id, result);
8758
+ if (options.shouldExecuteToolCall && !options.shouldExecuteToolCall(tc))
8759
+ continue;
8760
+ const controller = new AbortController();
8761
+ inFlight.set(tc.id, controller);
8762
+ void runFunctionTool({
8763
+ def,
8764
+ toolCall: tc,
8765
+ rawArgs: tc.args,
8766
+ toolCallId: tc.id,
8767
+ controller,
8768
+ executionGuard: options.executionGuard,
8769
+ settleToolCall,
8770
+ settleWithoutContinuing,
8771
+ }).finally(() => {
8169
8772
  inFlight.delete(tc.id);
8170
8773
  });
8171
8774
  }
8172
8775
  });
8173
8776
  }
8777
+ /**
8778
+ * Settle the moment the signal fires instead of waiting for the handler.
8779
+ *
8780
+ * Honoring `context.signal` is opt-in, and most handlers ignore it, so awaiting
8781
+ * the handler would leave a stopped call unsettled — dangling on the server
8782
+ * thread and pinned in `inFlight` — until it happens to finish, or forever. The
8783
+ * handler's late real result is then discarded: the id is already settled and
8784
+ * in the adapter's `resolvedIds`, so it can neither double-settle nor
8785
+ * re-dispatch. `executeFunctionTool` normalizes throws, so the losing promise
8786
+ * never rejects.
8787
+ */
8788
+ async function raceAbort(execution, signal) {
8789
+ if (signal.aborted)
8790
+ return { aborted: true };
8791
+ let onAbort = () => undefined;
8792
+ const aborted = new Promise((resolve) => {
8793
+ onAbort = () => resolve({ aborted: true });
8794
+ signal.addEventListener('abort', onAbort, { once: true });
8795
+ });
8796
+ try {
8797
+ return await Promise.race([
8798
+ execution.then((result) => ({ aborted: signal.aborted, result })),
8799
+ aborted,
8800
+ ]);
8801
+ }
8802
+ finally {
8803
+ signal.removeEventListener('abort', onAbort);
8804
+ }
8805
+ }
8806
+ async function runFunctionTool(input) {
8807
+ const { def, toolCall, rawArgs, toolCallId, controller, executionGuard, settleToolCall, settleWithoutContinuing, } = input;
8808
+ const signal = controller.signal;
8809
+ // An aborted call MUST still settle: leaving it unsettled keeps it out of
8810
+ // `resolvedIds`, so it stays pending and is re-dispatched after the next run
8811
+ // (re-running a side-effecting handler the user explicitly stopped) and
8812
+ // leaves the server thread holding a tool call with no tool result. It must
8813
+ // settle through the NON-continuing channel — see `settleWithoutContinuing`.
8814
+ let settled = false;
8815
+ /** Settle and continue the run. Only for outcomes the user did not cancel. */
8816
+ const settleAndContinue = (result) => {
8817
+ if (settled)
8818
+ return;
8819
+ settled = true;
8820
+ settleToolCall(toolCall, result);
8821
+ };
8822
+ /** Settle without continuing the run. Abort and teardown paths only. */
8823
+ const settleCancelled = (result = cancelledClientToolResult(toolCallId)) => {
8824
+ if (settled)
8825
+ return;
8826
+ settled = true;
8827
+ settleWithoutContinuing(toolCall, result);
8828
+ };
8829
+ if (!executionGuard || !shouldClaimBeforeExecute(def)) {
8830
+ const outcome = await raceAbort(executeFunctionTool(def, rawArgs, { signal }), signal);
8831
+ if (outcome.aborted)
8832
+ settleCancelled();
8833
+ else
8834
+ settleAndContinue(outcome.result);
8835
+ return;
8836
+ }
8837
+ const key = { threadId: executionGuard.threadId, toolCallId };
8838
+ let claim;
8839
+ try {
8840
+ claim = await executionGuard.store.claim(key);
8841
+ }
8842
+ catch (err) {
8843
+ // A claim that rejects after the user stopped must still settle, but must
8844
+ // never continue the run.
8845
+ if (signal.aborted)
8846
+ settleCancelled();
8847
+ else
8848
+ settleAndContinue(clientToolGuardFailureResult(toolCallId, err));
8849
+ return;
8850
+ }
8851
+ if (signal.aborted) {
8852
+ // Aborted while the claim RPC was in flight. If the claim succeeded, the
8853
+ // durable row is now `executing`; record the cancelled result or it stays
8854
+ // that way forever and a later reload fails closed as "interrupted".
8855
+ if (claim === 'claimed') {
8856
+ await recordThenSettle(executionGuard, key, cancelledClientToolResult(toolCallId), toolCallId, settleCancelled);
8857
+ }
8858
+ else {
8859
+ settleCancelled();
8860
+ }
8861
+ return;
8862
+ }
8863
+ if (claim === 'claimed') {
8864
+ const outcome = await raceAbort(executeFunctionTool(def, rawArgs, { signal }), signal);
8865
+ const result = outcome.aborted
8866
+ ? cancelledClientToolResult(toolCallId)
8867
+ : outcome.result;
8868
+ await recordThenSettle(executionGuard, key, result, toolCallId, outcome.aborted ? settleCancelled : settleAndContinue);
8869
+ return;
8870
+ }
8871
+ if (claim.status === 'done') {
8872
+ settleAndContinue(claim.result);
8873
+ return;
8874
+ }
8875
+ const result = claim.status === 'failed' && claim.result
8876
+ ? claim.result
8877
+ : defaultInterruptedClientToolResult(toolCallId);
8878
+ await recordThenSettle(executionGuard, key, result, toolCallId, settleAndContinue);
8879
+ }
8880
+ /**
8881
+ * Record the final result then settle through `settle`. Deliberately
8882
+ * abort-agnostic: an aborted execution must still write its (cancelled) result,
8883
+ * otherwise the guard store stays at `executing` forever and a later reload
8884
+ * fails closed with a misleading "interrupted" message. The caller picks the
8885
+ * settlement channel, so a cancelled result never continues the run.
8886
+ */
8887
+ async function recordThenSettle(executionGuard, key, result, toolCallId, settle) {
8888
+ try {
8889
+ await executionGuard.store.record(key, result);
8890
+ }
8891
+ catch (err) {
8892
+ settle(clientToolGuardFailureResult(toolCallId, err));
8893
+ return;
8894
+ }
8895
+ settle(result);
8896
+ }
8174
8897
 
8175
8898
  // SPDX-License-Identifier: MIT
8899
+ const DEFAULT_MAX_CONTINUATION_TURNS = 10;
8176
8900
  /** Build the catalog spec list shipped to the model. */
8177
8901
  function toClientToolSpecs(registry) {
8178
8902
  return Object.entries(registry).map(([name, def]) => ({
@@ -8194,9 +8918,140 @@ function viewComponents(registry) {
8194
8918
  }
8195
8919
  return out;
8196
8920
  }
8197
- function createClientToolsCoordinator(registry) {
8921
+ function createClientToolsCoordinator(registry, options = {}) {
8198
8922
  const viewRegistry = views(viewComponents(registry));
8199
8923
  const ackedViews = new Set();
8924
+ let currentGroup;
8925
+ let currentUserTurnKey = '';
8926
+ let continuationTurns = 0;
8927
+ function toolWantsFollowUp(tc) {
8928
+ return registry[tc.name]?.followUp !== false;
8929
+ }
8930
+ function latestUserTurnKey(agent) {
8931
+ const messages = agent.messages();
8932
+ for (let i = messages.length - 1; i >= 0; i--) {
8933
+ const msg = messages[i];
8934
+ if (msg.role === 'user' || msg.role === 'human')
8935
+ return msg.id ?? `index:${i}`;
8936
+ }
8937
+ return 'no-user-turn';
8938
+ }
8939
+ function continuationLimit(calls) {
8940
+ const maxTurns = options.continuationPolicy?.maxTurns ?? DEFAULT_MAX_CONTINUATION_TURNS;
8941
+ if (maxTurns === 0)
8942
+ return undefined;
8943
+ const attemptedTurn = continuationTurns + 1;
8944
+ if (attemptedTurn <= maxTurns) {
8945
+ continuationTurns = attemptedTurn;
8946
+ return undefined;
8947
+ }
8948
+ return {
8949
+ maxTurns,
8950
+ attemptedTurn,
8951
+ toolCallIds: calls.map((tc) => tc.id),
8952
+ toolNames: calls.map((tc) => tc.name),
8953
+ };
8954
+ }
8955
+ function emitContinuationLimit(event) {
8956
+ console.error(`Client tool continuation stopped after ${event.maxTurns} turn(s); pending tool calls: ${event.toolCallIds.join(', ')}`);
8957
+ options.continuationPolicy?.onLimit?.(event);
8958
+ }
8959
+ function createGroup(agent, calls) {
8960
+ const userTurnKey = latestUserTurnKey(agent);
8961
+ if (userTurnKey !== currentUserTurnKey) {
8962
+ currentUserTurnKey = userTurnKey;
8963
+ continuationTurns = 0;
8964
+ }
8965
+ const limit = continuationLimit(calls);
8966
+ if (limit)
8967
+ emitContinuationLimit(limit);
8968
+ return {
8969
+ ids: new Set(calls.map((tc) => tc.id)),
8970
+ hasFollowUp: calls.some(toolWantsFollowUp),
8971
+ settledIds: new Set(),
8972
+ allowed: !limit,
8973
+ };
8974
+ }
8975
+ function groupFor(agent, cap, tc) {
8976
+ if (currentGroup?.ids.has(tc.id))
8977
+ return currentGroup;
8978
+ const pending = cap.pending();
8979
+ const calls = pending.some((pendingCall) => pendingCall.id === tc.id)
8980
+ ? pending
8981
+ : [tc];
8982
+ currentGroup = createGroup(agent, calls);
8983
+ return currentGroup;
8984
+ }
8985
+ function shouldHandleClientToolCall(agent, cap, tc) {
8986
+ return groupFor(agent, cap, tc).allowed;
8987
+ }
8988
+ function warnMissingSettle(tc) {
8989
+ console.warn(`Client tool "${tc.name}" requested batched or terminal settlement, but the agent capability does not implement settle(); falling back to resolve().`);
8990
+ }
8991
+ /** No settle() and the run must not continue — the result cannot be recorded. */
8992
+ function warnUnrecordableWithoutSettle(tc) {
8993
+ console.warn(`Client tool "${tc.name}" must not continue the run, but the agent capability does not implement settle(); the result cannot be recorded without starting a run.`);
8994
+ }
8995
+ function flushSettledResults(cap) {
8996
+ if (!cap.flush) {
8997
+ console.warn('Client tool results were settled with no follow-up run, but the agent capability does not implement flush(); results may not reach the server.');
8998
+ return;
8999
+ }
9000
+ void Promise.resolve(cap.flush()).catch((err) => {
9001
+ console.error('Client tool flush failed', err);
9002
+ });
9003
+ }
9004
+ function settleClientToolCall(cap, agent, tc, result, mayContinue = true) {
9005
+ const group = groupFor(agent, cap, tc);
9006
+ if (group.settledIds.has(tc.id))
9007
+ return;
9008
+ group.settledIds.add(tc.id);
9009
+ const groupComplete = Array.from(group.ids).every((id) => group.settledIds.has(id));
9010
+ // Over the continuation limit, or cancelled by the user (stop / teardown):
9011
+ // still record the result so the server never keeps an unanswered tool
9012
+ // call, but never continue the run — `resolve()` submits a new run, which
9013
+ // for a cancelled call would undo the very stop the user asked for.
9014
+ if (!group.allowed || !mayContinue) {
9015
+ if (!cap.settle) {
9016
+ warnUnrecordableWithoutSettle(tc);
9017
+ return;
9018
+ }
9019
+ cap.settle(tc.id, result);
9020
+ // Flush ONCE, when the last call in the group settles. Adapters coalesce
9021
+ // concurrent flushes (returning the in-flight promise), so flushing per
9022
+ // call would strand every batch after the first.
9023
+ if (groupComplete) {
9024
+ flushSettledResults(cap);
9025
+ // A limit-blocked group must stay `currentGroup` so repeated effect
9026
+ // passes over the same never-executed calls keep short-circuiting on
9027
+ // `settledIds`. Cancelled calls leave `pending()` once settled, so
9028
+ // their group can be retired normally.
9029
+ if (group.allowed)
9030
+ currentGroup = undefined;
9031
+ }
9032
+ return;
9033
+ }
9034
+ if (!cap.settle) {
9035
+ if (group.ids.size > 1 || registry[tc.name]?.followUp === false)
9036
+ warnMissingSettle(tc);
9037
+ cap.resolve(tc.id, result);
9038
+ if (groupComplete)
9039
+ currentGroup = undefined;
9040
+ return;
9041
+ }
9042
+ if (groupComplete && group.hasFollowUp) {
9043
+ cap.resolve(tc.id, result);
9044
+ currentGroup = undefined;
9045
+ return;
9046
+ }
9047
+ cap.settle(tc.id, result);
9048
+ if (groupComplete) {
9049
+ // Terminal group: nothing will continue the run, so make the settled
9050
+ // results durable ourselves or the server keeps an unanswered tool call.
9051
+ flushSettledResults(cap);
9052
+ currentGroup = undefined;
9053
+ }
9054
+ }
8200
9055
  return {
8201
9056
  viewRegistry,
8202
9057
  connect(agent) {
@@ -8204,7 +9059,24 @@ function createClientToolsCoordinator(registry) {
8204
9059
  if (!cap)
8205
9060
  return;
8206
9061
  cap.setCatalog(toClientToolSpecs(registry));
8207
- startClientToolExecutor(agent, registry); // function tools
9062
+ startClientToolExecutor(agent, registry, {
9063
+ executionGuard: options.executionGuard,
9064
+ shouldExecuteToolCall: (tc) => {
9065
+ if (shouldHandleClientToolCall(agent, cap, tc))
9066
+ return true;
9067
+ // Blocked before executing: record why, so the model sees a reason
9068
+ // instead of an unanswered tool call.
9069
+ settleClientToolCall(cap, agent, tc, {
9070
+ ok: false,
9071
+ error: `client tool continuation limit reached; ${tc.name} was not executed`,
9072
+ });
9073
+ return false;
9074
+ },
9075
+ settleToolCall: (tc, result) => settleClientToolCall(cap, agent, tc, result),
9076
+ // Cancelled calls reuse the same group bookkeeping (so the flush-once-
9077
+ // per-group property holds) but are forced down the settle+flush path.
9078
+ settleWithoutContinuing: (tc, result) => settleClientToolCall(cap, agent, tc, result, false),
9079
+ }); // function tools
8208
9080
  // Auto-ack `view` tools: they render but produce no user value.
8209
9081
  effect(() => {
8210
9082
  for (const tc of cap.pending()) {
@@ -8214,7 +9086,7 @@ function createClientToolsCoordinator(registry) {
8214
9086
  if (ackedViews.has(tc.id))
8215
9087
  continue;
8216
9088
  ackedViews.add(tc.id);
8217
- cap.resolve(tc.id, { ok: true, value: { shown: true } });
9089
+ settleClientToolCall(cap, agent, tc, { ok: true, value: { shown: true } });
8218
9090
  }
8219
9091
  });
8220
9092
  },
@@ -8229,7 +9101,7 @@ function createClientToolsCoordinator(registry) {
8229
9101
  const name = event.elementKey;
8230
9102
  const pending = cap.pending().find((tc) => tc.name === name && registry[tc.name]?.kind === 'ask');
8231
9103
  if (pending)
8232
- cap.resolve(pending.id, { ok: true, value: event.value });
9104
+ settleClientToolCall(cap, agent, pending, { ok: true, value: event.value });
8233
9105
  },
8234
9106
  };
8235
9107
  }
@@ -9823,6 +10695,8 @@ class ChatComponent {
9823
10695
  'generate_json_render_spec',
9824
10696
  'render_spec',
9825
10697
  ], ...(ngDevMode ? [{ debugName: "genuiToolNames" }] : []));
10698
+ clientToolExecutionGuard = input(undefined, ...(ngDevMode ? [{ debugName: "clientToolExecutionGuard" }] : []));
10699
+ clientToolContinuationPolicy = input(undefined, ...(ngDevMode ? [{ debugName: "clientToolContinuationPolicy" }] : []));
9826
10700
  showWelcome = computed(() => {
9827
10701
  if (this.welcomeDisabled())
9828
10702
  return false;
@@ -9833,6 +10707,7 @@ class ChatComponent {
9833
10707
  }, ...(ngDevMode ? [{ debugName: "showWelcome" }] : []));
9834
10708
  threadSelected = output();
9835
10709
  renderEvent = output();
10710
+ clientToolContinuationLimit = output();
9836
10711
  /** Emitted when the user clicks the regenerate button on an assistant message. */
9837
10712
  regenerate = output();
9838
10713
  /** Emitted when the user rates an assistant message. */
@@ -9858,7 +10733,17 @@ class ChatComponent {
9858
10733
  */
9859
10734
  coordinator = computed(() => {
9860
10735
  const reg = this.clientTools();
9861
- return reg ? createClientToolsCoordinator(reg) : undefined;
10736
+ const policy = this.clientToolContinuationPolicy();
10737
+ return reg ? createClientToolsCoordinator(reg, {
10738
+ executionGuard: this.clientToolExecutionGuard(),
10739
+ continuationPolicy: {
10740
+ ...policy,
10741
+ onLimit: (event) => {
10742
+ policy?.onLimit?.(event);
10743
+ this.clientToolContinuationLimit.emit(event);
10744
+ },
10745
+ },
10746
+ }) : undefined;
9862
10747
  }, ...(ngDevMode ? [{ debugName: "coordinator" }] : []));
9863
10748
  /**
9864
10749
  * The view registry actually used for rendering tool-views and for
@@ -9911,22 +10796,6 @@ class ChatComponent {
9911
10796
  const raw = messageContent(message);
9912
10797
  return a2uiActionLabel(raw) ?? raw;
9913
10798
  }
9914
- /**
9915
- * True while a message's reasoning is mid-stream — i.e. it's the latest
9916
- * message, the agent is loading, the message has reasoning content, and
9917
- * no response text has arrived yet. Once the response text begins, the
9918
- * reasoning pill collapses (per its internal logic).
9919
- */
9920
- isReasoningStreaming(message, index) {
9921
- const agent = this.agent();
9922
- const isTail = index === agent.messages().length - 1;
9923
- if (!isTail || !agent.isLoading())
9924
- return false;
9925
- if (!message.reasoning || message.reasoning.length === 0)
9926
- return false;
9927
- const text = typeof message.content === 'string' ? message.content : '';
9928
- return text.length === 0;
9929
- }
9930
10799
  /** The nearest preceding assistant message (skipping hidden tool messages), or undefined. */
9931
10800
  prevAssistant(msgs, index) {
9932
10801
  for (let j = index - 1; j >= 0; j--) {
@@ -9949,9 +10818,10 @@ class ChatComponent {
9949
10818
  }
9950
10819
  /**
9951
10820
  * Aggregate the reasoning RUN starting at `index`: joins each step's
9952
- * reasoning, sums durations, counts steps, and computes the streaming flag
9953
- * and the merged label when N > 1 ("Thought for {total} · {N} steps", or
9954
- * just "{N} steps" when no step reported timing).
10821
+ * reasoning, sums durations, counts steps, and returns the last step's
10822
+ * delivery because that step owns the current aggregate snapshot. Also
10823
+ * computes the merged label when N > 1 ("Thought for {total} · {N} steps",
10824
+ * or just "{N} steps" when no step reported timing).
9955
10825
  */
9956
10826
  reasoningRun(index) {
9957
10827
  const msgs = this.agent().messages();
@@ -9961,18 +10831,18 @@ class ChatComponent {
9961
10831
  if (m.role === 'tool')
9962
10832
  continue; // skip hidden tool messages
9963
10833
  if (m.role === 'assistant' && m.reasoning) {
9964
- steps.push({ msg: m, idx: j });
10834
+ steps.push(m);
9965
10835
  continue;
9966
10836
  }
9967
10837
  break; // any other message ends the run
9968
10838
  }
9969
- const content = steps.map((s) => s.msg.reasoning ?? '').filter(Boolean).join('\n\n');
10839
+ const content = steps.map((step) => step.reasoning ?? '').filter(Boolean).join('\n\n');
9970
10840
  const durations = steps
9971
- .map((s) => s.msg.reasoningDurationMs)
10841
+ .map((step) => step.reasoningDurationMs)
9972
10842
  .filter((d) => typeof d === 'number');
9973
10843
  const durationMs = durations.length ? durations.reduce((a, b) => a + b, 0) : undefined;
9974
10844
  const last = steps[steps.length - 1];
9975
- const streaming = last ? this.isReasoningStreaming(last.msg, last.idx) : false;
10845
+ const delivery = last?.delivery ?? msgs[index].delivery;
9976
10846
  // Only claim a duration when at least one step reported timing. Otherwise
9977
10847
  // "Thought for <1s" would read as "fast" when it really means "unknown", so
9978
10848
  // drop the duration phrase and label by step count alone.
@@ -9981,9 +10851,10 @@ class ChatComponent {
9981
10851
  ? `Thought for ${formatDuration(durationMs)} · ${steps.length} steps`
9982
10852
  : `${steps.length} steps`
9983
10853
  : undefined;
9984
- return { content, durationMs, streaming, label };
10854
+ return { content, durationMs, delivery, label };
9985
10855
  }
9986
10856
  classifiers = new Map();
10857
+ markdownDocuments = new Map();
9987
10858
  destroyRef = inject(DestroyRef);
9988
10859
  injector = inject(Injector);
9989
10860
  // Resolved against the component's own `providers` in normal use. The fallback
@@ -10007,28 +10878,24 @@ class ChatComponent {
10007
10878
  programmaticScrollCount = 0;
10008
10879
  static PIN_TOLERANCE_PX = 150;
10009
10880
  /**
10010
- * True iff there's a current (last-index) assistant message that's
10011
- * still streaming. The bubble's own caret already signals loading;
10881
+ * True iff the current (last-index) assistant message owns an active
10882
+ * delivery generation. The bubble's own caret already signals loading;
10012
10883
  * we suppress the floor typing-indicator in that case so the user
10013
10884
  * doesn't see two loading affordances at once.
10014
10885
  *
10015
10886
  * Matches the same `streaming + current` condition the bubble uses
10016
- * to enable `.chat-message__caret`:
10017
- * `this.agent().isLoading() && i === this.agent().messages().length - 1`
10018
- * `i === this.agent().messages().length - 1`
10887
+ * to enable `.chat-message__caret`.
10019
10888
  *
10020
10889
  * Restricted to assistant role because the caret only renders on
10021
10890
  * assistant bubbles (`:host([data-role="assistant"][data-current=...
10022
10891
  * ][data-streaming=...])`).
10023
10892
  */
10024
10893
  currentAssistantStreaming = computed(() => {
10025
- if (!this.agent().isLoading())
10026
- return false;
10027
10894
  const msgs = this.agent().messages();
10028
10895
  if (msgs.length === 0)
10029
10896
  return false;
10030
10897
  const last = msgs[msgs.length - 1];
10031
- return last?.role === 'assistant';
10898
+ return last?.role === 'assistant' && last.delivery.phase === 'streaming';
10032
10899
  }, ...(ngDevMode ? [{ debugName: "currentAssistantStreaming" }] : []));
10033
10900
  constructor() {
10034
10901
  // Inject the chat lib's root CSS custom properties (--tplane-chat-bg,
@@ -10252,10 +11119,14 @@ class ChatComponent {
10252
11119
  }
10253
11120
  for (const key of [...this.classifiers.keys()]) {
10254
11121
  if (!liveIds.has(key)) {
10255
- this.classifiers.get(key)?.dispose();
11122
+ this.classifiers.get(key)?.classifier.dispose();
10256
11123
  this.classifiers.delete(key);
10257
11124
  }
10258
11125
  }
11126
+ for (const key of [...this.markdownDocuments.keys()]) {
11127
+ if (!liveIds.has(key))
11128
+ this.markdownDocuments.delete(key);
11129
+ }
10259
11130
  });
10260
11131
  }
10261
11132
  prevRole(index) {
@@ -10435,20 +11306,34 @@ class ChatComponent {
10435
11306
  return false;
10436
11307
  }
10437
11308
  classifyMessage(content, message) {
10438
- const id = message.id ?? '';
10439
- let c = this.classifiers.get(id);
10440
- if (!c) {
10441
- c = createContentClassifier();
10442
- this.classifiers.set(id, c);
10443
- }
10444
- c.update(content);
10445
- return c;
11309
+ const generation = message.delivery.generation;
11310
+ let entry = this.classifiers.get(message.id);
11311
+ if (!entry || entry.generation !== generation) {
11312
+ entry?.classifier.dispose();
11313
+ entry = { generation, classifier: createContentClassifier() };
11314
+ this.classifiers.set(message.id, entry);
11315
+ }
11316
+ entry.classifier.update(content);
11317
+ return entry.classifier;
11318
+ }
11319
+ markdownDocumentFor(content, message) {
11320
+ const prior = this.markdownDocuments.get(message.id);
11321
+ const delivery = message.delivery;
11322
+ if (prior?.generation === delivery.generation &&
11323
+ prior.phase === delivery.phase &&
11324
+ prior.content === content) {
11325
+ return prior;
11326
+ }
11327
+ const document = markdownDocument(content, delivery);
11328
+ this.markdownDocuments.set(message.id, document);
11329
+ return document;
10446
11330
  }
10447
11331
  clearClassifiers() {
10448
- for (const [, c] of this.classifiers) {
10449
- c.dispose();
11332
+ for (const [, entry] of this.classifiers) {
11333
+ entry.classifier.dispose();
10450
11334
  }
10451
11335
  this.classifiers.clear();
11336
+ this.markdownDocuments.clear();
10452
11337
  }
10453
11338
  onSpecEvent(event, messageIndex) {
10454
11339
  this.renderEvent.emit({ messageIndex, event });
@@ -10492,7 +11377,7 @@ class ChatComponent {
10492
11377
  this.messageCopy.emit({ messageIndex: idx, content });
10493
11378
  }
10494
11379
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10495
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatComponent, isStandalone: true, selector: "chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, clientTools: { classPropertyName: "clientTools", publicName: "clientTools", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, threads: { classPropertyName: "threads", publicName: "threads", isSignal: true, isRequired: false, transformFunction: null }, activeThreadId: { classPropertyName: "activeThreadId", publicName: "activeThreadId", isSignal: true, isRequired: false, transformFunction: null }, welcomeDisabled: { classPropertyName: "welcomeDisabled", publicName: "welcomeDisabled", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, modelPickerPlaceholder: { classPropertyName: "modelPickerPlaceholder", publicName: "modelPickerPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, genuiToolNames: { classPropertyName: "genuiToolNames", publicName: "genuiToolNames", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", threadSelected: "threadSelected", renderEvent: "renderEvent", regenerate: "regenerate", rate: "rate", messageCopy: "messageCopy" }, providers: [
11380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ChatComponent, isStandalone: true, selector: "chat", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, clientTools: { classPropertyName: "clientTools", publicName: "clientTools", isSignal: true, isRequired: false, transformFunction: null }, store: { classPropertyName: "store", publicName: "store", isSignal: true, isRequired: false, transformFunction: null }, handlers: { classPropertyName: "handlers", publicName: "handlers", isSignal: true, isRequired: false, transformFunction: null }, threads: { classPropertyName: "threads", publicName: "threads", isSignal: true, isRequired: false, transformFunction: null }, activeThreadId: { classPropertyName: "activeThreadId", publicName: "activeThreadId", isSignal: true, isRequired: false, transformFunction: null }, welcomeDisabled: { classPropertyName: "welcomeDisabled", publicName: "welcomeDisabled", isSignal: true, isRequired: false, transformFunction: null }, modelOptions: { classPropertyName: "modelOptions", publicName: "modelOptions", isSignal: true, isRequired: false, transformFunction: null }, showModelPicker: { classPropertyName: "showModelPicker", publicName: "showModelPicker", isSignal: true, isRequired: false, transformFunction: null }, selectedModel: { classPropertyName: "selectedModel", publicName: "selectedModel", isSignal: true, isRequired: false, transformFunction: null }, modelPickerPlaceholder: { classPropertyName: "modelPickerPlaceholder", publicName: "modelPickerPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, genuiToolNames: { classPropertyName: "genuiToolNames", publicName: "genuiToolNames", isSignal: true, isRequired: false, transformFunction: null }, clientToolExecutionGuard: { classPropertyName: "clientToolExecutionGuard", publicName: "clientToolExecutionGuard", isSignal: true, isRequired: false, transformFunction: null }, clientToolContinuationPolicy: { classPropertyName: "clientToolContinuationPolicy", publicName: "clientToolContinuationPolicy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedModel: "selectedModelChange", threadSelected: "threadSelected", renderEvent: "renderEvent", clientToolContinuationLimit: "clientToolContinuationLimit", regenerate: "regenerate", rate: "rate", messageCopy: "messageCopy" }, providers: [
10496
11381
  { provide: CHAT_LIFECYCLE, useFactory: createChatLifecycle },
10497
11382
  ], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
10498
11383
  @if (showWelcome()) {
@@ -10534,12 +11419,11 @@ class ChatComponent {
10534
11419
  <ng-template chatMessageTemplate="ai" let-message let-i="index">
10535
11420
  @let content = messageContent(message);
10536
11421
  @let classified = classifyMessage(content, message);
10537
- @let pending = classified.type() === 'pending';
10538
11422
  <chat-message
10539
11423
  [role]="'assistant'"
10540
11424
  [message]="message"
10541
11425
  [prevRole]="prevRole(i)"
10542
- [streaming]="agent().isLoading() && i === agent().messages().length - 1"
11426
+ [streaming]="message.delivery.phase === 'streaming'"
10543
11427
  [current]="i === agent().messages().length - 1"
10544
11428
  >
10545
11429
  <!-- Reasoning is merged across a run of consecutive (tool-
@@ -10552,7 +11436,7 @@ class ChatComponent {
10552
11436
  @let run = reasoningRun(i);
10553
11437
  <chat-reasoning
10554
11438
  [content]="run.content"
10555
- [isStreaming]="run.streaming"
11439
+ [delivery]="run.delivery"
10556
11440
  [durationMs]="run.durationMs"
10557
11441
  [label]="run.label"
10558
11442
  />
@@ -10571,7 +11455,7 @@ class ChatComponent {
10571
11455
  (events)="onClientToolEvent($event)"
10572
11456
  />
10573
11457
  @if (classified.markdown(); as md) {
10574
- <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
11458
+ <chat-streaming-md [document]="markdownDocumentFor(md, message)" />
10575
11459
  }
10576
11460
  @if (classified.spec(); as spec) {
10577
11461
  <!-- Pass ONLY the explicit consumer store (may be
@@ -10664,7 +11548,7 @@ class ChatComponent {
10664
11548
  </div>
10665
11549
  </div>
10666
11550
  }
10667
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--tplane-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--tplane-chat-separator);background:var(--tplane-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--tplane-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--tplane-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--tplane-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--tplane-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--tplane-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--tplane-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["content", "streaming", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"], outputs: ["events"] }, { kind: "component", type: A2uiSurfaceComponent, selector: "a2ui-surface", inputs: ["surface", "state", "catalog", "handlers", "surfaceFallback"], outputs: ["events", "action"] }, { kind: "component", type: ChatMessageActionsComponent, selector: "chat-message-actions", inputs: ["content", "disabled"], outputs: ["regenerate", "rate", "contentCopied"] }, { kind: "component", type: ChatWelcomeComponent, selector: "chat-welcome" }, { kind: "component", type: ChatSelectComponent, selector: "chat-select", inputs: ["options", "value", "placeholder", "disabled", "menuLabel", "panelClass"], outputs: ["valueChange"] }, { kind: "component", type: ChatReasoningComponent, selector: "chat-reasoning", inputs: ["content", "isStreaming", "durationMs", "label", "defaultExpanded"] }, { kind: "component", type: ChatScrollBubbleComponent, selector: "chat-scroll-bubble", inputs: ["mode"], outputs: ["clicked"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11551
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--tplane-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--tplane-chat-separator);background:var(--tplane-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--tplane-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--tplane-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--tplane-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--tplane-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--tplane-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--tplane-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"], dependencies: [{ kind: "component", type: ChatWindowComponent, selector: "chat-window" }, { kind: "component", type: ChatMessageListComponent, selector: "chat-message-list", inputs: ["agent"] }, { kind: "directive", type: MessageTemplateDirective, selector: "ng-template[chatMessageTemplate]", inputs: ["chatMessageTemplate"] }, { kind: "component", type: ChatMessageComponent, selector: "chat-message", inputs: ["role", "current", "streaming", "prevRole", "message"] }, { kind: "component", type: ChatInputComponent, selector: "chat-input", inputs: ["agent", "submitOnEnter", "placeholder", "showStopButton"], outputs: ["submitted", "stopped"] }, { kind: "component", type: ChatTypingIndicatorComponent, selector: "chat-typing-indicator", inputs: ["agent"] }, { kind: "component", type: ChatErrorComponent, selector: "chat-error", inputs: ["agent"] }, { kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatGenerativeUiComponent, selector: "chat-generative-ui", inputs: ["spec", "registry", "store", "handlers", "loading"], outputs: ["events"] }, { kind: "component", type: ChatStreamingMdComponent, selector: "chat-streaming-md", inputs: ["document", "viewRegistry"] }, { kind: "component", type: ChatToolCallsComponent, selector: "chat-tool-calls", inputs: ["agent", "message", "grouping", "groupSummary", "excludeToolNames"] }, { kind: "component", type: ChatToolViewsComponent, selector: "chat-tool-views", inputs: ["agent", "message", "views", "store", "handlers"], outputs: ["events"] }, { kind: "component", type: A2uiSurfaceComponent, selector: "a2ui-surface", inputs: ["surface", "state", "catalog", "handlers", "surfaceFallback"], outputs: ["events", "action"] }, { kind: "component", type: ChatMessageActionsComponent, selector: "chat-message-actions", inputs: ["content", "disabled"], outputs: ["regenerate", "rate", "contentCopied"] }, { kind: "component", type: ChatWelcomeComponent, selector: "chat-welcome" }, { kind: "component", type: ChatSelectComponent, selector: "chat-select", inputs: ["options", "value", "placeholder", "disabled", "menuLabel", "panelClass"], outputs: ["valueChange"] }, { kind: "component", type: ChatReasoningComponent, selector: "chat-reasoning", inputs: ["content", "delivery", "durationMs", "label", "defaultExpanded"] }, { kind: "component", type: ChatScrollBubbleComponent, selector: "chat-scroll-bubble", inputs: ["mode"], outputs: ["clicked"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10668
11552
  }
10669
11553
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatComponent, decorators: [{
10670
11554
  type: Component,
@@ -10718,12 +11602,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10718
11602
  <ng-template chatMessageTemplate="ai" let-message let-i="index">
10719
11603
  @let content = messageContent(message);
10720
11604
  @let classified = classifyMessage(content, message);
10721
- @let pending = classified.type() === 'pending';
10722
11605
  <chat-message
10723
11606
  [role]="'assistant'"
10724
11607
  [message]="message"
10725
11608
  [prevRole]="prevRole(i)"
10726
- [streaming]="agent().isLoading() && i === agent().messages().length - 1"
11609
+ [streaming]="message.delivery.phase === 'streaming'"
10727
11610
  [current]="i === agent().messages().length - 1"
10728
11611
  >
10729
11612
  <!-- Reasoning is merged across a run of consecutive (tool-
@@ -10736,7 +11619,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10736
11619
  @let run = reasoningRun(i);
10737
11620
  <chat-reasoning
10738
11621
  [content]="run.content"
10739
- [isStreaming]="run.streaming"
11622
+ [delivery]="run.delivery"
10740
11623
  [durationMs]="run.durationMs"
10741
11624
  [label]="run.label"
10742
11625
  />
@@ -10755,7 +11638,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10755
11638
  (events)="onClientToolEvent($event)"
10756
11639
  />
10757
11640
  @if (classified.markdown(); as md) {
10758
- <chat-streaming-md [content]="md" [streaming]="agent().isLoading() && i === agent().messages().length - 1" />
11641
+ <chat-streaming-md [document]="markdownDocumentFor(md, message)" />
10759
11642
  }
10760
11643
  @if (classified.spec(); as spec) {
10761
11644
  <!-- Pass ONLY the explicit consumer store (may be
@@ -10849,7 +11732,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
10849
11732
  </div>
10850
11733
  }
10851
11734
  `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:flex;flex-direction:column;flex:1 1 auto;height:100%;min-height:0;max-height:100%;overflow:hidden;background:var(--tplane-chat-bg)}:host>chat-welcome{display:flex;flex:1 1 auto;width:100%}.chat-shell{display:flex;flex:1;min-height:0;overflow:hidden}.chat-shell__sidebar{width:240px;flex-shrink:0;border-right:1px solid var(--tplane-chat-separator);background:var(--tplane-chat-surface-alt);overflow-y:auto;display:none}@media(min-width:768px){.chat-shell__sidebar{display:block}}.chat-shell__main{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}.chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:60px 20px;color:var(--tplane-chat-text-muted);text-align:center;flex:1;min-height:0}.chat-empty[hidden]{display:none}.chat-empty__title{font-size:1.125rem;font-weight:500;color:var(--tplane-chat-text);margin:0}.chat-empty__sub{margin:0;font-size:var(--tplane-chat-font-size-sm)}.chat-scroll{flex:1;min-height:0;overflow-y:auto;padding-top:var(--tplane-chat-edge-pad)}.chat-scroll::-webkit-scrollbar{width:6px}.chat-scroll::-webkit-scrollbar-thumb{background:var(--tplane-chat-separator);border-radius:10px}[chatFooter]{padding-bottom:var(--tplane-chat-edge-pad)}.chat-footer-wrap{position:relative}\n"] }]
10852
- }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], clientTools: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientTools", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], activeThreadId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeThreadId", required: false }] }], welcomeDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeDisabled", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], modelPickerPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelPickerPlaceholder", required: false }] }], genuiToolNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "genuiToolNames", required: false }] }], threadSelected: [{ type: i0.Output, args: ["threadSelected"] }], renderEvent: [{ type: i0.Output, args: ["renderEvent"] }], regenerate: [{ type: i0.Output, args: ["regenerate"] }], rate: [{ type: i0.Output, args: ["rate"] }], messageCopy: [{ type: i0.Output, args: ["messageCopy"] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
11735
+ }], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], clientTools: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientTools", required: false }] }], store: [{ type: i0.Input, args: [{ isSignal: true, alias: "store", required: false }] }], handlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "handlers", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], activeThreadId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeThreadId", required: false }] }], welcomeDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "welcomeDisabled", required: false }] }], modelOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelOptions", required: false }] }], showModelPicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showModelPicker", required: false }] }], selectedModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedModel", required: false }] }, { type: i0.Output, args: ["selectedModelChange"] }], modelPickerPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelPickerPlaceholder", required: false }] }], genuiToolNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "genuiToolNames", required: false }] }], clientToolExecutionGuard: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientToolExecutionGuard", required: false }] }], clientToolContinuationPolicy: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientToolContinuationPolicy", required: false }] }], threadSelected: [{ type: i0.Output, args: ["threadSelected"] }], renderEvent: [{ type: i0.Output, args: ["renderEvent"] }], clientToolContinuationLimit: [{ type: i0.Output, args: ["clientToolContinuationLimit"] }], regenerate: [{ type: i0.Output, args: ["regenerate"] }], rate: [{ type: i0.Output, args: ["rate"] }], messageCopy: [{ type: i0.Output, args: ["messageCopy"] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
10853
11736
 
10854
11737
  // libs/chat/src/lib/compositions/chat-popup/chat-popup.component.ts
10855
11738
  // SPDX-License-Identifier: MIT
@@ -10936,7 +11819,7 @@ class ChatPopupComponent {
10936
11819
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
10937
11820
  </chat>
10938
11821
  </div>
10939
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{position:fixed;bottom:1rem;right:1rem;z-index:var(--tplane-chat-z-overlay-content, 30)}.chat-popup__launcher{position:relative}.chat-popup__window{position:fixed;bottom:5rem;right:1rem;width:24rem;height:600px;max-height:calc(100vh - 6rem);background:var(--tplane-chat-bg);border:1px solid var(--tplane-chat-separator);border-radius:.75rem;box-shadow:0 5px 40px #00000029;transform-origin:bottom right;transform:scale(.95) translateY(20px);opacity:0;pointer-events:none;transition:transform .2s ease-out,opacity .1s ease-out;overflow:hidden;display:flex;flex-direction:column}.chat-popup__window[data-open=true]{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}@media(max-width:640px){.chat-popup__window{inset:0 auto auto 0;width:100vw;height:100vh;max-height:100vh;border-radius:0}}.chat-popup__close{position:absolute;top:8px;right:8px;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--tplane-chat-text-muted);border-radius:50%;z-index:1;display:flex;align-items:center;justify-content:center}.chat-popup__close:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "clientTools", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11822
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{position:fixed;bottom:1rem;right:1rem;z-index:var(--tplane-chat-z-overlay-content, 30)}.chat-popup__launcher{position:relative}.chat-popup__window{position:fixed;bottom:5rem;right:1rem;width:24rem;height:600px;max-height:calc(100vh - 6rem);background:var(--tplane-chat-bg);border:1px solid var(--tplane-chat-separator);border-radius:.75rem;box-shadow:0 5px 40px #00000029;transform-origin:bottom right;transform:scale(.95) translateY(20px);opacity:0;pointer-events:none;transition:transform .2s ease-out,opacity .1s ease-out;overflow:hidden;display:flex;flex-direction:column}.chat-popup__window[data-open=true]{transform:scale(1) translateY(0);opacity:1;pointer-events:auto}@media(max-width:640px){.chat-popup__window{inset:0 auto auto 0;width:100vw;height:100vh;max-height:100vh;border-radius:0}}.chat-popup__close{position:absolute;top:8px;right:8px;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--tplane-chat-text-muted);border-radius:50%;z-index:1;display:flex;align-items:center;justify-content:center}.chat-popup__close:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "clientTools", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames", "clientToolExecutionGuard", "clientToolContinuationPolicy"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "clientToolContinuationLimit", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10940
11823
  }
10941
11824
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatPopupComponent, decorators: [{
10942
11825
  type: Component,
@@ -11059,7 +11942,7 @@ class ChatSidebarComponent {
11059
11942
  <ng-content select="[chatWelcomeSuggestions]" chatWelcomeSuggestions />
11060
11943
  </chat>
11061
11944
  </aside>
11062
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:flex;height:100%;min-height:0}.chat-sidebar__content{flex:1 1 auto;min-width:0;min-height:0;transition:margin-right .3s ease}:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:var(--tplane-chat-sidebar-width-drawer, 28rem)}@media(max-width:640px){:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:0}}.chat-sidebar__panel{position:fixed;top:0;right:0;bottom:var(--tplane-chat-debug-claim-bottom, 0);width:var(--tplane-chat-sidebar-width-drawer, 28rem);background:var(--tplane-chat-bg);border-left:1px solid var(--tplane-chat-separator);box-shadow:-8px 0 32px #00000014;transform:translate(100%);transition:transform .2s ease-out,bottom .2s ease-out;z-index:var(--tplane-chat-z-overlay-content, 30);display:flex;flex-direction:column}.chat-sidebar__panel[data-open=true]{transform:translate(0)}@media(max-width:640px){.chat-sidebar__panel{width:100vw}}.chat-sidebar__panel-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-bottom:1px solid var(--tplane-chat-separator);min-height:48px}.chat-sidebar__panel-title{min-width:0;flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--tplane-chat-text);font-weight:500;font-size:var(--tplane-chat-font-size-sm)}.chat-sidebar__close{flex:0 0 auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--tplane-chat-text-muted);border-radius:50%;display:flex;align-items:center;justify-content:center}.chat-sidebar__close:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidebar__launcher{position:fixed;bottom:calc(1rem + var(--tplane-chat-debug-claim-bottom, 0));right:1rem;z-index:var(--tplane-chat-z-overlay-content, 30);transition:bottom .2s ease-out}:host([data-open=\"true\"]) .chat-sidebar__launcher{display:none}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "clientTools", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11945
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:flex;height:100%;min-height:0}.chat-sidebar__content{flex:1 1 auto;min-width:0;min-height:0;transition:margin-right .3s ease}:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:var(--tplane-chat-sidebar-width-drawer, 28rem)}@media(max-width:640px){:host([data-push=\"true\"][data-open=\"true\"]) .chat-sidebar__content{margin-right:0}}.chat-sidebar__panel{position:fixed;top:0;right:0;bottom:var(--tplane-chat-debug-claim-bottom, 0);width:var(--tplane-chat-sidebar-width-drawer, 28rem);background:var(--tplane-chat-bg);border-left:1px solid var(--tplane-chat-separator);box-shadow:-8px 0 32px #00000014;transform:translate(100%);transition:transform .2s ease-out,bottom .2s ease-out;z-index:var(--tplane-chat-z-overlay-content, 30);display:flex;flex-direction:column}.chat-sidebar__panel[data-open=true]{transform:translate(0)}@media(max-width:640px){.chat-sidebar__panel{width:100vw}}.chat-sidebar__panel-header{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-bottom:1px solid var(--tplane-chat-separator);min-height:48px}.chat-sidebar__panel-title{min-width:0;flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--tplane-chat-text);font-weight:500;font-size:var(--tplane-chat-font-size-sm)}.chat-sidebar__close{flex:0 0 auto;width:32px;height:32px;background:transparent;border:0;cursor:pointer;color:var(--tplane-chat-text-muted);border-radius:50%;display:flex;align-items:center;justify-content:center}.chat-sidebar__close:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidebar__launcher{position:fixed;bottom:calc(1rem + var(--tplane-chat-debug-claim-bottom, 0));right:1rem;z-index:var(--tplane-chat-z-overlay-content, 30);transition:bottom .2s ease-out}:host([data-open=\"true\"]) .chat-sidebar__launcher{display:none}\n"], dependencies: [{ kind: "component", type: ChatComponent, selector: "chat", inputs: ["agent", "views", "clientTools", "store", "handlers", "threads", "activeThreadId", "welcomeDisabled", "modelOptions", "showModelPicker", "selectedModel", "modelPickerPlaceholder", "genuiToolNames", "clientToolExecutionGuard", "clientToolContinuationPolicy"], outputs: ["selectedModelChange", "threadSelected", "renderEvent", "clientToolContinuationLimit", "regenerate", "rate", "messageCopy"] }, { kind: "component", type: ChatLauncherButtonComponent, selector: "chat-launcher-button", outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11063
11946
  }
11064
11947
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidebarComponent, decorators: [{
11065
11948
  type: Component,
@@ -11222,6 +12105,14 @@ const CHAT_SIDENAV_STYLES = `
11222
12105
  bottom: 0;
11223
12106
  width: var(--tplane-chat-sidenav-width-drawer);
11224
12107
  z-index: var(--tplane-chat-z-drawer, 1001);
12108
+ /* The host is a fixed, drawer-width, full-height box at z-index 1001 that
12109
+ * stays put whether or not the drawer is open — only the inner .chat-sidenav
12110
+ * PANEL slides off-screen (translateX(-100%)) when closed. Without this, the
12111
+ * transparent closed host still intercepts clicks over the left strip of the
12112
+ * content beneath it (e.g. App-mode forces drawer on desktop, so it covered
12113
+ * the itinerary overlay + welcome suggestions). Make the host inert; the
12114
+ * on-screen panel re-enables pointer events below. */
12115
+ pointer-events: none;
11225
12116
  }
11226
12117
  :host([data-mode="drawer"][data-open="true"]) {
11227
12118
  box-shadow: 8px 0 32px rgba(0, 0, 0, 0.18);
@@ -11240,9 +12131,14 @@ const CHAT_SIDENAV_STYLES = `
11240
12131
  height: 100%;
11241
12132
  transition: transform 200ms ease;
11242
12133
  transform: translateX(-100%);
12134
+ /* Keep the closed/closing panel inert. The host is inert above, and the
12135
+ * panel can still be hit during its transform transition unless pointer
12136
+ * events are restored only for the open state below. */
12137
+ pointer-events: none;
11243
12138
  }
11244
12139
  :host([data-mode="drawer"][data-open="true"]) .chat-sidenav {
11245
12140
  transform: translateX(0);
12141
+ pointer-events: auto;
11246
12142
  }
11247
12143
  .chat-sidenav__header {
11248
12144
  flex-shrink: 0;
@@ -11990,7 +12886,7 @@ class ChatSidenavComponent {
11990
12886
  </div>
11991
12887
  </nav>
11992
12888
  <ng-container #debugHost />
11993
- `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;height:100%}:host([data-mode=\"expanded\"]),:host([data-mode=\"collapsed\"]){position:fixed;top:0;left:0;bottom:0;z-index:100}:host([data-mode=\"expanded\"]){width:var(--tplane-chat-sidenav-width-expanded)}:host([data-mode=\"collapsed\"]){width:var(--tplane-chat-sidenav-width-collapsed)}:host([data-mode=\"expanded\"]) .chat-sidenav{width:100%}:host([data-mode=\"collapsed\"]) .chat-sidenav{width:100%}:host([data-mode=\"drawer\"]){position:fixed;top:0;left:0;bottom:0;width:var(--tplane-chat-sidenav-width-drawer);z-index:var(--tplane-chat-z-drawer, 1001)}:host([data-mode=\"drawer\"][data-open=\"true\"]){box-shadow:8px 0 32px #0000002e}.chat-sidenav{display:flex;flex-direction:column;height:100%;background:var(--tplane-chat-bg);border-right:1px solid var(--tplane-chat-separator);box-sizing:border-box;overflow:hidden}:host([data-mode=\"drawer\"]) .chat-sidenav{width:100%;height:100%;transition:transform .2s ease;transform:translate(-100%)}:host([data-mode=\"drawer\"][data-open=\"true\"]) .chat-sidenav{transform:translate(0)}.chat-sidenav__header{flex-shrink:0;padding:var(--tplane-chat-space-3)}.chat-sidenav__header:empty{display:none}.chat-sidenav__topbar{flex-shrink:0;display:flex;flex-direction:row;align-items:center;justify-content:space-between;gap:4px;padding:var(--tplane-chat-space-2) var(--tplane-chat-space-3);border-bottom:1px solid var(--tplane-chat-separator)}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar{flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:var(--tplane-chat-space-2) 0}.chat-sidenav__topbar .chat-sidenav__action--close{width:36px;height:36px;padding:0;justify-content:center;flex:0 0 auto}.chat-sidenav__topbar .chat-sidenav__action--close .chat-sidenav__action-label{display:none}.chat-sidenav__action--new{border:0;padding:12px 18px;border-radius:8px;font-size:var(--tplane-chat-font-size-sm);font-weight:600;display:flex;align-items:center;gap:8px;cursor:pointer;width:100%}.chat-sidenav__action--new:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new .chat-sidenav__action-label{display:none}.chat-sidenav__actions{flex-shrink:0;display:flex;flex-direction:column;gap:4px;padding:var(--tplane-chat-space-3)}:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{align-items:center;padding:var(--tplane-chat-space-2)}.chat-sidenav__action{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;background:transparent;color:var(--tplane-chat-text);border-radius:8px;cursor:pointer;font-family:inherit;font-size:var(--tplane-chat-font-size-sm);font-weight:400;text-align:left}.chat-sidenav__action:hover{background:var(--tplane-chat-surface-alt)}.chat-sidenav__action:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action{width:36px;height:36px;padding:0;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action-label{display:none}.chat-sidenav__action.chat-sidenav__action--new{background:var(--tplane-chat-text);color:var(--tplane-chat-bg);border-radius:8px;padding:12px 18px;font-weight:600;font-size:var(--tplane-chat-font-size-sm)}.chat-sidenav__action.chat-sidenav__action--new:hover{background:var(--tplane-chat-text);filter:brightness(.92)}.chat-sidenav__action-icon{width:16px;height:16px;flex-shrink:0}.chat-sidenav__primary,.chat-sidenav__sections{flex-shrink:0}.chat-sidenav__threads{flex:1 1 auto;min-height:0;overflow-y:auto}.chat-sidenav__threads-heading{padding:8px 12px 4px;font-size:11px;color:var(--tplane-chat-text-muted);text-transform:uppercase;letter-spacing:.4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads-heading{display:none}.chat-sidenav__footer{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-top:1px solid var(--tplane-chat-separator);gap:8px;flex-shrink:0}.chat-sidenav__footer-left{display:flex;align-items:center;gap:4px;min-height:28px}.chat-sidenav__debug{height:28px;min-width:28px;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;font:inherit;font-size:12px;font-weight:500}.chat-sidenav__debug:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__debug:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__debug-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 8px color-mix(in srgb,#22c55e 60%,transparent);flex:0 0 auto}.chat-sidenav__debug-dot--streaming{animation:chat-sidenav-debug-pulse 1.2s ease-in-out infinite}@keyframes chat-sidenav-debug-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.85)}}.chat-sidenav__footer-right{display:flex;align-items:center;gap:4px;margin-left:auto}.chat-sidenav__toggle{width:28px;height:28px;border-radius:8px;border:0;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center}.chat-sidenav__toggle:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer{flex-direction:column;align-items:center;padding:10px 4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left{display:flex;flex-direction:column;align-items:center;min-height:28px}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug{width:28px;padding:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug-label,:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left>:not(.chat-sidenav__debug){display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-right{flex-direction:column}.chat-sidenav__account{display:none}.chat-sidenav__account:has(>*){display:flex;align-items:center;gap:4px}.chat-sidenav__archived{flex-shrink:0}.chat-sidenav__archived-heading{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:var(--tplane-chat-font-size-sm);text-align:left;cursor:pointer}.chat-sidenav__archived-heading:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__archived-heading:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__archived-chevron{width:12px;height:12px;transition:transform .15s ease;flex-shrink:0}.chat-sidenav__archived[data-open=true] .chat-sidenav__archived-chevron{transform:rotate(90deg)}.chat-sidenav__archived-empty{padding:8px 12px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-sm)}:host([data-mode=\"collapsed\"]) .chat-sidenav__archived{display:none}.chat-sidenav__projects{flex-shrink:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__projects{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__initial{display:inline-flex}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-title{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-time{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__kebab{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__grip{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-wrap{justify-content:center}:host([data-mode=\"collapsed\"]) .chat-thread-list__item{padding:6px;justify-content:center;align-items:center;flex-direction:row}:host([data-mode=\"collapsed\"]) .chat-thread-list__new{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center;background:transparent}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search .chat-sidenav__action-label{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads,:host([data-mode=\"collapsed\"]) .chat-sidenav__projects,:host([data-mode=\"collapsed\"]) .chat-sidenav__archived,:host([data-mode=\"collapsed\"]) .chat-sidenav__sections,:host([data-mode=\"collapsed\"]) .chat-sidenav__primary{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar,:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{padding:8px 4px;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatProjectListComponent, selector: "chat-project-list", inputs: ["projects", "activeProjectId", "showNewProjectButton", "actions"], outputs: ["projectSelected", "newProjectRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12889
+ `, isInline: true, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;height:100%}:host([data-mode=\"expanded\"]),:host([data-mode=\"collapsed\"]){position:fixed;top:0;left:0;bottom:0;z-index:100}:host([data-mode=\"expanded\"]){width:var(--tplane-chat-sidenav-width-expanded)}:host([data-mode=\"collapsed\"]){width:var(--tplane-chat-sidenav-width-collapsed)}:host([data-mode=\"expanded\"]) .chat-sidenav{width:100%}:host([data-mode=\"collapsed\"]) .chat-sidenav{width:100%}:host([data-mode=\"drawer\"]){position:fixed;top:0;left:0;bottom:0;width:var(--tplane-chat-sidenav-width-drawer);z-index:var(--tplane-chat-z-drawer, 1001);pointer-events:none}:host([data-mode=\"drawer\"][data-open=\"true\"]){box-shadow:8px 0 32px #0000002e}.chat-sidenav{display:flex;flex-direction:column;height:100%;background:var(--tplane-chat-bg);border-right:1px solid var(--tplane-chat-separator);box-sizing:border-box;overflow:hidden}:host([data-mode=\"drawer\"]) .chat-sidenav{width:100%;height:100%;transition:transform .2s ease;transform:translate(-100%);pointer-events:none}:host([data-mode=\"drawer\"][data-open=\"true\"]) .chat-sidenav{transform:translate(0);pointer-events:auto}.chat-sidenav__header{flex-shrink:0;padding:var(--tplane-chat-space-3)}.chat-sidenav__header:empty{display:none}.chat-sidenav__topbar{flex-shrink:0;display:flex;flex-direction:row;align-items:center;justify-content:space-between;gap:4px;padding:var(--tplane-chat-space-2) var(--tplane-chat-space-3);border-bottom:1px solid var(--tplane-chat-separator)}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar{flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:var(--tplane-chat-space-2) 0}.chat-sidenav__topbar .chat-sidenav__action--close{width:36px;height:36px;padding:0;justify-content:center;flex:0 0 auto}.chat-sidenav__topbar .chat-sidenav__action--close .chat-sidenav__action-label{display:none}.chat-sidenav__action--new{border:0;padding:12px 18px;border-radius:8px;font-size:var(--tplane-chat-font-size-sm);font-weight:600;display:flex;align-items:center;gap:8px;cursor:pointer;width:100%}.chat-sidenav__action--new:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new .chat-sidenav__action-label{display:none}.chat-sidenav__actions{flex-shrink:0;display:flex;flex-direction:column;gap:4px;padding:var(--tplane-chat-space-3)}:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{align-items:center;padding:var(--tplane-chat-space-2)}.chat-sidenav__action{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;background:transparent;color:var(--tplane-chat-text);border-radius:8px;cursor:pointer;font-family:inherit;font-size:var(--tplane-chat-font-size-sm);font-weight:400;text-align:left}.chat-sidenav__action:hover{background:var(--tplane-chat-surface-alt)}.chat-sidenav__action:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action{width:36px;height:36px;padding:0;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action-label{display:none}.chat-sidenav__action.chat-sidenav__action--new{background:var(--tplane-chat-text);color:var(--tplane-chat-bg);border-radius:8px;padding:12px 18px;font-weight:600;font-size:var(--tplane-chat-font-size-sm)}.chat-sidenav__action.chat-sidenav__action--new:hover{background:var(--tplane-chat-text);filter:brightness(.92)}.chat-sidenav__action-icon{width:16px;height:16px;flex-shrink:0}.chat-sidenav__primary,.chat-sidenav__sections{flex-shrink:0}.chat-sidenav__threads{flex:1 1 auto;min-height:0;overflow-y:auto}.chat-sidenav__threads-heading{padding:8px 12px 4px;font-size:11px;color:var(--tplane-chat-text-muted);text-transform:uppercase;letter-spacing:.4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads-heading{display:none}.chat-sidenav__footer{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-top:1px solid var(--tplane-chat-separator);gap:8px;flex-shrink:0}.chat-sidenav__footer-left{display:flex;align-items:center;gap:4px;min-height:28px}.chat-sidenav__debug{height:28px;min-width:28px;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;font:inherit;font-size:12px;font-weight:500}.chat-sidenav__debug:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__debug:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__debug-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 8px color-mix(in srgb,#22c55e 60%,transparent);flex:0 0 auto}.chat-sidenav__debug-dot--streaming{animation:chat-sidenav-debug-pulse 1.2s ease-in-out infinite}@keyframes chat-sidenav-debug-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.85)}}.chat-sidenav__footer-right{display:flex;align-items:center;gap:4px;margin-left:auto}.chat-sidenav__toggle{width:28px;height:28px;border-radius:8px;border:0;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center}.chat-sidenav__toggle:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer{flex-direction:column;align-items:center;padding:10px 4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left{display:flex;flex-direction:column;align-items:center;min-height:28px}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug{width:28px;padding:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug-label,:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left>:not(.chat-sidenav__debug){display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-right{flex-direction:column}.chat-sidenav__account{display:none}.chat-sidenav__account:has(>*){display:flex;align-items:center;gap:4px}.chat-sidenav__archived{flex-shrink:0}.chat-sidenav__archived-heading{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:var(--tplane-chat-font-size-sm);text-align:left;cursor:pointer}.chat-sidenav__archived-heading:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__archived-heading:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__archived-chevron{width:12px;height:12px;transition:transform .15s ease;flex-shrink:0}.chat-sidenav__archived[data-open=true] .chat-sidenav__archived-chevron{transform:rotate(90deg)}.chat-sidenav__archived-empty{padding:8px 12px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-sm)}:host([data-mode=\"collapsed\"]) .chat-sidenav__archived{display:none}.chat-sidenav__projects{flex-shrink:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__projects{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__initial{display:inline-flex}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-title{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-time{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__kebab{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__grip{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-wrap{justify-content:center}:host([data-mode=\"collapsed\"]) .chat-thread-list__item{padding:6px;justify-content:center;align-items:center;flex-direction:row}:host([data-mode=\"collapsed\"]) .chat-thread-list__new{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center;background:transparent}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search .chat-sidenav__action-label{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads,:host([data-mode=\"collapsed\"]) .chat-sidenav__projects,:host([data-mode=\"collapsed\"]) .chat-sidenav__archived,:host([data-mode=\"collapsed\"]) .chat-sidenav__sections,:host([data-mode=\"collapsed\"]) .chat-sidenav__primary{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar,:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{padding:8px 4px;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "component", type: ChatThreadListComponent, selector: "chat-thread-list", inputs: ["threads", "activeThreadId", "showNewThreadButton", "actions", "mode", "projects"], outputs: ["threadSelected", "newThreadRequested"] }, { kind: "component", type: ChatProjectListComponent, selector: "chat-project-list", inputs: ["projects", "activeProjectId", "showNewProjectButton", "actions"], outputs: ["projectSelected", "newProjectRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11994
12890
  }
11995
12891
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ChatSidenavComponent, decorators: [{
11996
12892
  type: Component,
@@ -12234,7 +13130,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
12234
13130
  </div>
12235
13131
  </nav>
12236
13132
  <ng-container #debugHost />
12237
- `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;height:100%}:host([data-mode=\"expanded\"]),:host([data-mode=\"collapsed\"]){position:fixed;top:0;left:0;bottom:0;z-index:100}:host([data-mode=\"expanded\"]){width:var(--tplane-chat-sidenav-width-expanded)}:host([data-mode=\"collapsed\"]){width:var(--tplane-chat-sidenav-width-collapsed)}:host([data-mode=\"expanded\"]) .chat-sidenav{width:100%}:host([data-mode=\"collapsed\"]) .chat-sidenav{width:100%}:host([data-mode=\"drawer\"]){position:fixed;top:0;left:0;bottom:0;width:var(--tplane-chat-sidenav-width-drawer);z-index:var(--tplane-chat-z-drawer, 1001)}:host([data-mode=\"drawer\"][data-open=\"true\"]){box-shadow:8px 0 32px #0000002e}.chat-sidenav{display:flex;flex-direction:column;height:100%;background:var(--tplane-chat-bg);border-right:1px solid var(--tplane-chat-separator);box-sizing:border-box;overflow:hidden}:host([data-mode=\"drawer\"]) .chat-sidenav{width:100%;height:100%;transition:transform .2s ease;transform:translate(-100%)}:host([data-mode=\"drawer\"][data-open=\"true\"]) .chat-sidenav{transform:translate(0)}.chat-sidenav__header{flex-shrink:0;padding:var(--tplane-chat-space-3)}.chat-sidenav__header:empty{display:none}.chat-sidenav__topbar{flex-shrink:0;display:flex;flex-direction:row;align-items:center;justify-content:space-between;gap:4px;padding:var(--tplane-chat-space-2) var(--tplane-chat-space-3);border-bottom:1px solid var(--tplane-chat-separator)}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar{flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:var(--tplane-chat-space-2) 0}.chat-sidenav__topbar .chat-sidenav__action--close{width:36px;height:36px;padding:0;justify-content:center;flex:0 0 auto}.chat-sidenav__topbar .chat-sidenav__action--close .chat-sidenav__action-label{display:none}.chat-sidenav__action--new{border:0;padding:12px 18px;border-radius:8px;font-size:var(--tplane-chat-font-size-sm);font-weight:600;display:flex;align-items:center;gap:8px;cursor:pointer;width:100%}.chat-sidenav__action--new:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new .chat-sidenav__action-label{display:none}.chat-sidenav__actions{flex-shrink:0;display:flex;flex-direction:column;gap:4px;padding:var(--tplane-chat-space-3)}:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{align-items:center;padding:var(--tplane-chat-space-2)}.chat-sidenav__action{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;background:transparent;color:var(--tplane-chat-text);border-radius:8px;cursor:pointer;font-family:inherit;font-size:var(--tplane-chat-font-size-sm);font-weight:400;text-align:left}.chat-sidenav__action:hover{background:var(--tplane-chat-surface-alt)}.chat-sidenav__action:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action{width:36px;height:36px;padding:0;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action-label{display:none}.chat-sidenav__action.chat-sidenav__action--new{background:var(--tplane-chat-text);color:var(--tplane-chat-bg);border-radius:8px;padding:12px 18px;font-weight:600;font-size:var(--tplane-chat-font-size-sm)}.chat-sidenav__action.chat-sidenav__action--new:hover{background:var(--tplane-chat-text);filter:brightness(.92)}.chat-sidenav__action-icon{width:16px;height:16px;flex-shrink:0}.chat-sidenav__primary,.chat-sidenav__sections{flex-shrink:0}.chat-sidenav__threads{flex:1 1 auto;min-height:0;overflow-y:auto}.chat-sidenav__threads-heading{padding:8px 12px 4px;font-size:11px;color:var(--tplane-chat-text-muted);text-transform:uppercase;letter-spacing:.4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads-heading{display:none}.chat-sidenav__footer{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-top:1px solid var(--tplane-chat-separator);gap:8px;flex-shrink:0}.chat-sidenav__footer-left{display:flex;align-items:center;gap:4px;min-height:28px}.chat-sidenav__debug{height:28px;min-width:28px;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;font:inherit;font-size:12px;font-weight:500}.chat-sidenav__debug:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__debug:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__debug-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 8px color-mix(in srgb,#22c55e 60%,transparent);flex:0 0 auto}.chat-sidenav__debug-dot--streaming{animation:chat-sidenav-debug-pulse 1.2s ease-in-out infinite}@keyframes chat-sidenav-debug-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.85)}}.chat-sidenav__footer-right{display:flex;align-items:center;gap:4px;margin-left:auto}.chat-sidenav__toggle{width:28px;height:28px;border-radius:8px;border:0;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center}.chat-sidenav__toggle:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer{flex-direction:column;align-items:center;padding:10px 4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left{display:flex;flex-direction:column;align-items:center;min-height:28px}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug{width:28px;padding:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug-label,:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left>:not(.chat-sidenav__debug){display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-right{flex-direction:column}.chat-sidenav__account{display:none}.chat-sidenav__account:has(>*){display:flex;align-items:center;gap:4px}.chat-sidenav__archived{flex-shrink:0}.chat-sidenav__archived-heading{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:var(--tplane-chat-font-size-sm);text-align:left;cursor:pointer}.chat-sidenav__archived-heading:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__archived-heading:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__archived-chevron{width:12px;height:12px;transition:transform .15s ease;flex-shrink:0}.chat-sidenav__archived[data-open=true] .chat-sidenav__archived-chevron{transform:rotate(90deg)}.chat-sidenav__archived-empty{padding:8px 12px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-sm)}:host([data-mode=\"collapsed\"]) .chat-sidenav__archived{display:none}.chat-sidenav__projects{flex-shrink:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__projects{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__initial{display:inline-flex}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-title{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-time{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__kebab{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__grip{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-wrap{justify-content:center}:host([data-mode=\"collapsed\"]) .chat-thread-list__item{padding:6px;justify-content:center;align-items:center;flex-direction:row}:host([data-mode=\"collapsed\"]) .chat-thread-list__new{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center;background:transparent}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search .chat-sidenav__action-label{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads,:host([data-mode=\"collapsed\"]) .chat-sidenav__projects,:host([data-mode=\"collapsed\"]) .chat-sidenav__archived,:host([data-mode=\"collapsed\"]) .chat-sidenav__sections,:host([data-mode=\"collapsed\"]) .chat-sidenav__primary{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar,:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{padding:8px 4px;align-items:center;justify-content:center}\n"] }]
13133
+ `, styles: [":host{font-family:var(--tplane-chat-font-family);color:var(--tplane-chat-text)}\n", ":host{display:block;height:100%}:host([data-mode=\"expanded\"]),:host([data-mode=\"collapsed\"]){position:fixed;top:0;left:0;bottom:0;z-index:100}:host([data-mode=\"expanded\"]){width:var(--tplane-chat-sidenav-width-expanded)}:host([data-mode=\"collapsed\"]){width:var(--tplane-chat-sidenav-width-collapsed)}:host([data-mode=\"expanded\"]) .chat-sidenav{width:100%}:host([data-mode=\"collapsed\"]) .chat-sidenav{width:100%}:host([data-mode=\"drawer\"]){position:fixed;top:0;left:0;bottom:0;width:var(--tplane-chat-sidenav-width-drawer);z-index:var(--tplane-chat-z-drawer, 1001);pointer-events:none}:host([data-mode=\"drawer\"][data-open=\"true\"]){box-shadow:8px 0 32px #0000002e}.chat-sidenav{display:flex;flex-direction:column;height:100%;background:var(--tplane-chat-bg);border-right:1px solid var(--tplane-chat-separator);box-sizing:border-box;overflow:hidden}:host([data-mode=\"drawer\"]) .chat-sidenav{width:100%;height:100%;transition:transform .2s ease;transform:translate(-100%);pointer-events:none}:host([data-mode=\"drawer\"][data-open=\"true\"]) .chat-sidenav{transform:translate(0);pointer-events:auto}.chat-sidenav__header{flex-shrink:0;padding:var(--tplane-chat-space-3)}.chat-sidenav__header:empty{display:none}.chat-sidenav__topbar{flex-shrink:0;display:flex;flex-direction:row;align-items:center;justify-content:space-between;gap:4px;padding:var(--tplane-chat-space-2) var(--tplane-chat-space-3);border-bottom:1px solid var(--tplane-chat-separator)}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar{flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:var(--tplane-chat-space-2) 0}.chat-sidenav__topbar .chat-sidenav__action--close{width:36px;height:36px;padding:0;justify-content:center;flex:0 0 auto}.chat-sidenav__topbar .chat-sidenav__action--close .chat-sidenav__action-label{display:none}.chat-sidenav__action--new{border:0;padding:12px 18px;border-radius:8px;font-size:var(--tplane-chat-font-size-sm);font-weight:600;display:flex;align-items:center;gap:8px;cursor:pointer;width:100%}.chat-sidenav__action--new:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--new .chat-sidenav__action-label{display:none}.chat-sidenav__actions{flex-shrink:0;display:flex;flex-direction:column;gap:4px;padding:var(--tplane-chat-space-3)}:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{align-items:center;padding:var(--tplane-chat-space-2)}.chat-sidenav__action{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;background:transparent;color:var(--tplane-chat-text);border-radius:8px;cursor:pointer;font-family:inherit;font-size:var(--tplane-chat-font-size-sm);font-weight:400;text-align:left}.chat-sidenav__action:hover{background:var(--tplane-chat-surface-alt)}.chat-sidenav__action:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}:host([data-mode=\"collapsed\"]) .chat-sidenav__action{width:36px;height:36px;padding:0;justify-content:center}:host([data-mode=\"collapsed\"]) .chat-sidenav__action-label{display:none}.chat-sidenav__action.chat-sidenav__action--new{background:var(--tplane-chat-text);color:var(--tplane-chat-bg);border-radius:8px;padding:12px 18px;font-weight:600;font-size:var(--tplane-chat-font-size-sm)}.chat-sidenav__action.chat-sidenav__action--new:hover{background:var(--tplane-chat-text);filter:brightness(.92)}.chat-sidenav__action-icon{width:16px;height:16px;flex-shrink:0}.chat-sidenav__primary,.chat-sidenav__sections{flex-shrink:0}.chat-sidenav__threads{flex:1 1 auto;min-height:0;overflow-y:auto}.chat-sidenav__threads-heading{padding:8px 12px 4px;font-size:11px;color:var(--tplane-chat-text-muted);text-transform:uppercase;letter-spacing:.4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads-heading{display:none}.chat-sidenav__footer{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-top:1px solid var(--tplane-chat-separator);gap:8px;flex-shrink:0}.chat-sidenav__footer-left{display:flex;align-items:center;gap:4px;min-height:28px}.chat-sidenav__debug{height:28px;min-width:28px;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;font:inherit;font-size:12px;font-weight:500}.chat-sidenav__debug:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__debug:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__debug-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 8px color-mix(in srgb,#22c55e 60%,transparent);flex:0 0 auto}.chat-sidenav__debug-dot--streaming{animation:chat-sidenav-debug-pulse 1.2s ease-in-out infinite}@keyframes chat-sidenav-debug-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.85)}}.chat-sidenav__footer-right{display:flex;align-items:center;gap:4px;margin-left:auto}.chat-sidenav__toggle{width:28px;height:28px;border-radius:8px;border:0;background:transparent;color:var(--tplane-chat-text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center}.chat-sidenav__toggle:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer{flex-direction:column;align-items:center;padding:10px 4px}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left{display:flex;flex-direction:column;align-items:center;min-height:28px}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug{width:28px;padding:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__debug-label,:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-left>:not(.chat-sidenav__debug){display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__footer-right{flex-direction:column}.chat-sidenav__account{display:none}.chat-sidenav__account:has(>*){display:flex;align-items:center;gap:4px}.chat-sidenav__archived{flex-shrink:0}.chat-sidenav__archived-heading{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:0;border-radius:8px;background:transparent;color:var(--tplane-chat-text);font:inherit;font-size:var(--tplane-chat-font-size-sm);text-align:left;cursor:pointer}.chat-sidenav__archived-heading:hover{background:var(--tplane-chat-surface-alt);color:var(--tplane-chat-text)}.chat-sidenav__archived-heading:focus-visible{outline:2px solid var(--tplane-chat-primary);outline-offset:2px}.chat-sidenav__archived-chevron{width:12px;height:12px;transition:transform .15s ease;flex-shrink:0}.chat-sidenav__archived[data-open=true] .chat-sidenav__archived-chevron{transform:rotate(90deg)}.chat-sidenav__archived-empty{padding:8px 12px;color:var(--tplane-chat-text-muted);font-size:var(--tplane-chat-font-size-sm)}:host([data-mode=\"collapsed\"]) .chat-sidenav__archived{display:none}.chat-sidenav__projects{flex-shrink:0}:host([data-mode=\"collapsed\"]) .chat-sidenav__projects{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__initial{display:inline-flex}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-title{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-time{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__kebab{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__grip{display:none}:host([data-mode=\"collapsed\"]) .chat-thread-list__item-wrap{justify-content:center}:host([data-mode=\"collapsed\"]) .chat-thread-list__item{padding:6px;justify-content:center;align-items:center;flex-direction:row}:host([data-mode=\"collapsed\"]) .chat-thread-list__new{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search{width:32px;height:32px;padding:0;border-radius:10px;justify-content:center;background:transparent}:host([data-mode=\"collapsed\"]) .chat-sidenav__action--search .chat-sidenav__action-label{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__threads,:host([data-mode=\"collapsed\"]) .chat-sidenav__projects,:host([data-mode=\"collapsed\"]) .chat-sidenav__archived,:host([data-mode=\"collapsed\"]) .chat-sidenav__sections,:host([data-mode=\"collapsed\"]) .chat-sidenav__primary{display:none}:host([data-mode=\"collapsed\"]) .chat-sidenav__topbar,:host([data-mode=\"collapsed\"]) .chat-sidenav__actions{padding:8px 4px;align-items:center;justify-content:center}\n"] }]
12238
13134
  }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], activeThreadId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeThreadId", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], archivedThreads: [{ type: i0.Input, args: [{ isSignal: true, alias: "archivedThreads", required: false }] }], projects: [{ type: i0.Input, args: [{ isSignal: true, alias: "projects", required: false }] }], selectedProjectId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedProjectId", required: false }] }], projectActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "projectActions", required: false }] }], agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], newChat: [{ type: i0.Output, args: ["newChat"] }], threadSelected: [{ type: i0.Output, args: ["threadSelected"] }], searchOpened: [{ type: i0.Output, args: ["searchOpened"] }], openChange: [{ type: i0.Output, args: ["openChange"] }], modeChange: [{ type: i0.Output, args: ["modeChange"] }], projectSelected: [{ type: i0.Output, args: ["projectSelected"] }], newProjectRequested: [{ type: i0.Output, args: ["newProjectRequested"] }], debugHost: [{ type: i0.ViewChild, args: ['debugHost', { ...{
12239
13135
  read: ViewContainerRef,
12240
13136
  }, isSignal: true }] }] } });
@@ -13612,8 +14508,11 @@ function a2uiBasicCatalog() {
13612
14508
  * @param description Natural-language description the model sees.
13613
14509
  * @param schema Standard Schema (e.g. a Zod object) for the arguments; the
13614
14510
  * handler's argument type is inferred from it.
13615
- * @param handler Runs in the browser when the model calls the tool; its return
13616
- * type `R` is carried on the resulting {@link FunctionToolDef}.
14511
+ * @param handler Runs in the browser when the model calls the tool. The second
14512
+ * argument carries an `AbortSignal`; its return type `R` is carried on the
14513
+ * resulting {@link FunctionToolDef}.
14514
+ * @param options Execution policy options, including `idempotent: true` to skip
14515
+ * durable pre-execution claims when a guard is configured.
13617
14516
  * @returns A {@link FunctionToolDef} for inclusion in {@link tools}.
13618
14517
  * @example
13619
14518
  * ```ts
@@ -13621,8 +14520,8 @@ function a2uiBasicCatalog() {
13621
14520
  * const registry = tools({ move_stop: move });
13622
14521
  * ```
13623
14522
  */
13624
- function action(description, schema, handler) {
13625
- return { kind: 'function', description, schema, handler };
14523
+ function action(description, schema, handler, options = {}) {
14524
+ return { kind: 'function', description, schema, handler, ...options };
13626
14525
  }
13627
14526
  /**
13628
14527
  * Render-only component tool — the model fills the component's props from the
@@ -13654,8 +14553,8 @@ function action(description, schema, handler) {
13654
14553
  * const registry = tools({ day_card: dayCard });
13655
14554
  * ```
13656
14555
  */
13657
- function view(description, schema, component) {
13658
- return { kind: 'view', description, schema, component: component };
14556
+ function view(description, schema, component, options = {}) {
14557
+ return { kind: 'view', description, schema, component: component, ...options };
13659
14558
  }
13660
14559
  /**
13661
14560
  * Interactive (human-in-the-loop) component tool — the model fills the
@@ -13689,8 +14588,8 @@ function view(description, schema, component) {
13689
14588
  * const registry = tools({ pick_option: choice });
13690
14589
  * ```
13691
14590
  */
13692
- function ask(description, schema, component) {
13693
- return { kind: 'ask', description, schema, component: component };
14591
+ function ask(description, schema, component, options = {}) {
14592
+ return { kind: 'ask', description, schema, component: component, ...options };
13694
14593
  }
13695
14594
  /**
13696
14595
  * Collect named client tools into a frozen, name-keyed registry.
@@ -13718,6 +14617,15 @@ function tools(map) {
13718
14617
  return Object.freeze({ ...map });
13719
14618
  }
13720
14619
 
14620
+ /** Select client tool calls that are ready for browser-side resolution. */
14621
+ function selectPendingClientToolCalls(input) {
14622
+ if (input.isLoading)
14623
+ return [];
14624
+ return input.toolCalls.filter((tc) => input.catalogNames.has(tc.name) &&
14625
+ tc.result === undefined &&
14626
+ !input.resolvedIds.has(tc.id));
14627
+ }
14628
+
13721
14629
  // SPDX-License-Identifier: MIT
13722
14630
  /**
13723
14631
  * Build an in-memory {@link Agent} for tests and stories — no transport, no
@@ -13728,8 +14636,10 @@ function tools(map) {
13728
14636
  * @returns A {@link MockAgent} satisfying the full `Agent` contract.
13729
14637
  * @example
13730
14638
  * ```ts
14639
+ * import { staticDelivery } from '@threadplane/chat';
14640
+ *
13731
14641
  * const agent = mockAgent({
13732
- * messages: [{ id: '1', role: 'assistant', content: 'Hi' }],
14642
+ * messages: [{ id: '1', role: 'assistant', content: 'Hi', delivery: staticDelivery('1') }],
13733
14643
  * isLoading: true,
13734
14644
  * });
13735
14645
  * ```
@@ -13785,5 +14695,5 @@ function mockAgent(opts = {}) {
13785
14695
  * Generated bundle index. Do not edit.
13786
14696
  */
13787
14697
 
13788
- export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, AGENT_ERROR_MESSAGES, AgentError, CHAT_CONFIG, CHAT_LIFECYCLE, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationPreviewComponent, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatConnectedOverlayDirective, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatOverlayOriginDirective, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownHtmlComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownMathComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, a2uiBasicCatalog, action, ask, buildA2uiActionMessage, cacheplaneMarkdownViews, citationTypeLabel, createA2uiSurfaceStore, createAgentRef, createContentClassifier, createParseTreeStore, createPartialArgsBridge, deriveDomain, deriveJsonSchema, deriveMonogram, deriveSourceType, emitBinding, executeFunctionTool, extractErrorMessage, formatDuration, formatPublished, getInterrupt, getMessageType, injectThreadRouting, isAbortError, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, messageContent, mockAgent, monogramColor, monogramHue, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, startClientToolExecutor, statusColor, submitMessage, toAgentError, toClientToolSpecs, tools, validateArgs, view };
14698
+ export { A2uiAudioPlayerComponent, A2uiButtonComponent, A2uiCardComponent, A2uiCheckBoxComponent, A2uiColumnComponent, A2uiDateTimeInputComponent, A2uiDividerComponent, A2uiIconComponent, A2uiImageComponent, A2uiListComponent, A2uiModalComponent, A2uiMultipleChoiceComponent, A2uiRowComponent, A2uiSliderComponent, A2uiSurfaceComponent, A2uiTabsComponent, A2uiTextComponent, A2uiTextFieldComponent, A2uiVideoComponent, AGENT_ERROR_MESSAGES, AgentError, CHAT_CONFIG, CHAT_LIFECYCLE, ChatApprovalCardComponent, ChatCitationCardTemplateDirective, ChatCitationPreviewComponent, ChatCitationsCardComponent, ChatCitationsComponent, ChatComponent, ChatConfirmDialogComponent, ChatConnectedOverlayDirective, ChatErrorComponent, ChatGenerativeUiComponent, ChatGenuiSkeletonComponent, ChatHistorySearchPaletteComponent, ChatInputComponent, ChatInterruptComponent, ChatInterruptPanelComponent, ChatLauncherButtonComponent, ChatMessageActionsComponent, ChatMessageComponent, ChatMessageListComponent, ChatOverflowMenuComponent, ChatOverlayOriginDirective, ChatPopupComponent, ChatProjectListComponent, ChatReasoningComponent, ChatScrollBubbleComponent, ChatSelectComponent, ChatSidebarComponent, ChatSidenavComponent, ChatSidenavScrimComponent, ChatStreamingMdComponent, ChatSubagentCardComponent, ChatSubagentsComponent, ChatSuggestionsComponent, ChatThreadListComponent, ChatTimelineComponent, ChatTimelineSliderComponent, ChatToolCallCardComponent, ChatToolCallTemplateDirective, ChatToolCallsComponent, ChatToolViewsComponent, ChatTraceComponent, ChatTypingIndicatorComponent, ChatWelcomeComponent, ChatWelcomeSuggestionComponent, ChatWindowComponent, CitationsResolverService, IS_HEADER_ROW, MARKDOWN_VIEW_REGISTRY, MarkdownAutolinkComponent, MarkdownBlockquoteComponent, MarkdownChildrenComponent, MarkdownCitationReferenceComponent, MarkdownCodeBlockComponent, MarkdownDocumentComponent, MarkdownEmphasisComponent, MarkdownHardBreakComponent, MarkdownHeadingComponent, MarkdownHtmlComponent, MarkdownImageComponent, MarkdownInlineCodeComponent, MarkdownLinkComponent, MarkdownListComponent, MarkdownListItemComponent, MarkdownMathComponent, MarkdownParagraphComponent, MarkdownSoftBreakComponent, MarkdownStrikethroughComponent, MarkdownStrongComponent, MarkdownTableCellComponent, MarkdownTableComponent, MarkdownTableRowComponent, MarkdownTextComponent, MarkdownThematicBreakComponent, MessageTemplateDirective, STREAMING_MARKDOWN_CONTRACT_VIOLATION_POLICY, a2uiBasicCatalog, action, ask, buildA2uiActionMessage, cacheplaneMarkdownViews, cancelledClientToolResult, citationSourceVisual, citationTypeLabel, citationTypeMeta, clientToolGuardFailureResult, completeDelivery, createA2uiSurfaceStore, createAgentRef, createContentClassifier, createParseTreeStore, createPartialArgsBridge, defaultInterruptedClientToolResult, deriveDomain, deriveJsonSchema, deriveMonogram, deriveSourceType, emitBinding, executeFunctionTool, extractErrorMessage, formatDuration, formatPublished, getInterrupt, getMessageType, injectThreadRouting, isAbortError, isAssistantMessage, isSystemMessage, isToolMessage, isTyping, isUserMessage, markdownDocument, messageContent, mockAgent, monogramColor, monogramHue, normalizeEnvelopeArgs, normalizeViewEntry, provideChat, renderMarkdown, selectPendingClientToolCalls, shouldClaimBeforeExecute, startClientToolExecutor, staticDelivery, statusColor, streamingDelivery, submitMessage, toAgentError, toClientToolSpecs, tools, validateArgs, view };
13789
14699
  //# sourceMappingURL=threadplane-chat.mjs.map