@adecore/agents-react 0.36.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/agents/provider-paths.js +3 -1
  2. package/dist/chat/chat-client.d.ts +5 -0
  3. package/dist/chat/chat-client.js +91 -20
  4. package/dist/chat/index.d.ts +1 -0
  5. package/dist/chat/index.js +2 -1
  6. package/dist/chat/logic/picker-entries.js +11 -6
  7. package/dist/chat/logic/scrubber.d.ts +4 -2
  8. package/dist/chat/logic/scrubber.js +3 -3
  9. package/dist/chat/pending-prompts.d.ts +15 -0
  10. package/dist/chat/pending-prompts.js +93 -0
  11. package/dist/chat/reading-width.js +6 -2
  12. package/dist/chat/ui/PromptComposer.d.ts +2 -2
  13. package/dist/chat/ui/PromptComposer.js +92 -98
  14. package/dist/chat/ui/SubagentTimeline.js +128 -182
  15. package/dist/chat/ui/Timeline.js +86 -60
  16. package/dist/chat/ui/code-lines.d.ts +5 -0
  17. package/dist/chat/ui/code-lines.js +37 -0
  18. package/dist/chat/ui/composer/use-pending-requests.d.ts +2 -2
  19. package/dist/chat/ui/composer/use-pending-requests.js +29 -37
  20. package/dist/chat/ui/highlighter.d.ts +1 -1
  21. package/dist/chat/ui/highlighter.js +4 -2
  22. package/dist/locales/en/agent-chat.d.ts +1 -0
  23. package/dist/locales/en/agent-chat.js +1 -0
  24. package/dist/locales/nl/agent-chat.d.ts +1 -0
  25. package/dist/locales/nl/agent-chat.js +1 -0
  26. package/dist/prompts/logic/subjects.d.ts +5 -0
  27. package/dist/prompts/ui/PromptCard.js +1 -1
  28. package/dist/prompts/ui/PromptView.js +104 -95
  29. package/dist/providers/parts.js +2 -1
  30. package/dist/state/chats.d.ts +4 -0
  31. package/dist/state/chats.js +84 -48
  32. package/dist/theme.css +6 -2
  33. package/dist/usage/format.js +4 -2
  34. package/package.json +8 -8
  35. package/src/agents/provider-paths.ts +5 -1
  36. package/src/chat/chat-client.ts +92 -21
  37. package/src/chat/index.ts +2 -0
  38. package/src/chat/logic/picker-entries.ts +6 -4
  39. package/src/chat/logic/scrubber.ts +8 -6
  40. package/src/chat/pending-prompts.ts +108 -0
  41. package/src/chat/reading-width.ts +9 -2
  42. package/src/chat/ui/PromptComposer.tsx +10 -7
  43. package/src/chat/ui/SubagentTimeline.tsx +1 -24
  44. package/src/chat/ui/Timeline.tsx +66 -40
  45. package/src/chat/ui/code-lines.ts +37 -0
  46. package/src/chat/ui/composer/use-pending-requests.ts +13 -21
  47. package/src/chat/ui/highlighter.ts +4 -3
  48. package/src/locales/en/agent-chat.ts +1 -0
  49. package/src/locales/nl/agent-chat.ts +1 -0
  50. package/src/prompts/logic/subjects.ts +4 -1
  51. package/src/prompts/ui/PromptCard.tsx +1 -1
  52. package/src/prompts/ui/PromptView.tsx +7 -1
  53. package/src/providers/parts.tsx +2 -1
  54. package/src/state/chats.ts +63 -18
  55. package/src/theme.css +6 -2
  56. package/src/usage/format.ts +4 -2
@@ -1,7 +1,7 @@
1
1
  import { c as _c } from "react/compiler-runtime";
2
2
  import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { useVirtualizer } from '@tanstack/react-virtual';
4
+ import { defaultRangeExtractor, useVirtualizer } from '@tanstack/react-virtual';
5
5
  import clsx from 'clsx';
6
6
  import { LoaderCircle } from 'lucide-react';
7
7
  import { useForkedTurns } from '../forks.js';
@@ -409,7 +409,8 @@ export function Timeline({
409
409
  const visuals = useChatRow(chatId, row_3 => row_3?.visuals);
410
410
  const forkedTurns = useForkedTurns(chatId);
411
411
  const info = useChatRow(chatId, row_4 => row_4?.info ?? null);
412
- const cursor = useChatRow(chatId, row_5 => row_5?.history?.cursor ?? null);
412
+ const historyPending = useChatRow(chatId, row_5 => row_5?.historyPending === true);
413
+ const cursor = useChatRow(chatId, row_6 => row_6?.history?.cursor ?? null);
413
414
  const scope = useChatScope();
414
415
  const {
415
416
  id: scopeId,
@@ -456,9 +457,6 @@ export function Timeline({
456
457
  });
457
458
  const [loadingEarlier, setLoadingEarlier] = useState(false);
458
459
  const loadingEarlierRef = useRef(false);
459
- // The row being read and where it stood, taken after every render, so a page going in above puts it back.
460
- const anchorRef = useRef(null);
461
- const headRef = useRef(undefined);
462
460
  const loadEarlier = useCallback(() => {
463
461
  if (cursor === null || loadingEarlierRef.current) {
464
462
  return;
@@ -490,9 +488,10 @@ export function Timeline({
490
488
  const heldFrom = cursor === null ? null : items?.[order?.[0] ?? '']?.createdAt ?? null;
491
489
  const rows = useMemo(() => withTimedRows(threadRows, timedRowsOf(cards, drawsVisuals ? visuals ?? [] : null, heldFrom)), [threadRows, cards, drawsVisuals, visuals, heldFrom]);
492
490
  const welcomes = useHostWelcome(chatId);
491
+ const loadingHistory = historyPending && (info !== null && (info.usage.turns > 0 || info.activeTurnId !== null) || (order?.length ?? 0) > 0);
493
492
  const empty = rows.length === 0;
494
493
  // A chat that took a turn before is one whose thread has not come in yet, not one to greet.
495
- const welcome = welcomes && empty && (info === null || info.usage.turns === 0 && info.activeTurnId === null);
494
+ const welcome = !loadingHistory && welcomes && empty && (info === null || info.usage.turns === 0 && info.activeTurnId === null);
496
495
  const marks = useMemo(() => bookmarkRows(rows, bookmarks ?? [], items ?? {}), [rows, bookmarks, items]);
497
496
  const ticks = useMemo(() => ticksOf(rows, marks), [rows, marks]);
498
497
  // A message a jump is on its way to; it may first have to open the turn its reply folded into.
@@ -519,7 +518,7 @@ export function Timeline({
519
518
  } : null
520
519
  }), [revealBlock, flash, keyOf, chatId]);
521
520
  // Whether the thread's text clears the strip depends on the width of the view, which only the thread's own frame tells.
522
- useEffect(() => {
521
+ useLayoutEffect(() => {
523
522
  const element = frameRef.current;
524
523
  if (element === null || typeof ResizeObserver === 'undefined') {
525
524
  return;
@@ -555,14 +554,32 @@ export function Timeline({
555
554
  useEffect(() => registerTimeline(chatId, scrollRef.current, enabled => {
556
555
  followRef.current = enabled;
557
556
  }, () => coveredHeightRef.current), [chatId]);
557
+ const [measuredThread, setMeasuredThread] = useState(null);
558
+ const initialLayout = measuredThread !== keyOf(chatId);
559
+ const [, settleLayout] = useReducer(count => count + 1, 0);
560
+ // Changing this callback invalidates every cached measurement, including on a scroll.
561
+ const getItemKey = useCallback(index => rows[index].id, [rows]);
558
562
  // Nothing here builds with the React Compiler, so its memoization rule has nothing to break.
559
563
  // oxlint-disable-next-line react/incompatible-library
560
564
  const virtualizer = useVirtualizer({
561
- count: rows.length,
565
+ count: loadingHistory ? 0 : rows.length,
562
566
  getScrollElement: () => scrollRef.current,
563
567
  estimateSize: () => ESTIMATED_ROW_PX,
564
- getItemKey: index => rows[index].id,
568
+ getItemKey,
565
569
  overscan: 8,
570
+ anchorTo: 'end',
571
+ // Include the end before the first pin, so its actual heights are known before revealing it.
572
+ rangeExtractor: range => {
573
+ const indexes = defaultRangeExtractor(range);
574
+ if (!initialLayout) {
575
+ return indexes;
576
+ }
577
+ const tail = Math.max(8, Math.ceil(frame.height / ESTIMATED_ROW_PX));
578
+ return [...new Set([...indexes, ...Array.from({
579
+ length: Math.min(tail, range.count)
580
+ }, (_, index_0) => range.count - 1 - index_0)])].sort((left, right) => left - right);
581
+ },
582
+ measureElement: (element_1, entry) => Math.round(entry?.borderBoxSize?.[0]?.blockSize ?? element_1.offsetHeight),
566
583
  scrollPaddingEnd: coveredHeight
567
584
  });
568
585
  /*
@@ -572,42 +589,34 @@ export function Timeline({
572
589
  * A word that does not add a line moves nothing, so the thread does not listen to the words.
573
590
  */
574
591
  const totalSize = virtualizer.getTotalSize();
592
+ const virtualRows = virtualizer.getVirtualItems();
575
593
  useLayoutEffect(() => {
576
- const element_1 = scrollRef.current;
577
- if (!followRef.current || element_1 === null) {
594
+ if (!initialLayout || empty || loadingHistory || frame.height === 0) {
578
595
  return;
579
596
  }
580
- // The composer is in the scroll flow, so its measured height is part of this end position.
581
- element_1.scrollTop = element_1.scrollHeight;
582
- scrollGeometry.current = {
583
- top: element_1.scrollTop,
584
- height: element_1.scrollHeight,
585
- viewport: element_1.clientHeight
586
- };
587
- }, [rows.length, totalSize, composerHeight, frame.height]);
588
- // A page that went in above moves every row down by what it measures; the reader stays on the row they were on.
589
- useLayoutEffect(() => {
590
- const element_2 = scrollRef.current;
591
- const head = order?.[0];
592
- const moved = head !== headRef.current;
593
- headRef.current = head;
594
- if (element_2 === null) {
597
+ // Ref measurements can change offsets after render. Commit those offsets before the first paint.
598
+ const measured = virtualizer.getVirtualItems();
599
+ if (virtualRows.length !== measured.length || virtualRows.some((row_7, index_1) => row_7.key !== measured[index_1]?.key || row_7.start !== measured[index_1]?.start || row_7.size !== measured[index_1]?.size)) {
600
+ settleLayout();
595
601
  return;
596
602
  }
597
- const anchor = anchorRef.current;
598
- const index_0 = moved && anchor !== null && !followRef.current ? rows.findIndex(row_6 => row_6.id === anchor.id) : -1;
599
- if (anchor !== null && index_0 !== -1) {
600
- // The total measures again what this commit's rows reported; the cache alone lags behind them.
601
- virtualizer.getTotalSize();
602
- element_2.scrollTop = virtualizer.measurementsCache[index_0].start - anchor.offset;
603
+ if (measured.at(-1)?.index === rows.length - 1) {
604
+ setMeasuredThread(keyOf(chatId));
605
+ }
606
+ }, [initialLayout, empty, loadingHistory, frame.height, virtualRows, virtualizer, rows.length, keyOf, chatId]);
607
+ useLayoutEffect(() => {
608
+ const element_2 = scrollRef.current;
609
+ if (!followRef.current || element_2 === null) {
603
610
  return;
604
611
  }
605
- const row_7 = virtualizer.getVirtualItemForOffset(element_2.scrollTop);
606
- anchorRef.current = row_7 === undefined ? null : {
607
- id: String(row_7.key),
608
- offset: row_7.start - element_2.scrollTop
612
+ // The composer is in the scroll flow, so its measured height is part of this end position.
613
+ element_2.scrollTop = element_2.scrollHeight;
614
+ scrollGeometry.current = {
615
+ top: element_2.scrollTop,
616
+ height: element_2.scrollHeight,
617
+ viewport: element_2.clientHeight
609
618
  };
610
- });
619
+ }, [rows.length, totalSize, composerHeight, frame.height]);
611
620
  // Close to the top, the page before goes in; again after each page that left too little above the reader.
612
621
  useEffect(() => {
613
622
  const element_3 = scrollRef.current;
@@ -621,12 +630,15 @@ export function Timeline({
621
630
  const showsScrubber = frame.column && onMainAgent && ticks.length >= SCRUBBER_MIN_TICKS;
622
631
  const readingWidth = useChatPreferences(preferences => preferences.readingWidth);
623
632
  const paddingLeft = threadPaddingLeft(frame.width, showsScrubber, readingMeasure(readingWidth));
624
- /*
625
- * Where each message starts, from the virtualizer's measurements (an estimate for a row it never
626
- * drew), so reading the scroll position costs no layout.
627
- */
633
+ // The virtualizer materializes measurements on access; binary searches need only a few message starts.
628
634
  const measurements = virtualizer.measurementsCache;
629
- const starts = ticks.map(tick => measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX);
635
+ const starts = {
636
+ length: ticks.length,
637
+ at: index_2 => {
638
+ const tick = ticks.at(index_2);
639
+ return tick === undefined ? undefined : measurements[tick.rowIndex]?.start ?? tick.rowIndex * ESTIMATED_ROW_PX;
640
+ }
641
+ };
630
642
  const scrollTop = virtualizer.scrollOffset ?? 0;
631
643
  const viewport = virtualizer.scrollRect?.height ?? 0;
632
644
  const visibleHeight = Math.max(0, viewport - coveredHeight);
@@ -641,7 +653,7 @@ export function Timeline({
641
653
  * The virtualizer draws again only when its range of rows changes, which a scroll inside one long
642
654
  * row never does; the scroll itself redraws the strip once the messages in view are others.
643
655
  */
644
- const [, redrawStrip] = useReducer(count => count + 1, 0);
656
+ const [, redrawStrip] = useReducer(count_0 => count_0 + 1, 0);
645
657
  const stripRef = useRef({
646
658
  starts,
647
659
  inView
@@ -685,11 +697,11 @@ export function Timeline({
685
697
  openGroup: groups.add,
686
698
  openSubagent: subagents.add,
687
699
  // A row already on screen stays put; the find scrolls to the match itself once it is drawn.
688
- scrollToRow: index_1 => {
700
+ scrollToRow: index_3 => {
689
701
  const top_0 = scrollRef.current?.scrollTop ?? 0;
690
702
  const bottom = top_0 + visibleHeightRef.current;
691
- if (!virtualizer.getVirtualItems().some(virtualRow_0 => virtualRow_0.index === index_1 && virtualRow_0.end > top_0 && virtualRow_0.start < bottom)) {
692
- virtualizer.scrollToIndex(index_1, {
703
+ if (!virtualizer.getVirtualItems().some(virtualRow_0 => virtualRow_0.index === index_3 && virtualRow_0.end > top_0 && virtualRow_0.start < bottom)) {
704
+ virtualizer.scrollToIndex(index_3, {
693
705
  align: 'center'
694
706
  });
695
707
  }
@@ -707,8 +719,8 @@ export function Timeline({
707
719
  }, [searching, loadEarlier]);
708
720
  const found = useMemo(() => searching ? ticksWithHits(ticks, chatFind.hitRows) : [], [searching, ticks, chatFind.hitRows]);
709
721
  const foundCurrent = chatFind.currentRow === null ? null : tickOfRow(ticks, chatFind.currentRow);
710
- const jumpTo = index_2 => {
711
- const tick_0 = ticks[index_2];
722
+ const jumpTo = index_4 => {
723
+ const tick_0 = ticks[index_4];
712
724
  if (!tick_0) {
713
725
  return;
714
726
  }
@@ -730,6 +742,9 @@ export function Timeline({
730
742
  setSeeking(null);
731
743
  return;
732
744
  }
745
+ if (loadingHistory) {
746
+ return;
747
+ }
733
748
  if (seeking.block) {
734
749
  const jump = uiReplyJump(seeking.block, rows, items, turns.ids, cursor !== null);
735
750
  if (jump.kind === 'earlier') {
@@ -748,10 +763,10 @@ export function Timeline({
748
763
  }
749
764
  return;
750
765
  }
751
- const index_3 = rows.findIndex(row_8 => row_8.id === seeking.itemId);
752
- if (index_3 !== -1) {
766
+ const index_5 = rows.findIndex(row_8 => row_8.id === seeking.itemId);
767
+ if (index_5 !== -1) {
753
768
  setSeeking(null);
754
- showRowAtTop(virtualizer, followRef, index_3);
769
+ showRowAtTop(virtualizer, followRef, index_5);
755
770
  if (seeking.options?.flash) {
756
771
  useTimelineFlash.getState().flash(seeking.key, seeking.itemId, false);
757
772
  }
@@ -773,8 +788,8 @@ export function Timeline({
773
788
  }
774
789
  setSeeking(null);
775
790
  seeking.options?.onMissing?.();
776
- }, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier, keyOf, chatId]);
777
- const waitingForThread = seeking !== null && items === undefined;
791
+ }, [seeking, rows, items, turns, virtualizer, cursor, loadEarlier, keyOf, chatId, loadingHistory]);
792
+ const waitingForThread = seeking !== null && (items === undefined || loadingHistory);
778
793
  useEffect(() => {
779
794
  if (!waitingForThread) {
780
795
  return;
@@ -845,7 +860,7 @@ export function Timeline({
845
860
  // The strip is memoized and draws on every scroll, so it gets one callback for its life.
846
861
  const jumpRef = useRef(jumpTo);
847
862
  jumpRef.current = jumpTo;
848
- const pick = useCallback(index_4 => jumpRef.current(index_4), []);
863
+ const pick = useCallback(index_6 => jumpRef.current(index_6), []);
849
864
  // Read when the card asks rather than on every render, so the strip's memo holds while the thread streams.
850
865
  const cardChat = useMemo(() => ({
851
866
  chatId,
@@ -863,11 +878,11 @@ export function Timeline({
863
878
  if (!onMainAgent) {
864
879
  return false;
865
880
  }
866
- const index_5 = stepMessage(starts, i => ticks[i].kind === 'person', scrollTop, direction);
867
- if (index_5 === null) {
881
+ const index_7 = stepMessage(starts, i => ticks[i].kind === 'person', scrollTop, direction);
882
+ if (index_7 === null) {
868
883
  return false;
869
884
  }
870
- jumpTo(index_5);
885
+ jumpTo(index_7);
871
886
  return true;
872
887
  };
873
888
  useEffect(() => registerMessageStepper(keyOf(chatId), direction_0 => stepRef.current(direction_0)), [keyOf, chatId]);
@@ -953,20 +968,31 @@ export function Timeline({
953
968
  },
954
969
  inert: overlay ? true : undefined,
955
970
  onContextMenu: e_0 => setTarget(readTimelineTarget(e_0.target, threadRef.current, withCurrentText(rows, fullItems()))),
956
- children: welcome ? /*#__PURE__*/_jsx(WelcomeGreeting, {
971
+ children: loadingHistory ? /*#__PURE__*/_jsxs("div", {
972
+ className: "flex grow items-center justify-center gap-2 text-sm text-text-muted",
973
+ role: "status",
974
+ children: [/*#__PURE__*/_jsx(Icon, {
975
+ icon: LoaderCircle,
976
+ size: 16,
977
+ className: "animate-spin"
978
+ }), /*#__PURE__*/_jsx("span", {
979
+ children: t('timeline.loading')
980
+ })]
981
+ }) : welcome ? /*#__PURE__*/_jsx(WelcomeGreeting, {
957
982
  chatId: chatId
958
983
  }) : empty ? /*#__PURE__*/_jsx(EmptyThread, {
959
984
  chatId: chatId
960
985
  }) : /*#__PURE__*/_jsx("div", {
961
986
  className: "relative w-full shrink-0",
962
987
  style: {
963
- height: virtualizer.getTotalSize()
988
+ height: totalSize,
989
+ visibility: initialLayout ? 'hidden' : undefined
964
990
  },
965
991
  children: /*#__PURE__*/_jsx(UiReplyNavigationContext.Provider, {
966
992
  value: uiNavigation,
967
993
  children: /*#__PURE__*/_jsx(FindRevealContext.Provider, {
968
994
  value: chatFind.reveal,
969
- children: virtualizer.getVirtualItems().map(virtualRow_1 => {
995
+ children: virtualRows.map(virtualRow_1 => {
970
996
  const row_12 = rows[virtualRow_1.index];
971
997
  return /*#__PURE__*/_jsx(ThreadRow, {
972
998
  row: row_12,
@@ -7,6 +7,11 @@ export type Tokenize<State> = (code: string, state: State | undefined) => {
7
7
  lines: CodeToken[][];
8
8
  state: State | undefined;
9
9
  };
10
+ export declare class TokenCache<State> {
11
+ private readonly entries;
12
+ private size;
13
+ wrap(namespace: string, tokenize: Tokenize<State>): Tokenize<State>;
14
+ }
10
15
  export declare class IncrementalLines<State> {
11
16
  private readonly tokenize;
12
17
  private source;
@@ -1,3 +1,40 @@
1
+ // Virtual rows unmount offscreen; share this budget across their languages and themes.
2
+ const TOKEN_CACHE_BUDGET = 4 * 1024 * 1024;
3
+ const TOKEN_CACHE_ENTRIES = 128;
4
+ export class TokenCache {
5
+ entries = new Map();
6
+ size = 0;
7
+ wrap(namespace, tokenize) {
8
+ return (code, state) => {
9
+ if (state !== undefined) {
10
+ return tokenize(code, state);
11
+ }
12
+ const key = `${namespace}\0${code}`;
13
+ const known = this.entries.get(key);
14
+ if (known !== undefined) {
15
+ this.entries.delete(key);
16
+ this.entries.set(key, known);
17
+ return known.result;
18
+ }
19
+ const result = tokenize(code, state);
20
+ // Account for tokens too, so dense code cannot fill the cache with tiny strings.
21
+ const cost = key.length * 2 + result.lines.reduce((sum, line) => sum + line.length * 64, 0);
22
+ if (cost <= TOKEN_CACHE_BUDGET) {
23
+ while (this.entries.size > 0 && (this.size + cost > TOKEN_CACHE_BUDGET || this.entries.size >= TOKEN_CACHE_ENTRIES)) {
24
+ const oldest = this.entries.keys().next().value;
25
+ this.size -= this.entries.get(oldest).cost;
26
+ this.entries.delete(oldest);
27
+ }
28
+ this.entries.set(key, {
29
+ result,
30
+ cost
31
+ });
32
+ this.size += cost;
33
+ }
34
+ return result;
35
+ };
36
+ }
37
+ }
1
38
  /*
2
39
  * The lines of a code block that grows at the end. A line that ended keeps the tokens it got, and
3
40
  * the grammar state after it is where the next line starts, so a delta tokenizes the lines that are
@@ -1,2 +1,2 @@
1
- import type { ChatApprovalItem, ChatQuestionItem } from '@adecore/agent-contracts';
2
- export declare function usePendingRequests(chatId: string): Array<ChatApprovalItem | ChatQuestionItem>;
1
+ import type { PromptSubject } from '../../../prompts/logic/subjects.js';
2
+ export declare function usePendingRequests(chatId: string): PromptSubject[];
@@ -1,43 +1,35 @@
1
1
  import { c as _c } from "react/compiler-runtime";
2
- import { useMemo } from 'react';
3
- import { useChatRow, waitingRequestsOf } from '../../../state/chats.js';
4
- /* The approvals and then the questions the chat waits on, which take the composer's place until answered. */
2
+ import { useEffect, useState } from 'react';
3
+ import { useChatScope } from '../../../scope.js';
4
+ import { ChatPromptFeed } from '../../pending-prompts.js';
5
5
  export function usePendingRequests(chatId) {
6
- const $ = _c(4);
7
- const structure = useChatRow(chatId, _temp);
8
- const order = useChatRow(chatId, _temp2);
9
- const waitingBefore = useChatRow(chatId, _temp3);
6
+ const $ = _c(5);
7
+ const scope = useChatScope();
10
8
  let t0;
11
- if ($[0] !== order || $[1] !== structure || $[2] !== waitingBefore) {
12
- const approvals = [];
13
- const questions = [];
14
- for (const item of waitingRequestsOf(structure && order ? {
15
- structure,
16
- order,
17
- waitingBefore
18
- } : undefined)) {
19
- if (item.kind === "approval") {
20
- approvals.push(item);
21
- } else {
22
- questions.push(item);
23
- }
24
- }
25
- t0 = [...approvals, ...questions];
26
- $[0] = order;
27
- $[1] = structure;
28
- $[2] = waitingBefore;
29
- $[3] = t0;
9
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
10
+ t0 = [];
11
+ $[0] = t0;
30
12
  } else {
31
- t0 = $[3];
13
+ t0 = $[0];
32
14
  }
33
- return t0;
34
- }
35
- function _temp3(row_1) {
36
- return row_1?.waitingBefore;
37
- }
38
- function _temp2(row_0) {
39
- return row_0?.order;
40
- }
41
- function _temp(row) {
42
- return row?.structure;
15
+ const [subjects, setSubjects] = useState(t0);
16
+ let t1;
17
+ let t2;
18
+ if ($[1] !== chatId || $[2] !== scope) {
19
+ t1 = () => {
20
+ const feed = new ChatPromptFeed(scope, chatId, setSubjects);
21
+ feed.start();
22
+ return () => feed.dispose();
23
+ };
24
+ t2 = [scope, chatId];
25
+ $[1] = chatId;
26
+ $[2] = scope;
27
+ $[3] = t1;
28
+ $[4] = t2;
29
+ } else {
30
+ t1 = $[3];
31
+ t2 = $[4];
32
+ }
33
+ useEffect(t1, t2);
34
+ return subjects;
43
35
  }
@@ -1,6 +1,6 @@
1
1
  import type { CSSProperties } from 'react';
2
2
  import type { Highlighter } from 'shiki';
3
- import type { CodeToken, Tokenize } from './code-lines.js';
3
+ import { type CodeToken, type Tokenize } from './code-lines.js';
4
4
  import { type ChatHost } from '../../host.js';
5
5
  export type { Highlighter };
6
6
  export type GrammarState = ReturnType<Highlighter['getLastGrammarState']>;
@@ -1,4 +1,5 @@
1
1
  import { c as _c } from "react/compiler-runtime";
2
+ import { TokenCache } from './code-lines.js';
2
3
  import { useChatHost } from '../../host.js';
3
4
  import { shikiThemeOf } from './code-theme.js';
4
5
  // A plain language needs no grammar; shiki draws it in the theme's own foreground.
@@ -22,6 +23,7 @@ function createCodeHighlighter(source) {
22
23
  const languageLoads = new Map();
23
24
  const themes = new Set();
24
25
  const themeLoads = new Map();
26
+ const tokens = new TokenCache();
25
27
  const tokenizers = new Map();
26
28
  const load = () => {
27
29
  highlighterLoad ??= source().then(loaded => {
@@ -83,7 +85,7 @@ function createCodeHighlighter(source) {
83
85
  theme
84
86
  };
85
87
  entry = {
86
- tokenize: (code, state) => {
88
+ tokenize: tokens.wrap(key, (code, state) => {
87
89
  const result = loaded.codeToTokens(code, state ? {
88
90
  ...options,
89
91
  grammarState: state
@@ -92,7 +94,7 @@ function createCodeHighlighter(source) {
92
94
  lines: result.tokens,
93
95
  state: result.grammarState
94
96
  };
95
- },
97
+ }),
96
98
  fg: loaded.getTheme(theme).fg
97
99
  };
98
100
  tokenizers.set(key, entry);
@@ -233,6 +233,7 @@ declare const _default: {
233
233
  canRead: string;
234
234
  worksIn: string;
235
235
  };
236
+ loading: string;
236
237
  loadingEarlier: string;
237
238
  stripFailed: string;
238
239
  cardFailed: string;
@@ -233,6 +233,7 @@ export default {
233
233
  canRead: 'It can read',
234
234
  worksIn: 'It works in'
235
235
  },
236
+ loading: 'Loading conversation',
236
237
  loadingEarlier: 'Loading earlier messages',
237
238
  stripFailed: 'Could not show the messages',
238
239
  cardFailed: 'Could not show this card',
@@ -233,6 +233,7 @@ declare const _default: {
233
233
  canRead: string;
234
234
  worksIn: string;
235
235
  };
236
+ loading: string;
236
237
  loadingEarlier: string;
237
238
  stripFailed: string;
238
239
  cardFailed: string;
@@ -233,6 +233,7 @@ export default {
233
233
  canRead: 'Kan lezen',
234
234
  worksIn: 'Werkt in'
235
235
  },
236
+ loading: 'Gesprek laden',
236
237
  loadingEarlier: 'Eerdere berichten laden',
237
238
  stripFailed: 'Kon de berichten niet tonen',
238
239
  cardFailed: 'Kon deze kaart niet tonen',
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { AgentKind } from '@adecore/agent-contracts';
2
3
  import type { ChatHost } from '../../host.js';
3
4
  import { type PendingPrompt, type PromptAction, type PromptDraft } from './prompts.js';
4
5
  export interface HostPrompt {
@@ -12,6 +13,10 @@ export type PromptSubject = {
12
13
  kind: 'chat';
13
14
  nodeId: string;
14
15
  item: PendingPrompt;
16
+ source?: {
17
+ label: string;
18
+ provider?: AgentKind;
19
+ };
15
20
  } | {
16
21
  kind: 'host';
17
22
  nodeId: string;
@@ -120,7 +120,7 @@ export function PromptCard(t0) {
120
120
  let t8;
121
121
  if ($[12] !== meta) {
122
122
  t8 = meta && /*#__PURE__*/_jsx("div", {
123
- className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-muted",
123
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-muted",
124
124
  children: meta
125
125
  });
126
126
  $[12] = meta;