@filigran/chatbot 3.12.2 → 3.14.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 ADDED
@@ -0,0 +1,137 @@
1
+ # Changelog
2
+
3
+ Notable changes to `@filigran/chatbot`, so a host can see what an upgrade brings
4
+ before taking it.
5
+
6
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
7
+ the versions follow [semantic versioning](https://semver.org/spec/v2.0.0.html).
8
+ This file starts at 3.10.0; earlier releases are on the
9
+ [releases page](https://github.com/FiligranHQ/filigran-ui/releases).
10
+
11
+ ## 3.14.0 — 2026-10-19
12
+
13
+ ### Added
14
+
15
+ - A deck, report or video the assistant made shows as a card where its
16
+ `[[PRESENTATION:…]]`, `[[REPORT:…]]` or `[[VIDEO:…]]` marker stands (#450):
17
+ its title, its slides, sections or scenes and length, a video's render
18
+ progress, and `Open in XTM One`. The marker never shows as text, mid-stream
19
+ or after a reload, and copy and read aloud skip it. The panel tells XTM One
20
+ it renders them (`supports_artifacts`); XTM One describes each one in the
21
+ answer's `artifacts`.
22
+ - The fullscreen history can be grouped by workspace (#276): one foldable group
23
+ per workspace the user can file into, then `Not in a workspace` and
24
+ `Other workspaces`. A workspace is created, renamed and deleted from the
25
+ list, and a conversation is started in one, or moved to one from its row menu
26
+ or by dragging it onto a group.
27
+
28
+ ### Host opt-in
29
+
30
+ - `apiEndpoints.artifacts` (no default; XTM One serves `/chat/artifacts`) lets
31
+ the deck, report and video cards download them (PDF and PowerPoint for a
32
+ deck, PDF for a report, MP4 for a rendered video) and follow a video while
33
+ it renders, through the host's proxy. Without it the cards show and link to
34
+ XTM One, with no download. A failed download goes to `onDownloadError`, with
35
+ the name of the file asked for.
36
+ - `apiEndpoints.workspaces` (no default, like `approve`) turns the workspace
37
+ groups on, once the host's proxy forwards the workspace routes. A backend
38
+ that refuses the list leaves the history flat, as before.
39
+
40
+ ## 3.13.0 — 2026-10-16
41
+
42
+ ### Added
43
+
44
+ - The assistant can act on the page the panel is mounted in (#441). The agent
45
+ asks for one step at a time — take a snapshot, click, open a menu, type, or
46
+ navigate — and the panel answers each with a fresh reading of the page.
47
+ - The reading lists one line per control a user could act on, with its role,
48
+ accessible name, state and in-app destination, so the agent can name the one
49
+ it means.
50
+ - `Acting on the page…` shows on the status line while a step runs.
51
+
52
+ ### Host opt-in
53
+
54
+ - The panel offers the capability only when the host sets `apiEndpoints.approve`
55
+ **and** passes `onRelativeLinkClick`, because its proxy has to forward the
56
+ answer whole. A host that offers neither is unaffected, and a step arriving
57
+ without that opt-in is refused without the page being read or touched.
58
+
59
+ ### Security
60
+
61
+ - A reading never carries a secret: a password, a one-time code or a card field
62
+ is masked by `type` and by `autocomplete`, and its contents cannot surface
63
+ through an enclosing control's name or value, nor through a heading above it.
64
+ Hidden, `aria-hidden`, `inert` and closed `<details>` content stays out, as
65
+ does the panel's own.
66
+ - A step that would leave the page is refused rather than performed: another
67
+ origin, a link that opens a tab, a path that resolves off-site. A click or a
68
+ form submission that would load a page is cancelled while the host's own
69
+ handlers still run.
70
+ - A step is refused on a control the user could not use either: disabled,
71
+ read-only, a masked field, a file chooser, or one no longer visible when the
72
+ step runs.
73
+ - A step ends with the turn that asked for it. Stop, New chat, switching
74
+ conversation and closing the panel all end it, and one arriving afterwards is
75
+ answered rather than left to time out.
76
+
77
+ ## 3.12.2 — 2026-10-02
78
+
79
+ ### Fixed
80
+
81
+ - The panel keeps its own styles inside a host that ships its Tailwind utilities
82
+ outside any cascade layer: the message action icons were invisible and the
83
+ header hover backgrounds round instead of square (#431).
84
+
85
+ ## 3.12.1 — 2026-10-01
86
+
87
+ ### Fixed
88
+
89
+ - The reader can scroll up while the agent reasons or streams. The transcript
90
+ follows the end only while the reader is there; scrolling up stops the follow,
91
+ and returning to the bottom or sending a message resumes it (#429).
92
+
93
+ ## 3.12.0 — 2026-09-30
94
+
95
+ ### Added
96
+
97
+ - Every message ends with a footer: an answer shows its time, reasoning details,
98
+ Copy response, thumbs up / down and Read aloud; a user message shows Copy and
99
+ its time.
100
+ - Message times follow the new `locale` prop, and an answer is dated when it
101
+ completes, so the live time matches the time after a reload.
102
+ - Persisted feedback through the new `apiEndpoints.feedback`, optimistic and
103
+ rolled back on error; a rating restored with the conversation is locked (#419).
104
+
105
+ ## 3.11.1 — 2026-09-29
106
+
107
+ ### Changed
108
+
109
+ - The dictation button sits inside the composer, before Send, so its toolbar row
110
+ no longer appears alone in hosts with nothing else to put there.
111
+ - Listening is visible on the field itself, and the composer height no longer
112
+ shifts.
113
+ - Every send cancels dictation and drops the phrase still being recognised, so
114
+ no words land in a composer that was just emptied.
115
+
116
+ ## 3.11.0 — 2026-09-29
117
+
118
+ ### Changed
119
+
120
+ - A streamed answer is rendered block by block: a chunk re-parses only the blocks
121
+ it can still change.
122
+ - Streamed text and reasoning are applied once per animation frame rather than
123
+ once per network read.
124
+ - The markdown renderers keep their identity, so the live answer's DOM, a
125
+ selection in it and its images are no longer rebuilt per chunk.
126
+
127
+ ## 3.10.0 — 2026-09-26
128
+
129
+ ### Changed
130
+
131
+ - The waiting game is a link to the XTM One arcade rather than an embedded game
132
+ (#411).
133
+
134
+ ### Fixed
135
+
136
+ - The agent's reasoning stays on screen instead of being replaced by the waiting
137
+ game (#409).
package/README.md CHANGED
@@ -9,6 +9,7 @@ Filigran chat panel — a standalone React + Tailwind chatbot component with SSE
9
9
  - ✋ **Tool Approval** — When the agent stops at a tool that needs a human's consent, the turn pauses mid-answer and the reviewer approves, declines with a reason, or approves always — opt-in per host, see [Tool approval](#post-apibaseurlapiendpointsapprove)
10
10
  - 🗂️ **Conversation History** — Switch between (and delete) past conversations from a header menu, or from a permanent sidebar in fullscreen mode (collapsible, searchable past 7 entries, rename in place)
11
11
  - 🤖 **Multi-Agent Support** — Switch between different AI agents
12
+ - 🔗 **Conversation References** — Type `@` in the composer to point the agent at another conversation; opt-in per host, see [Conversation references](#conversation-references)
12
13
  - 📎 **File Attachments** — Upload and paste files (PDF, TXT, images)
13
14
  - 📥 **Agent-Generated Files** — Renders downloadable file cards from agent output and strips the `[[FILE:id]]` markers from the prose
14
15
  - 📝 **Full Markdown** — Tables (mis-delimited ones repaired), code blocks with copy button, lists, blockquotes, soft line breaks, inline images with a lightbox
@@ -258,6 +259,78 @@ Renames a conversation. Body: `{ "title": "..." }`. Only reached from the
258
259
  fullscreen sidebar; a backend without the route simply fails the request and
259
260
  the row reverts to its previous title.
260
261
 
262
+ With [workspaces](#workspaces) on, the same route files a conversation:
263
+ `{ "workspace_id": "uuid" }`, or `{ "workspace_id": null }` to take it out of
264
+ its workspace. A refusal's `detail` is shown under the list.
265
+
266
+ ### Workspaces
267
+
268
+ The fullscreen conversation list can be grouped by workspace, the way a
269
+ desktop assistant groups its projects: one collapsible group per workspace
270
+ (the collapsed ones are remembered per browser), a conversation started
271
+ inside a workspace with its group's compose button, a workspace created,
272
+ renamed or deleted from the list, and a conversation moved between groups
273
+ from its row menu or by dragging it onto a group.
274
+
275
+ The feature is off until the host names the route:
276
+ `apiEndpoints.workspaces` has **no default**, for the same reason as
277
+ `apiEndpoints.approve` - a proxied host must expose every route below before
278
+ the panel offers them. A backend that refuses the list (not licensed, an
279
+ older proxy) also leaves the list flat.
280
+
281
+ | Request | Purpose |
282
+ | --- | --- |
283
+ | `GET {apiBaseUrl}{workspaces}` | The caller's workspaces: a bare array or `{ "workspaces": [...] }` of `{ id, name, is_default, is_own, can_manage, is_archived }`. Archived ones are left out. |
284
+ | `POST {apiBaseUrl}{workspaces}` | Create one: `{ "name": "..." }`. |
285
+ | `PATCH {apiBaseUrl}{workspaces}/{id}` | Rename one: `{ "name": "..." }`. |
286
+ | `DELETE {apiBaseUrl}{workspaces}/{id}` | Delete one (never offered for the default). Its conversations are expected back in no workspace (`workspace_id: null`), never moved to a default; the history is reloaded after the delete. |
287
+ | `PATCH {apiBaseUrl}{history}/{conversation_id}` | File a conversation: `{ "workspace_id": "uuid" \| null }`. |
288
+ | `POST {apiBaseUrl}{sessions}` | Carries `workspace_id` when the conversation is started inside a workspace. |
289
+
290
+ `GET {history}` entries carry `workspace_id` (`null` for none). A conversation
291
+ in no workspace is a normal state, listed under "Not in a workspace": a new
292
+ conversation starts there unless it is started inside a workspace. A workspace the
293
+ caller may only read (`can_manage: false`) gets a group only while it holds
294
+ one of their conversations, and conversations filed in a workspace the list
295
+ does not return are grouped under "Other workspaces". XTM One serves all of it
296
+ at `/chat/workspaces` and `/chat/sessions`.
297
+
298
+ ### Conversation references
299
+
300
+ Typing `@` in the composer - at the start of the text or after a space, never
301
+ inside a word or an email (`a@b`) - opens a menu of the user's other
302
+ conversations, searched by title or key as they type. A pick inserts `@<key> `, and the
303
+ message goes out with the conversations it still references, so the agent reads
304
+ them; a `@word` typed by hand stays plain text, and so does a pick whose `@key`
305
+ is edited out before sending. The arrows move in the menu, Enter or Tab insert,
306
+ Escape closes it.
307
+
308
+ The feature is off until the host names the route:
309
+ `apiEndpoints.conversationReferences` has **no default**, for the same reason
310
+ as `apiEndpoints.approve` - a proxied host must expose the route, and forward
311
+ the new body field, before the composer offers the menu. Unset, `@` is plain
312
+ text and the message body is unchanged.
313
+
314
+ | Request | Purpose |
315
+ | --- | --- |
316
+ | `GET {apiBaseUrl}{conversationReferences}?q=<text>&limit=8&exclude=<conversation_id>` | The menu: `{ "conversations": [{ id, title, key, updated_at, is_own }] }` (or a bare array), the conversations the user can open, most recent first, that `q` matches (case-insensitive): every word of `q` (split on spaces, `-` and `_`) is in the title, or the key starts with `q` - so a key typed back (`@hi-bob` for "Hi Bob"), or a key that is not made of the title's words (an accent folded, a title without Latin letters), still finds its conversation. `q` is omitted for a bare `@`, `exclude` while the conversation is not created yet. `key` is what the pick inserts after `@`, always the backend's own. `is_own: false` marks a conversation shared with the user ("Shared with you"). |
317
+ | `POST {apiBaseUrl}{messages}` | Carries `referenced_conversation_ids: string[]`: the picks the text still holds, in the order they appear, each once, at most 5. Omitted when there is none. |
318
+ | `POST {apiBaseUrl}{sessions}` | A restored user message may carry `conversation_refs: [{ conversation_id, title, key }]`. |
319
+
320
+ The references of a sent message show as chips above it, also after a reload.
321
+ A chip opens its conversation in the panel when the conversation list holds it
322
+ (the list is read as soon as the thread shows a reference), and is a plain label
323
+ otherwise - a conversation shared with the user, say. While an answer streams,
324
+ a message with a reference waits for it instead of steering the running turn:
325
+ a steer reaches the agent as text alone, so it would never read what the
326
+ message references. Once the text references five conversations the menu says
327
+ so instead of offering more. The picks of an unsent draft are kept with its text, so a
328
+ draft restored when the panel reopens still references what it did.
329
+
330
+ XTM One serves it at `/chat/conversation-references`; a host behind its own
331
+ proxy names its route, relative to its `apiBaseUrl` (e.g.
332
+ `conversationReferences: '/conversation-references'`).
333
+
261
334
  ### `DELETE {apiBaseUrl}/chat/sessions/{conversation_id}`
262
335
 
263
336
  Deletes a conversation from the history menu. Any 2xx response counts as
@@ -279,6 +352,9 @@ Sends a message and streams the response via SSE.
279
352
  }
280
353
  ```
281
354
 
355
+ With [conversation references](#conversation-references) on, a message that
356
+ references another conversation also carries `referenced_conversation_ids`.
357
+
282
358
  The optional `context` object is forwarded verbatim from the `pageContext`
283
359
  prop (REST backend only) and is omitted entirely when empty. Use it to make
284
360
  the agent aware of the user's current location/page; the shape is up to the
@@ -507,6 +583,58 @@ chat must forward the request body whole (a proxy rebuilding it from a fixed
507
583
  field list silently drops `supports_tool_approval`), never time out the
508
584
  streaming turn, and pass SSE keepalives through untouched.
509
585
 
586
+ #### Acting on the host page
587
+
588
+ The same path answers `act_on_page` (XTM One #5559): with `apiEndpoints.approve`
589
+ set **and** `onRelativeLinkClick` passed — the host's opt-in, since it hands the
590
+ panel its navigation — the panel also sends `supports_page_actions: true`, and the
591
+ agent may act on the page the panel is mounted in. The host's proxy must forward
592
+ the approve route's body whole (a `page_action` answer carries no `decisions`).
593
+ Without that opt-in a frame is answered `page_actions_not_supported`, with the
594
+ page neither read nor acted on. Each step arrives on the stream as a
595
+ `status` frame with `event: "page_action_required"`; the panel performs it
596
+ (`click`, `open` — a hover, or a click when hovering opens nothing —, `type`, then
597
+ Enter only in a field that says it is a search - `type="search"`,
598
+ `role="searchbox"`, inside `role="search"`, or named like one (a `name`, `id`,
599
+ `placeholder`, `aria-label` or `title` whose whole words include "search",
600
+ "query", "filter" or "find", and only when it is the form's sole field) - never
601
+ in a form of one unnamed field, which is as likely a create or
602
+ rename dialog,
603
+ or `navigate` through `onRelativeLinkClick` — never
604
+ a page load, which would end the turn) and POSTs
605
+ `{ conversation_id, page_action: { action_id, ok, error, url, title, snapshot } }`.
606
+ `ok` is the action's own outcome, so a step is never retried because reading the
607
+ page back failed; when that read throws, `ok` stands and `error` carries
608
+ `snapshot_failed: …` with an empty snapshot. A stopped turn (Stop, New chat,
609
+ switching conversation) abandons the action instead of acting on the page.
610
+
611
+ The snapshot (`utils/pageSnapshot.ts`) lists one line per element a user could
612
+ act on — `[e1] link "Reports" -> /dashboard/analyses/reports`, with states such as
613
+ `(selected)`, `(pressed)`, `(menu)` for a closed menu or `(collapsed)` for a closed
614
+ group
615
+ — and leaves out the panel itself, hidden and inert content, and every secret
616
+ value: a `type="password"` field, and anything whose `autocomplete` marks it as a
617
+ password, a one-time code or a card field, so a revealed password is masked too.
618
+ A list cut off at the size cap ends with `[truncated]`. A state the host shows by
619
+ styling alone is invisible to it: mark toggles
620
+ with `aria-pressed`, the current item with `aria-current`, and a clickable card
621
+ with `role="button"`. A `409` means the
622
+ turn no longer waits for that action, and the panel drops it.
623
+
624
+ Actions that would leave the page are refused rather than performed, each
625
+ answered with `ok: false` and a reason: a `navigate` off-site, a `click` on a
626
+ link to another origin or one opening a new tab, a `type` on anything that is
627
+ not a text field, a read-only field, a masked one (a password, one-time code
628
+ or card field), a file input (whose chooser is an operating-system window), a
629
+ target that is disabled - including one disabled by its `<fieldset>` - and one
630
+ no longer visible when the action runs. A click never completes a native default that
631
+ would leave the page: an anchor's navigation and a form's submission are
632
+ cancelled while the host's own handlers still run. Clicking an in-host link is
633
+ routed through `onRelativeLinkClick` rather than clicked, for the same reason a
634
+ `navigate` is. An action this version does not know is answered as
635
+ `unsupported_action` instead of being dropped, so a backend running ahead of the
636
+ panel is not left waiting out its timeout.
637
+
510
638
  #### Recovering a prompt after a page reload
511
639
 
512
640
  `approval_required` is a single event on a stream, so a reload loses it —
@@ -549,6 +677,38 @@ abandonment, never the person deciding.
549
677
 
550
678
  REST backend only: `legacy` and `ag-ui` never meet this gate.
551
679
 
680
+ ### Decks, reports and videos
681
+
682
+ An answer that made a deck, a report or a video carries a marker where it shows
683
+ it: `[[PRESENTATION:<id>]]` (`[[PRESENTATION:<id>#/<slide id>]]` for one
684
+ slide), `[[REPORT:<id>]]`, `[[VIDEO:<id>]]`. The panel sends
685
+ `supports_artifacts: true` with every message and session restore of the REST
686
+ backend, and XTM One then describes each one in the answer's `artifacts`; the
687
+ panel draws a card where its marker stands. A marker is never shown as text, a
688
+ descriptor whose marker is missing renders after the prose, and a marker with
689
+ no descriptor renders nothing.
690
+
691
+ | Field | Meaning |
692
+ | --- | --- |
693
+ | `kind`, `id`, `title` | `presentation`, `report` or `video`, and what it is called. |
694
+ | `url` | Its page in XTM One: the card's `Open in XTM One` (http(s) only, new tab). |
695
+ | `slide_count`, `slide_ref` | A deck's slides, and the slide its marker named. |
696
+ | `section_count` | A report's sections. |
697
+ | `scene_count`, `duration_ms`, `status`, `progress` | A video's scenes and length, and its newest render (`pending`, `running`, `ready`, `failed`, `cancelled`; progress 0-100). |
698
+ | `downloads` | The formats it downloads in: `pdf`, `pptx`, `pptx_native`, `mp4`. Empty until a video has a finished render. |
699
+
700
+ The downloads go through the host, like file downloads, once it names the
701
+ route: `apiEndpoints.artifacts` has **no default**, for the same reason as
702
+ `apiEndpoints.approve`. Unset, the cards show and link to XTM One, with no
703
+ download buttons.
704
+
705
+ | Request | Purpose |
706
+ | --- | --- |
707
+ | `POST {apiBaseUrl}{artifacts}/{kind}/{id}/download` | `{ "format": "pdf" }`: the file, saved under its `Content-Disposition` name. A failure goes to `onDownloadError` with the name of the file asked for. |
708
+ | `GET {apiBaseUrl}{artifacts}/{kind}/{id}` | The descriptor as of now: a card asks again 10 seconds after each answer, one request at a time, for 15 minutes at most, while its video renders. |
709
+
710
+ XTM One serves both at `/chat/artifacts`.
711
+
552
712
  ### Message feedback
553
713
 
554
714
  Every message ends with a footer. An assistant answer (once it has finished
@@ -736,6 +896,7 @@ Every key the package can ask for, grouped by where it appears:
736
896
 
737
897
  **Composer**
738
898
 
899
+ - `'A message references at most {count} conversations'`
739
900
  - `'Ask a question...'`
740
901
  - `'Attachments wait for the current response'`
741
902
  - `'Dictate a message'`
@@ -743,11 +904,16 @@ Every key the package can ask for, grouped by where it appears:
743
904
  - `'Files uploading...'`
744
905
  - `'Insert prompt template'`
745
906
  - `'Listening...'`
907
+ - `'No conversation matches'`
746
908
  - `'No prompt matches'`
909
+ - `'Reference a conversation'`
910
+ - `'Referenced conversations wait for the current response'`
747
911
  - `'Search prompts...'`
748
912
  - `'Send now'`
913
+ - `'Shared with you'`
749
914
  - `'Stop dictation'`
750
915
  - `'Stop generating'`
916
+ - `'Untitled conversation'`
751
917
  - `'Uses AI. Verify results.'`
752
918
 
753
919
  **Messages, markdown and files**
@@ -769,6 +935,7 @@ Every key the package can ask for, grouped by where it appears:
769
935
  - `'Image preview'`
770
936
  - `'Load earlier messages'`
771
937
  - `'Loading image…'`
938
+ - `'Open conversation'`
772
939
  - `'Read aloud'`
773
940
  - `'Reasoning details'`
774
941
  - `'Reasoning details — turn limit reached'`
@@ -778,6 +945,23 @@ Every key the package can ask for, grouped by where it appears:
778
945
  - `'Tell us what went wrong (optional)'`
779
946
  - `'What could be improved?'`
780
947
 
948
+ **Decks, reports and videos**
949
+
950
+ - `'Download {format}'`
951
+ - `'Not rendered yet'`
952
+ - `'Open in XTM One'`
953
+ - `'PowerPoint'`
954
+ - `'PowerPoint (editable)'`
955
+ - `'Presentation'`
956
+ - `'Rendering {progress}%'`
957
+ - `'Report'`
958
+ - `'The render did not finish'`
959
+ - `'Video'`
960
+ - `'Waiting to render'`
961
+ - `'{count} scenes'`
962
+ - `'{count} sections'`
963
+ - `'{count} slides'`
964
+
781
965
  **Agent status**
782
966
 
783
967
  - `'Analyzing results…'`
package/dist/index.d.ts CHANGED
@@ -140,6 +140,55 @@ interface ApiEndpoints {
140
140
  * REST backend only, and not in `singleEndpoint` mode.
141
141
  */
142
142
  feedback?: string | null;
143
+ /**
144
+ * Path of the caller's workspaces, which the conversation list is grouped
145
+ * by (XTM One serves it at `/chat/workspaces`). The panel lists them with
146
+ * `GET {workspaces}`, creates one with `POST`, renames one with
147
+ * `PATCH {workspaces}/{id}` and deletes one with `DELETE {workspaces}/{id}`;
148
+ * it files a conversation with `PATCH {history}/{conversation_id}` and
149
+ * `{ workspace_id }`, and starts one inside a workspace by sending
150
+ * `workspace_id` with the session `POST`.
151
+ *
152
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a
153
+ * proxied host has to expose all of those routes before the panel offers
154
+ * the feature. Unset (or a backend that refuses the list), the history is
155
+ * the flat list it always was.
156
+ *
157
+ * REST backend only, and not in `singleEndpoint` mode.
158
+ */
159
+ workspaces?: string | null;
160
+ /**
161
+ * Path of the conversations a message can reference with `@` in the
162
+ * composer (XTM One serves it at `/chat/conversation-references`). Typing
163
+ * `@` opens a menu searched with
164
+ * `GET {conversationReferences}?q=<typed text>&limit=8&exclude=<current conversation id>`,
165
+ * answered with `{ conversations: [{ id, title, key, updated_at, is_own }] }`:
166
+ * the conversations the user can open, most recent first. A pick inserts
167
+ * `@<key>`, and the message is sent with the ids of the picks it still
168
+ * holds as `referenced_conversation_ids` (at most 5); a restored user
169
+ * message lists them under `conversation_refs`.
170
+ *
171
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a proxied
172
+ * host has to expose the route, and forward the field, before the composer
173
+ * offers the menu. Unset, `@` is plain text and the message body is
174
+ * unchanged.
175
+ *
176
+ * REST backend only, and not in `singleEndpoint` mode.
177
+ */
178
+ conversationReferences?: string | null;
179
+ /**
180
+ * Base path of the decks, reports and videos an answer made (XTM One serves
181
+ * it at `/chat/artifacts`). Their cards download one through
182
+ * `POST {apiBaseUrl}{artifacts}/{kind}/{id}/download` with `{ format }`, and
183
+ * follow a video still rendering through `GET {apiBaseUrl}{artifacts}/{kind}/{id}`.
184
+ *
185
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a proxied
186
+ * host has to forward both routes before the cards offer them. Unset, the
187
+ * cards still render, with `Open in XTM One` and no download.
188
+ *
189
+ * REST backend only.
190
+ */
191
+ artifacts?: string | null;
143
192
  }
144
193
  /** A reusable prompt the user can insert into the composer. */
145
194
  interface ChatPromptTemplate {
@@ -460,8 +509,15 @@ interface ChatMessage {
460
509
  */
461
510
  agentName?: string;
462
511
  files?: ChatFile[];
512
+ /**
513
+ * The conversations a user message references with `@` (see
514
+ * {@link ApiEndpoints.conversationReferences}), shown as chips.
515
+ */
516
+ conversationRefs?: ChatConversationRef[];
463
517
  /** Agent-generated downloadable files attached to an assistant message. */
464
518
  attachments?: ChatAttachment[];
519
+ /** The decks, reports and videos an assistant message made, in marker order. */
520
+ artifacts?: ChatArtifact[];
465
521
  toolNames?: string[];
466
522
  toolCallCount?: number;
467
523
  iterations?: number;
@@ -526,6 +582,33 @@ interface ChatAttachment {
526
582
  */
527
583
  fileTag?: 'download_file' | 'working_file';
528
584
  }
585
+ /** What an answer can make besides a file. */
586
+ type ChatArtifactKind = 'presentation' | 'report' | 'video';
587
+ /**
588
+ * A deck, report or video an answer made, as the backend describes it: the
589
+ * `artifacts` of a `done` event or of a restored answer, sent because the panel
590
+ * says it renders them (`supports_artifacts`). Rendered as a card where its
591
+ * `[[PRESENTATION:<id>]]`, `[[REPORT:<id>]]` or `[[VIDEO:<id>]]` marker stands.
592
+ */
593
+ interface ChatArtifact {
594
+ kind: ChatArtifactKind;
595
+ id: string;
596
+ title: string;
597
+ /** The page that shows it in XTM One; linked only when it is http(s). */
598
+ url?: string;
599
+ /** The slide a deck's marker points at. */
600
+ slideRef?: string;
601
+ slideCount?: number;
602
+ sectionCount?: number;
603
+ sceneCount?: number;
604
+ durationMs?: number;
605
+ /** A video's newest render: `pending`, `running`, `ready`, `failed` or `cancelled`. */
606
+ status?: string;
607
+ /** That render's progress, 0-100. */
608
+ progress?: number;
609
+ /** The formats it downloads in: `pdf`, `pptx`, `pptx_native`, `mp4`. Empty until a video is rendered. */
610
+ downloads: string[];
611
+ }
529
612
  interface ChatFile {
530
613
  name: string;
531
614
  type: string;
@@ -556,6 +639,42 @@ interface ChatConversationSummary {
556
639
  * genuinely have no agent.
557
640
  */
558
641
  agentName?: string;
642
+ /**
643
+ * The workspace the conversation is filed in (`null`: none), when the
644
+ * backend reports it. Undefined on backends that do not.
645
+ */
646
+ workspaceId?: string | null;
647
+ }
648
+ /**
649
+ * A conversation the composer's `@` menu offers (see
650
+ * {@link ApiEndpoints.conversationReferences}).
651
+ */
652
+ interface ChatConversationReferenceCandidate {
653
+ id: string;
654
+ title: string;
655
+ /** What the pick inserts after `@`: the backend's own, never one computed here. */
656
+ key: string;
657
+ updatedAt?: string;
658
+ /** False for a conversation shared with the user. */
659
+ isOwn: boolean;
660
+ }
661
+ /** A conversation a message references with `@<key>`. */
662
+ interface ChatConversationRef {
663
+ conversationId: string;
664
+ title: string;
665
+ key: string;
666
+ /** As the menu offered it: `false` keeps the "Shared with you" hint. Absent reads as the user's own. */
667
+ isOwn?: boolean;
668
+ }
669
+ /** A workspace the conversation list is grouped by. */
670
+ interface ChatWorkspace {
671
+ id: string;
672
+ name: string;
673
+ /** The caller owns it: a colleague's default shared with them is not theirs. */
674
+ isOwn: boolean;
675
+ isDefault: boolean;
676
+ /** Whether the caller may file conversations into it, rename or delete it. */
677
+ canManage: boolean;
559
678
  }
560
679
  interface XtmAgent {
561
680
  id: string;
@@ -575,4 +694,4 @@ interface TransferredAgent {
575
694
  }
576
695
 
577
696
  export { ChatPanel, ChatToggleButton };
578
- export type { ApiEndpoints, BackendType, ChatAttachment, ChatContextBreakdown, ChatContextUsage, ChatConversationSummary, ChatFile, ChatMessage, ChatMessagePersistedFeedback, ChatMode, ChatPanelProps, ChatPromptTemplate, ChatQuotaStatus, ChatToggleButtonProps, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };
697
+ export type { ApiEndpoints, BackendType, ChatArtifact, ChatArtifactKind, ChatAttachment, ChatContextBreakdown, ChatContextUsage, ChatConversationRef, ChatConversationReferenceCandidate, ChatConversationSummary, ChatFile, ChatMessage, ChatMessagePersistedFeedback, ChatMode, ChatPanelProps, ChatPromptTemplate, ChatQuotaStatus, ChatToggleButtonProps, ChatWorkspace, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };