@filigran/chatbot 3.18.1 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,162 @@ the versions follow [semantic versioning](https://semver.org/spec/v2.0.0.html).
8
8
  This file starts at 3.10.0; earlier releases are on the
9
9
  [releases page](https://github.com/FiligranHQ/filigran-ui/releases).
10
10
 
11
+ ## 3.19.0 — unreleased
12
+
13
+ ### Added
14
+
15
+ - `collapseUnnamedFiles`: the files a reply carries but whose markers it does
16
+ not name go behind one collapsed `Working files ({count})` line, all of them
17
+ (`true`) or those a predicate picks, instead of one card each. Opened, each
18
+ is listed with its card, after a thumbnail for a picture (fetched only once
19
+ the list opens; the picture icon when there is no URL, the fetch fails or
20
+ the bytes do not decode), and from 8 files a `Show less` closes it from its
21
+ end. Opened while following the end, it stops following once it pushes the
22
+ end out of view, so the list stays in view. Off unless the host asks.
23
+ - `renderReasoningAction`: a host's action in the corner of the reasoning
24
+ window, shown while the window is and gone with it.
25
+ - An image's lightbox has a Download action when the panel can download the
26
+ file, so a user's uploaded picture, which replaces its download card, can be
27
+ downloaded.
28
+ - A `Scroll to bottom` button shows once the reader has scrolled up, and
29
+ brings the view back to the end, following it again.
30
+ - Older messages load as the reader scrolling up nears the top, a second at
31
+ least after the last load ended, never while the view follows the end, and
32
+ only where the button would load them. A load that brings nothing (the
33
+ backend failing) stops them until the conversation is opened again; the
34
+ button still tries again. The button reads `Loading earlier messages…` while they load.
35
+ - `maxFileSize` limits each attached file, next to `maxTotalSize` (none by
36
+ default). A file refused, whichever limit it went past (`maxFileSize`,
37
+ `maxTotalSize`, `maxFileCount`), is named above the text field with why; it
38
+ was dropped without a word. `onFileRefused(file, reason)` tells the host too
39
+ (`ChatFileRefusal`).
40
+ - `acceptFileTypes` sets the file picker's `accept`, and refuses a pasted or
41
+ dropped file of another type (`'file-type'`). A file the browser gives no
42
+ MIME type (Chrome on Windows for `.md`, `.yaml`) is judged by the type its
43
+ extension stands for, and refused by a MIME token when it stands for none.
44
+ - `placeholder` replaces "Ask a question...": a text, or a function of the
45
+ agent selected.
46
+ - `onBeforeSend`'s draft says how many files the message carries
47
+ (`fileCount`).
48
+ - The composer takes the focus after a send from it and after a switch to
49
+ another conversation or a new one, unless the focus is in a field, a dialog
50
+ or the host's page; opening the panel takes none.
51
+ - `onBeforeMove(conversation, workspace)` is asked before a drag or a row's
52
+ Move menu files a conversation into a workspace (`workspace` is `null` when
53
+ taking it out of one): `false`, or a promise of it, leaves the conversation
54
+ where it was, e.g. once the user declined to share it with a workspace's
55
+ members. One that throws or rejects refuses the move.
56
+ - `onConversationActionFailed(action, conversation, error)` is told when
57
+ deleting, renaming, moving or clearing conversations fails (`'delete'`,
58
+ `'rename'`, `'move'`, `'clearAll'`), with the backend's `detail` when it
59
+ gave one; returning `true` keeps the panel's own notice from showing.
60
+ - A row of the conversation list shows the last message
61
+ (`last_message_preview`) under its title, and a small icon for a
62
+ conversation started in a messaging channel (`channel`), named `Started in
63
+ {channel}`: Slack, Teams, WhatsApp, Telegram, Discord and Google Chat out
64
+ of the box; `channelLabel(channel)` names another, or hides one (`null`).
65
+ `ChatConversationSummary` gains the optional `lastMessagePreview` and
66
+ `channel`.
67
+ - `sidebarSearchThreshold` sets how many conversations the list holds before
68
+ its search box shows (default 7, as before; `0` shows it from the first).
69
+ - `headerTitle` and `headerSubtitle`, a node or a function of what is on
70
+ screen (`ChatHeaderContext`: the conversation as the list holds it, and the
71
+ agent), show a title beside the header's agent name and a line under it.
72
+ - `agentHref(agent)` makes the header's agent name a link, followed in place
73
+ (an http(s) URL or a path of the host's origin), and `onAgentNameClick(agent)`
74
+ takes a plain click on it; the chevron beside the name opens the agent
75
+ picker then.
76
+ - `clearAllAction: 'archive'` words Clear all as an archive (`Archive all
77
+ conversations`).
78
+ - `XtmAgent.badges`: short labels the agents list may send (`badges`, strings
79
+ only), shown after the agent's name in the agent picker.
80
+ - `toolLabels`: the host's names for its tools, by raw name or as a function
81
+ of it, in the status line (a consult's tools too) and the reasoning details.
82
+ A name it does not give keeps the built-in one.
83
+ - `sidebarSections` also takes a function of the conversation list's search,
84
+ `(search: ChatSidebarSearch) => ReactNode`, given what the search box holds,
85
+ trimmed (`''` when empty). It is drawn again when the search or the function
86
+ changes, so a host passes a stable one, and puts hooks in a component the
87
+ function returns. With it the search box shows whatever the number of
88
+ conversations, and a search no conversation of the user's own matches says
89
+ "None of your conversations matches". Sections given as an element show as
90
+ they are.
91
+
92
+ ### Fixed
93
+
94
+ - The view keeps the last line in view when the thread's own box shrinks
95
+ while following the end (the composer growing to several lines, a tray
96
+ opening); only growth inside the thread was followed.
97
+ - Earlier messages already loaded, shown by the button, leave the reader
98
+ where they were in every browser, as older messages fetched do.
99
+ - A failed upload's chip says why: the upload's `detail`
100
+ (`ChatFile.uploadError`), or "Upload failed". It showed a bare ✕.
101
+ - A message holding a file that failed to upload is not sent until the user
102
+ removes the file, turn or no turn; with no turn running it went without the
103
+ file, and nothing said so.
104
+ - Files attached before a host turns `disableFileManagement` on stay shown
105
+ and removable; they were hidden, yet still sent, or a failed one held every
106
+ send without a word.
107
+ - Deleting, renaming or clearing conversations failed silently: the panel now
108
+ says it under the conversation list, or under the header when the list is
109
+ not shown, in the backend's words when it gives some. A refused rename
110
+ still keeps the old title, and now undoes only that row: other changes to
111
+ the list made meanwhile stay.
112
+ - The conversation list's search matches a conversation's last message and
113
+ its agent's name too, not its title alone.
114
+ - A message sent in a conversation moves it to the top of its section at
115
+ once, with the time and its text as the preview; it stayed where it was
116
+ until the turn ended and the list was read again.
117
+ - A read-only panel showed the welcome screen's "Suggestions" heading over no
118
+ suggestions: nothing can be sent there, so the section is left out.
119
+ - The status line says what the turn does where it said `Thinking...`:
120
+ `{agent} is answering…` for each mentioned agent of a turn, with that
121
+ agent's name before the statuses of its run, in the voice call too
122
+ (`AgentStatusState.answeringAgentName`);
123
+ `Transferring to {agent}…` on `transfer_start`; the setup step of
124
+ `preparing` by its `phase` (`Loading conversation history…`, `Summarizing
125
+ earlier conversation…`, `Recalling memory…`, `Connecting integrations…`,
126
+ `Preparing tools…`, else `Preparing…`); `Compacting conversation context…`;
127
+ `Saving response…` on `persisting`; and `Analyzing results (step {step})…`
128
+ from the frame's `iteration` after the first step. A host's `statusLabel`
129
+ reads them as `AgentStatusState.agentName`, `phase` and `iteration`.
130
+ - While mentioned agents answer, a message does not steer: it would reach
131
+ whichever of them runs. It waits for the turn, held with
132
+ `holdSendsDuringTurn`, as a message that cannot steer does.
133
+ - A stored answer the server's restart cut short (`interrupted: true`,
134
+ `ChatMessage.interrupted`) is marked as a truncated one is, and its reasoning
135
+ details say why.
136
+ - A finished answer with no text and no files says `No response was
137
+ generated. Please try again.`; it showed `...`.
138
+ - A paste from an office suite or a spreadsheet (Excel, Word, PowerPoint,
139
+ OneNote, LibreOffice, Numbers) puts the copied text in the composer. These
140
+ apps copy a selection as its text, its HTML and a picture of it, and the
141
+ panel attached the picture and dropped the text. A paste whose files are all
142
+ images, with plain text and an HTML fragment that shows text, attaches
143
+ nothing and reaches the text box; a screenshot, a browser's "Copy image" and
144
+ a file copied in a file manager still attach. A picture copied together with
145
+ text around it (an image and its caption in Word or Outlook) is read the same
146
+ way: the text is pasted, the picture is not attached; copy the picture alone
147
+ to attach it.
148
+ - Enter while an input method composes a word (Chinese, Japanese, Korean)
149
+ confirms the word instead of sending the half-typed message, renaming a
150
+ conversation or naming a workspace, and Escape then cancels the composition
151
+ without stopping the turn, closing the field or clearing a search.
152
+ - A file dropped on the panel where it attaches none (a `readOnly` panel, one
153
+ with `disableFileManagement` or no upload route, or the header and the
154
+ conversation list of any panel) leaves the page where it is; the browser
155
+ opened it in the tab, away from the chat. A host's own drop zone around the
156
+ panel still receives the drop, and a picture or link dragged from a page
157
+ still goes into a text field as its address.
158
+ - The composer's draft is written when the page goes away (`pagehide`) or is
159
+ hidden, so a reload right after typing keeps it; a keystroke within the
160
+ 300 ms the draft waits before being written was lost. Two panels on one
161
+ conversation write only the draft typed in them, so neither erases the
162
+ other's.
163
+ - A search box hidden as the list shrinks below its threshold clears its
164
+ query: the list stayed filtered by a search the user could neither see nor
165
+ clear.
166
+
11
167
  ## 3.18.1 — unreleased
12
168
 
13
169
  ### Fixed
package/README.md CHANGED
@@ -92,6 +92,10 @@ import { ChatPanel } from '@filigran/chatbot';
92
92
  | `disableFileManagement` | `boolean` | `false` | No attaching or pasting files in the composer |
93
93
  | `maxFileCount` | `number` | `10` | Most files attached to one message |
94
94
  | `maxTotalSize` | `number` | 50 MB | Most bytes the files attached to one message add up to |
95
+ | `maxFileSize` | `number` | — | Most bytes one attached file may hold; none of its own by default, only `maxTotalSize` |
96
+ | `acceptFileTypes` | `string` | — | The file types the composer takes, in the file input's `accept` form (`'.pdf,image/*'`): the picker offers only these, and a pasted or dropped file of another type is refused. A file the browser gives no MIME type (Chrome on Windows for `.md`, `.yaml`) is judged by the type its extension stands for (`.md`, `.markdown`, `.yaml`, `.yml`, `.csv`, `.tsv`, `.txt`, `.log`, `.json`), and refused by a MIME token when it stands for none. Any type by default |
97
+ | `onFileRefused` | `(file: File, reason: ChatFileRefusal) => void` | — | A file the composer refused: `'file-size'` (over `maxFileSize`), `'total-size'` (past `maxTotalSize`), `'file-count'` (past `maxFileCount`) or `'file-type'` (not of `acceptFileTypes`). The composer names the file and says why above the text field either way |
98
+ | `placeholder` | `string \| ((agent: XtmAgent \| null) => string)` | `'Ask a question...'` | The composer's placeholder, or one for the agent selected (`null` until the agents load); used as given, so the host translates it |
95
99
  | `onMessageFeedback` | `(id, feedback, message) => void` | — | Receives each rating of an answer (`null` clears it); `id` is the message's persisted id when the backend reported one (`done.message_id`, a restored entry's `id`), else the panel's own. On its own it enables 👍/👎 and the panel stores nothing; with `apiEndpoints.feedback` set it is called once the backend has stored the rating. With neither, no thumbs. |
96
100
  | `locale` | `string` | browser's | BCP 47 language tag of the platform's language (e.g. `'fr-FR'`): the language of the panel's catalog for what `t` does not translate, of its message times and of read-aloud. Unset, the browser's — except that a host passing `t` gets no catalog then |
97
101
  | `disableImagePreviews` | `boolean` | `false` | Render image attachments as download cards instead of inline previews |
@@ -116,10 +120,14 @@ import { ChatPanel } from '@filigran/chatbot';
116
120
  | `headerActions` / `banner` / `composerHeader` / `transcriptFooter` / `welcome` | `React.ReactNode` | — | The host's content in the header, above the thread, above the composer, after the last message, or in place of the welcome screen |
117
121
  | `renderMessageExtras` / `messageFooterActions` | `(message, { streaming }) => React.ReactNode` | — | The host's content under a message, and controls in a finished answer's footer; `streaming` while the message is the answer still being written. `message.raw` holds the backend's own fields — see [Host extensions](#host-extensions) |
118
122
  | `sidebarRowActions` | `(conversation) => React.ReactNode` | — | The host's controls on a row of the conversation list |
119
- | `sidebarHeaderActions` / `sidebarSections` | `React.ReactNode` | — | The host's controls beside New conversation, and sections under the list |
123
+ | `sidebarHeaderActions` | `React.ReactNode` | — | The host's controls beside New conversation |
124
+ | `sidebarSections` | `React.ReactNode \| ((search: ChatSidebarSearch) => React.ReactNode)` | — | Sections under the list; a function draws them from the list's search (`query`, trimmed), again when the search or the function changes (pass a stable one, and put hooks in a component it returns), and offers the search box whatever the number of conversations |
120
125
  | `statusLabel` | `(status: AgentStatusState) => string \| undefined` | — | The status line's text for a status, over the built-in one |
126
+ | `toolLabels` | `Record<string, string> \| ((toolName: string) => string \| undefined)` | — | Tool names as the status line and the reasoning details show them, by the tool's raw name; a name it does not give keeps the built-in one (`search_iocs` → `Search Iocs`). A new function redraws every message: keep one across renders unless its labels change |
121
127
  | `waitingMessages` | `string[]` | built-in | The waiting panel's rotating messages, already translated |
122
128
  | `renderWaitingPanel` | `() => React.ReactNode` | — | Replaces the waiting panel's content (its messages and the invitation to play) |
129
+ | `renderReasoningAction` | `() => React.ReactNode` | — | An action in the reasoning window's corner (a game to play while the agent works), shown while the window is |
130
+ | `collapseUnnamedFiles` | `boolean \| (attachment, message) => boolean` | `false` | Puts the files a reply does not name behind one collapsed `Working files (N)` line, with a thumbnail for each picture once opened: all of them, or those the predicate picks |
123
131
  | `fontFamily` | `string` | — | Font of the panel's text |
124
132
  | `theme` | `ChatPanelTheme` | — | The host's colours, type and shapes; see [Custom Theme](#custom-theme) |
125
133
  | `agentMentions` | `boolean` | `false` | `@` also mentions agents of the agent list; the message carries their ids as `mentioned_agent_ids` |
@@ -130,6 +138,14 @@ import { ChatPanel } from '@filigran/chatbot';
130
138
  | `onBeforeSend` | `(draft) => false \| { text?, displayText?, extras? } \| void` | — | Sees every non-empty send first and takes it over (`false`), rewords it, shows the user's bubble other words or adds body fields (a message with fields of its own is held with `holdSendsDuringTurn`) — see [Host extensions](#host-extensions) |
131
139
  | `hideConversationList` | `boolean` | `false` | Hides the conversation list of fullscreen and inline modes, for a rail or a task pane (usable from 320 px wide); the header's history menu stands in for it — see [Host extensions](#host-extensions) |
132
140
  | `holdSendsDuringTurn` | `boolean` | `false` | A message that cannot steer is held and sent when the turn ends, instead of waiting in the composer. A turn winding down after a provisional end (until its stored end or its stream's close, at most 5 s) holds sends with or without it |
141
+ | `onBeforeMove` | `(conversation, workspace \| null) => boolean \| Promise<boolean>` | — | Asked before a drag or a row's Move menu files a conversation into a workspace (`null`: out of one); `false` leaves it where it was |
142
+ | `onConversationActionFailed` | `(action, conversation \| null, error \| null) => boolean \| void` | — | A failed `'delete'`, `'rename'`, `'move'` or `'clearAll'`, with the backend's reason; return `true` when the host told the user itself, and the panel says nothing |
143
+ | `sidebarSearchThreshold` | `number` | `7` | The conversation list's search box shows above this many conversations; `0` shows it from the first |
144
+ | `channelLabel` | `(channel: string) => string \| null \| undefined` | — | Names the channel a row's conversation was started in (`Started in {channel}`); `null` shows nothing, `undefined` keeps the panel's names (Slack, Teams, WhatsApp, Telegram, Discord, Google Chat) |
145
+ | `headerTitle` / `headerSubtitle` | `React.ReactNode \| ((context: ChatHeaderContext) => React.ReactNode)` | — | A title beside the header's agent name and a line under it, or functions of the conversation on screen (as the list holds it) and the agent |
146
+ | `agentHref` | `(agent: XtmAgent) => string \| undefined` | — | Links the header's agent name to this address (http(s) or a path of the host's origin), in place; the chevron beside it opens the picker |
147
+ | `onAgentNameClick` | `(agent: XtmAgent) => void` | — | Takes a plain click on the header's agent name (over `agentHref`); the chevron beside it opens the picker |
148
+ | `clearAllAction` | `'delete' \| 'archive'` | `'delete'` | What Clear all does, for its wording (`Archive all conversations`) |
133
149
 
134
150
  Plain-data props (`requestHeaders`, `apiEndpoints`, `pageContext`, `promptSuggestions`) are compared by content and callback props are called
135
151
  through a stable wrapper, so building them inline is fine. `t` and `logoIcon` are used as given: keep them stable (`useCallback` / `useMemo`),
@@ -224,8 +240,9 @@ them stable, and a new one re-renders every message, which is also how to
224
240
  refresh what they render.
225
241
 
226
242
  `onBeforeSend(draft)` sees every non-empty send first, synchronously:
227
- `draft.text`, `draft.source` (`'composer'`, `'host'` or `'voice'`) and
228
- `draft.turnRunning` (a turn is running). It returns `false` to take the send
243
+ `draft.text`, `draft.source` (`'composer'`, `'host'` or `'voice'`),
244
+ `draft.turnRunning` (a turn is running) and `draft.fileCount` (the files the
245
+ message carries), e.g. to refuse a send whose files the host cannot use. It returns `false` to take the send
229
246
  over (the composer keeps its draft), or an object: `text` rewords what the
230
247
  backend receives, `displayText` is what the user's bubble shows, and `extras`
231
248
  adds body fields. The panel's own fields cannot be set this way, and a message
@@ -439,11 +456,15 @@ Returns available AI agents.
439
456
  "name": "General Assistant",
440
457
  "slug": "general",
441
458
  "icon": null,
442
- "description": "A helpful general-purpose assistant"
459
+ "description": "A helpful general-purpose assistant",
460
+ "badges": ["Company"]
443
461
  }
444
462
  ]
445
463
  ```
446
464
 
465
+ `badges` is optional: short labels, already translated, shown after the
466
+ agent's name in the agent picker. Anything but strings is ignored.
467
+
447
468
  ### `POST {apiBaseUrl}/chat/sessions`
448
469
 
449
470
  Restores conversation history (and, implicitly, resolves the session).
@@ -535,11 +556,17 @@ Lists the user's past conversations for the history menu in the chat header.
535
556
  "conversation_id": "uuid-here",
536
557
  "title": "What is the weather?",
537
558
  "updated_at": "2026-06-10T08:30:00Z",
538
- "message_count": 12
559
+ "message_count": 12,
560
+ "last_message_preview": "It is sunny in Paris.",
561
+ "channel": "slack"
539
562
  }
540
563
  ]
541
564
  ```
542
565
 
566
+ `last_message_preview` and `channel` are optional: a row shows the preview
567
+ under its title, and names a messaging channel the conversation was started
568
+ in (see `channelLabel`).
569
+
543
570
  Selecting a conversation restores it through the existing
544
571
  `POST /chat/sessions` contract above. The endpoint is fetched lazily when the
545
572
  history menu opens; a backend that doesn't implement it yet (404/405) simply
@@ -808,9 +835,9 @@ mentions or body fields of its own (`send`'s `extras`, `onBeforeSend`'s
808
835
  `extras`) never steers, since a steer reaches the agent as text alone: it waits
809
836
  for the turn to end (`holdSendsDuringTurn`), and `onBeforeSend` is asked again,
810
837
  with `turnRunning: false`, before it goes out. A file that failed to upload
811
- neither steers nor waits: while a turn runs, winds down or is let go of with
812
- the event stream, the composer offers no send and says why, and once no turn
813
- holds the conversation the message goes without that file. `send` and a voice
838
+ neither steers nor waits, nor goes without that file: its chip says why (the
839
+ upload's `detail`), and the composer offers no send, and says why, until the
840
+ user removes it. `send` and a voice
814
841
  call attach no files, so this never holds them. Esc stops generating.
815
842
 
816
843
  #### Multi-segment responses
package/dist/index.d.ts CHANGED
@@ -473,6 +473,30 @@ interface ChatPanelProps {
473
473
  maxFileCount?: number;
474
474
  /** Maximum total size in bytes for attached files. Default: 50 * 1024 * 1024 (50 MB). */
475
475
  maxTotalSize?: number;
476
+ /** Maximum size in bytes of one attached file. Default: none (only `maxTotalSize`). */
477
+ maxFileSize?: number;
478
+ /**
479
+ * The file types the composer takes, in the file input's `accept` form
480
+ * (`'.pdf,image/*'`): the picker offers only these, and a pasted or dropped
481
+ * file of another type is refused. A file the browser gives no MIME type
482
+ * (Chrome on Windows for `.md`, `.yaml`) is judged by the type its extension
483
+ * stands for (`.md`, `.markdown`, `.yaml`, `.yml`, `.csv`, `.tsv`, `.txt`,
484
+ * `.log`, `.json`), and refused by a MIME token when it stands for none.
485
+ * Default: any type.
486
+ */
487
+ acceptFileTypes?: string;
488
+ /**
489
+ * A file the user attached that the composer refused, and why. The composer
490
+ * names it and says why above the text field, whether or not the host
491
+ * listens.
492
+ */
493
+ onFileRefused?: (file: File, reason: ChatFileRefusal) => void;
494
+ /**
495
+ * The composer's placeholder, over "Ask a question...": a text, or one for
496
+ * the agent selected (`null` until the agents load). Used as given, so the
497
+ * host translates it.
498
+ */
499
+ placeholder?: string | ((agent: XtmAgent | null) => string);
476
500
  /** Additional HTTP headers added to chatbot API requests (messages, sessions, agents, uploads). */
477
501
  requestHeaders?: Record<string, string>;
478
502
  /**
@@ -664,14 +688,42 @@ interface ChatPanelProps {
664
688
  sidebarRowActions?: (conversation: ChatConversationSummary) => React.ReactNode;
665
689
  /** Controls added beside New conversation, atop the conversation list. */
666
690
  sidebarHeaderActions?: React.ReactNode;
667
- /** Sections added under the conversation list (conversations shared with the user). */
668
- sidebarSections?: React.ReactNode;
691
+ /**
692
+ * Sections added under the conversation list (conversations shared with the
693
+ * user). A function draws them from the list's search, so they filter on
694
+ * what is typed, as the list does. It is drawn again only when the search or
695
+ * the function changes: pass a stable one (`useCallback`). Hooks go in a
696
+ * component it returns (`({ query }) => <Shared query={query} />`): hook
697
+ * calls in the function itself break once it draws another branch. With it,
698
+ * the search box shows whatever the number of conversations, and a search
699
+ * with no match among the user's own says so ("None of your conversations
700
+ * matches").
701
+ */
702
+ sidebarSections?: React.ReactNode | ((search: ChatSidebarSearch) => React.ReactNode);
669
703
  /** The status line's text for a status, over the built-in one; `undefined` keeps it. */
670
704
  statusLabel?: (status: AgentStatusState) => string | undefined;
705
+ /**
706
+ * Tool names as the status line and the reasoning details show them, over
707
+ * the raw name: by name, or a function of it (`undefined` keeps the raw name).
708
+ * A new function redraws every message: keep one across renders unless its
709
+ * labels change.
710
+ */
711
+ toolLabels?: ChatToolLabels;
671
712
  /** The rotating messages of the waiting panel, already translated. */
672
713
  waitingMessages?: string[];
673
714
  /** Replaces the waiting panel's content (its messages and the invitation to play). */
674
715
  renderWaitingPanel?: () => React.ReactNode;
716
+ /**
717
+ * An action in the corner of the reasoning window (a game to play while the
718
+ * agent works), shown while the window is and gone with it.
719
+ */
720
+ renderReasoningAction?: () => React.ReactNode;
721
+ /**
722
+ * Files a reply carries but whose markers it does not name go behind one
723
+ * collapsed "Working files (N)" line instead of one card each: all of them
724
+ * (`true`), or those the predicate picks. Default: one card each.
725
+ */
726
+ collapseUnnamedFiles?: boolean | ((attachment: ChatAttachment, message: ChatMessage) => boolean);
675
727
  /** Font of the panel's text; the package's own otherwise. */
676
728
  fontFamily?: string;
677
729
  /**
@@ -753,6 +805,63 @@ interface ChatPanelProps {
753
805
  * `sidebar*` slots render nothing meanwhile. Default: false.
754
806
  */
755
807
  hideConversationList?: boolean;
808
+ /**
809
+ * Asked before a conversation is filed into a workspace (`null`: taken out
810
+ * of its workspace), by a drag or the row's Move menu: `false` (or a promise
811
+ * of it) leaves it where it is, e.g. once the user declined to share it with
812
+ * a workspace's members. One that throws or rejects refuses the move.
813
+ */
814
+ onBeforeMove?: (conversation: ChatConversationSummary, workspace: ChatWorkspace | null) => boolean | Promise<boolean>;
815
+ /**
816
+ * Told when deleting, renaming, moving or clearing conversations fails, with
817
+ * the backend's reason when it gave one (`conversation` is `null` for
818
+ * `clearAll`). The panel says it under the conversation list (or the
819
+ * header) unless this returns `true`: the host told the user itself.
820
+ */
821
+ onConversationActionFailed?: (action: ChatConversationAction, conversation: ChatConversationSummary | null, error: string | null) => boolean | void;
822
+ /**
823
+ * The conversation list's search box shows once the list holds more than
824
+ * this many conversations; `0` shows it from the first one. Default: 7.
825
+ */
826
+ sidebarSearchThreshold?: number;
827
+ /**
828
+ * Names the channel a conversation was started in, on its row (`Started in
829
+ * {channel}`): a string names it, `null` shows nothing, `undefined` keeps
830
+ * the panel's own name (Slack, Teams, WhatsApp, Telegram, Discord, Google
831
+ * Chat; no other channel is shown).
832
+ */
833
+ channelLabel?: (channel: string) => string | null | undefined;
834
+ /** A title beside the agent's name in the header (the conversation's, say), or a function of what is on screen. */
835
+ headerTitle?: React.ReactNode | ((context: ChatHeaderContext) => React.ReactNode);
836
+ /** A line under the agent's name in the header (`Agent · Slack`, say), or a function of what is on screen. */
837
+ headerSubtitle?: React.ReactNode | ((context: ChatHeaderContext) => React.ReactNode);
838
+ /**
839
+ * Links the header's agent name to this address (an http(s) URL or a path
840
+ * of the host's own origin), opened in place; the chevron beside the name
841
+ * opens the agent picker then. `undefined` links nothing.
842
+ */
843
+ agentHref?: (agent: XtmAgent) => string | undefined;
844
+ /**
845
+ * Called on a click of the header's agent name, over following `agentHref`
846
+ * (a click with a modifier key still follows it): a host routes to the
847
+ * agent's page itself. The chevron beside the name opens the agent picker then.
848
+ */
849
+ onAgentNameClick?: (agent: XtmAgent) => void;
850
+ /**
851
+ * What Clear all does to the conversations, for its wording: `'archive'`
852
+ * says "Archive all conversations". Default: `'delete'`. A host words it
853
+ * otherwise through `t`.
854
+ */
855
+ clearAllAction?: 'delete' | 'archive';
856
+ }
857
+ /** A conversation action of the list, as `onConversationActionFailed` names it. */
858
+ type ChatConversationAction = 'delete' | 'rename' | 'move' | 'clearAll';
859
+ /** What the header's `headerTitle` and `headerSubtitle` functions are given. */
860
+ interface ChatHeaderContext {
861
+ /** The conversation on screen as the conversation list holds it; `null` for a new chat, or before the list is read. */
862
+ conversation: ChatConversationSummary | null;
863
+ /** The agent the header names, when the agent list holds it. */
864
+ agent: XtmAgent | null;
756
865
  }
757
866
  /** What a marker card is drawn from. */
758
867
  interface ChatMarkerContext {
@@ -774,6 +883,8 @@ interface ChatCodeBlockContext {
774
883
  /** The answer is still streaming: the block may not be complete yet. */
775
884
  streaming: boolean;
776
885
  }
886
+ /** Tool names as a person reads them, by the tool's raw name (see `ChatPanelProps.toolLabels`). */
887
+ type ChatToolLabels = Readonly<Record<string, string>> | ((toolName: string) => string | undefined);
777
888
  /** Renders one fenced code block of a language (see `ChatPanelProps.codeBlockRenderers`); *code* is the block's text. `null` keeps the block as code. */
778
889
  type ChatCodeBlockRenderer = (code: string, context: ChatCodeBlockContext) => React.ReactNode;
779
890
  /** What a message slot (`renderMessageExtras`, `messageFooterActions`) is drawn with. */
@@ -844,6 +955,8 @@ interface ChatSendDraft {
844
955
  source: 'composer' | 'host' | 'voice';
845
956
  /** A turn is running. */
846
957
  turnRunning: boolean;
958
+ /** How many files the message carries (only the composer attaches files); always set by the panel. */
959
+ fileCount?: number;
847
960
  /**
848
961
  * A message held while a turn ran, asked about again as it leaves, the turn
849
962
  * over (`turnRunning: false`), each send held with it on its own. `false`
@@ -998,6 +1111,12 @@ interface ChatMessage {
998
1111
  * reasoning-details affordance, mirroring the XTM One web chat.
999
1112
  */
1000
1113
  isTruncated?: boolean;
1114
+ /**
1115
+ * True when the server restarted while the turn ran, so the answer stopped
1116
+ * before it could finish (`interrupted` on the stored message): marked as a
1117
+ * truncated answer is.
1118
+ */
1119
+ interrupted?: boolean;
1001
1120
  /**
1002
1121
  * The backend's own fields for the message, as it sent them: the restored
1003
1122
  * (or paged, or relayed) entry, or the `done` frame - or the stored end -
@@ -1105,6 +1224,17 @@ interface AgentStatusState {
1105
1224
  waitStartMs?: number;
1106
1225
  /** `consulting`: the consult the agent is waiting on. */
1107
1226
  consult?: ConsultStatus;
1227
+ /** `preparing`: the setup step running (`history`, `compaction`, `memory`, `integrations`, `tools`). */
1228
+ phase?: string;
1229
+ /** `analyzing`: the agent's step, from 1. */
1230
+ iteration?: number;
1231
+ /** `transferring`: the agent the conversation goes to; `mention_answering`: the mentioned agent answering. */
1232
+ agentName?: string;
1233
+ /**
1234
+ * The mentioned agent answering now, on every status of its run: a turn
1235
+ * that answers several mentioned agents runs one after the other.
1236
+ */
1237
+ answeringAgentName?: string;
1108
1238
  }
1109
1239
  /**
1110
1240
  * A consult the agent is waiting on, as the status line shows it. A consulted
@@ -1134,7 +1264,20 @@ interface ChatFile {
1134
1264
  fileId?: string;
1135
1265
  /** Upload status: 'pending' while uploading, 'done' when uploaded, 'error' on failure. */
1136
1266
  uploadStatus?: 'pending' | 'done' | 'error';
1267
+ /** Why the upload failed, as the backend said (`detail`), when it said. */
1268
+ uploadError?: string;
1269
+ }
1270
+ /** The conversation list's search, as a host's sections read it (`sidebarSections`). */
1271
+ interface ChatSidebarSearch {
1272
+ /** What the search box holds, trimmed; `''` when it holds nothing. */
1273
+ query: string;
1137
1274
  }
1275
+ /**
1276
+ * Why the composer refused a file (see `onFileRefused`): larger than
1277
+ * `maxFileSize`, past `maxTotalSize` with the files attached, past
1278
+ * `maxFileCount`, or not of `acceptFileTypes`.
1279
+ */
1280
+ type ChatFileRefusal = 'file-size' | 'total-size' | 'file-count' | 'file-type';
1138
1281
  /**
1139
1282
  * A past conversation surfaced in the history menu (REST backend only).
1140
1283
  * Listed via `GET {apiBaseUrl}{apiEndpoints.history ?? apiEndpoints.sessions ?? '/chat/sessions'}`
@@ -1159,6 +1302,10 @@ interface ChatConversationSummary {
1159
1302
  * backend reports it. Undefined on backends that do not.
1160
1303
  */
1161
1304
  workspaceId?: string | null;
1305
+ /** The last message's text, shortened by the backend (`last_message_preview`), when it sends one. */
1306
+ lastMessagePreview?: string;
1307
+ /** Where the conversation was started (`channel`: `slack`, `teams`, `web`, …), when the backend reports it. */
1308
+ channel?: string;
1162
1309
  }
1163
1310
  /**
1164
1311
  * A conversation the composer's `@` menu offers (see
@@ -1200,6 +1347,8 @@ interface XtmAgent {
1200
1347
  description: string | null;
1201
1348
  /** The heading the agent picker lists it under; ungrouped agents come first. */
1202
1349
  group?: string;
1350
+ /** Short labels shown after its name in the agent picker (`Company`), already translated. */
1351
+ badges?: string[];
1203
1352
  }
1204
1353
 
1205
1354
  /**
@@ -1220,4 +1369,4 @@ interface TransferredAgent {
1220
1369
  declare const voiceCallSupported: () => boolean;
1221
1370
 
1222
1371
  export { ChatPanel, ChatToggleButton, voiceCallSupported };
1223
- export type { AgentStatusState, ApiEndpoints, BackendType, ChatAnsweredBy, ChatArtifact, ChatArtifactKind, ChatAttachment, ChatCodeBlockContext, ChatCodeBlockRenderer, ChatContextBreakdown, ChatContextUsage, ChatConversationRef, ChatConversationReferenceCandidate, ChatConversationSummary, ChatFile, ChatHeldOutcome, ChatMarkerContext, ChatMarkerRenderer, ChatMessage, ChatMessagePersistedFeedback, ChatMessageSlotContext, ChatMode, ChatPanelHandle, ChatPanelMode, ChatPanelProps, ChatPanelTheme, ChatPromptTemplate, ChatQuotaStatus, ChatSendDecision, ChatSendDraft, ChatSendOptions, ChatSendOutcome, ChatStreamEventInfo, ChatToggleButtonProps, ChatTurnEnd, ChatTurnOutcome, ChatWorkspace, ConsultStatus, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };
1372
+ export type { AgentStatusState, ApiEndpoints, BackendType, ChatAnsweredBy, ChatArtifact, ChatArtifactKind, ChatAttachment, ChatCodeBlockContext, ChatCodeBlockRenderer, ChatContextBreakdown, ChatContextUsage, ChatConversationAction, ChatConversationRef, ChatConversationReferenceCandidate, ChatConversationSummary, ChatFile, ChatFileRefusal, ChatHeaderContext, ChatHeldOutcome, ChatMarkerContext, ChatMarkerRenderer, ChatMessage, ChatMessagePersistedFeedback, ChatMessageSlotContext, ChatMode, ChatPanelHandle, ChatPanelMode, ChatPanelProps, ChatPanelTheme, ChatPromptTemplate, ChatQuotaStatus, ChatSendDecision, ChatSendDraft, ChatSendOptions, ChatSendOutcome, ChatSidebarSearch, ChatStreamEventInfo, ChatToggleButtonProps, ChatToolLabels, ChatTurnEnd, ChatTurnOutcome, ChatWorkspace, ConsultStatus, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };