@filigran/chatbot 3.14.0 → 3.15.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,291 @@ 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.15.1 — unreleased
12
+
13
+ ### Fixed
14
+
15
+ - A user's message keeps its line breaks: its bubble shows the lines and
16
+ paragraphs it was sent with, instead of running them into one paragraph
17
+ (#458).
18
+
19
+ ## 3.15.0 — 2026-10-26
20
+
21
+ ### Added
22
+
23
+ - `mode="inline"`: the panel fills the host's container instead of floating
24
+ over the page, laid out like fullscreen (conversation list beside the
25
+ thread), with no view switcher and no close button. For a host that gives
26
+ the chat a page of its own.
27
+ - A host can own the conversation on screen (`conversationId`,
28
+ `onConversationChange`), pick the agent selected first (`initialAgent`,
29
+ `onAgentChange`) and drive the panel through its `ref`, a handle that is the
30
+ same object for the panel's life:
31
+ - `send(text, { extras, displayText })`: the backend receives *text* with
32
+ `extras` in its body, and the user's bubble shows `displayText` (as with
33
+ `onBeforeSend`'s, which wins when it gives one). It resolves to what
34
+ became of the message: `'sent'` once the backend takes it, `'steered'`,
35
+ `'held'`, `'dropped'` (empty, taken over, read-only, or the panel
36
+ unmounted first) or `'failed'` (the backend refused it).
37
+ - `newConversation`, `selectConversation`, `setDraft`, `focusComposer` and
38
+ `stop`.
39
+ - `getDraft()` reads the composer's text as it stands (typed, or set by
40
+ `setDraft`), e.g. for a host taking over a held message (`draft.held`)
41
+ to tell whether the composer still holds what was typed.
42
+ - `scrollToMessage` jumps to a message and highlights it.
43
+ - `reloadAgents()` reads the agent list again, keeping the agent picked
44
+ unless it is gone (none is picked once the list is empty). A failed
45
+ reload keeps the list it had, and only the latest reload lands.
46
+ - `startVoiceCall()` starts a voice call (below).
47
+ - `onClose` and `onModeChange` are optional: the header shows the close
48
+ button and the view switcher only when they are given.
49
+ - `storageKeyPrefix` namespaces every key the panel keeps in browser storage,
50
+ for a host that mounts more than one panel.
51
+ - `readOnly` hides the composer, with `readOnlyNotice` in its place, and
52
+ the panel then sends nothing, from the handle or the welcome screen's
53
+ suggestions.
54
+ - `getRequestExtras` adds the host's own fields to each message body (the
55
+ panel's own fields, set by it or not, are never taken from there), and
56
+ `onStreamEvent` hands the host every frame received, including those the
57
+ panel does not render, with what the panel read in it: `end`
58
+ (`'provisional'` on an `error` or `cancelled` marked `provisional: true`,
59
+ `'stored'` on an end naming the stored answer's `message_id`: a
60
+ `done` that is not intermediate, a `cancelled`, an `error`) and `replayed`
61
+ (the `thinking` frame of a turn replayed from its start, `resumed: true`).
62
+ - The panel renders the frames of a backend whose turns outlive their stream:
63
+ a turn re-attached mid-way (`thinking.resumed`), an announced wait
64
+ (`status: wait_start`, with a countdown, kept through what it reports
65
+ meanwhile: `wait_status`, `tool_heartbeat`, `tool_progress`,
66
+ `consult_progress`; streamed text or any other status ends it), `cancelled`,
67
+ an intermediate `done` and the agent that `answered_by` a segment. A
68
+ provisional end (an `error` or `cancelled` marked `provisional: true`; any
69
+ other is final) ends the turn at once; the stored end that follows gives the
70
+ answer its id and a `done`'s content.
71
+ - History is restored with no agent list (`apiEndpoints.agents: null`).
72
+ - A host can render its own cards for the `[[KIND:args]]` markers of an
73
+ answer (`markerRenderers`), by marker name, and take over the built-in
74
+ `FILE`, `PRESENTATION`, `REPORT` and `VIDEO` ones, including the items an
75
+ answer carries with no marker. A card that throws, or a renderer that does,
76
+ shows `This item could not be loaded.` and leaves the rest of the answer
77
+ alone. A marker no renderer names stays text, as before.
78
+ - `transformMessageText` rewrites a message's text before it is shown, copied
79
+ or read aloud (masking secrets, converting a channel's syntax); one that
80
+ throws leaves the text as it came.
81
+ - `codeBlockRenderers` renders a fenced code block of a language the host
82
+ names (`(code, { streaming }) => ReactNode`, matched case-insensitively) in
83
+ place of the code block, e.g. a chart drawn from the block's data. A
84
+ renderer that throws shows `This item could not be loaded.`, as a marker
85
+ card does; a language no renderer names, or a renderer returning `null`,
86
+ stays a code block.
87
+ - `onArtifactOpen` opens a deck, report or video in the host, in place of the
88
+ link to XTM One.
89
+ - Slots for the host's own content: `headerActions`, `banner`,
90
+ `composerHeader`, `transcriptFooter`, `welcome`, `renderMessageExtras` and
91
+ `messageFooterActions` (given the message and `{ streaming }`,
92
+ `ChatMessageSlotContext`: the message is the answer still being written),
93
+ `sidebarRowActions`, `sidebarHeaderActions` and `sidebarSections`.
94
+ - `statusLabel`, `waitingMessages` and `renderWaitingPanel` adapt the status
95
+ line and the waiting panel; `fontFamily` sets the panel's font.
96
+ - An agent's `icon` shows in the picker when it is an `https:` or
97
+ `data:image/` URL, or emoji, and agents carrying a `group` are listed under
98
+ its heading.
99
+ - `@` in the composer can mention agents (`agentMentions`): the agents of
100
+ the agent list are offered above the conversations, and the message carries
101
+ the ids of those it still mentions as `mentioned_agent_ids`, in mention
102
+ order. A message that mentions an agent never steers the running turn.
103
+ - `steerAttachments` lets uploaded files steer the running turn (the steer
104
+ route then takes `file_ids`), and `holdSendsDuringTurn` holds a message that
105
+ cannot steer (files, references, mentions, body fields of its own, a turn
106
+ that has just ended) and sends it as the next turn, with a way to cancel it
107
+ meanwhile. Messages held one after another go together, keeping their body
108
+ fields, and Stop gives back to the composer what the user typed for them;
109
+ a message sent once the stopped turn ended goes out as it winds down.
110
+ - A voice call over the panel, started with `startVoiceCall()` on the handle
111
+ (`voiceCallSupported()` tells a host whether to offer it): the browser's
112
+ speech recognition listens, a phrase is sent after one second of silence
113
+ (`onBeforeSend` sees `source: 'voice'`, a `ChatSendDraft.source` of its
114
+ own), and the answer is said through `apiEndpoints.tts` in pieces of about
115
+ 200 characters, each requested while the one before plays, in the browser's
116
+ voice where the route fails, the browser cannot play a piece, or no route is
117
+ named. Only the final answer of the turn that answers the question is said
118
+ (the panel's own, or the one a fold or a steer names), and only the
119
+ addressed agent's: the other agents' replies show by name, `Did not answer`
120
+ beside a failed one. Speaking over the answer, a tap or Skip stops it; a tap
121
+ while it is awaited cancels the question and its turn, as `stop()` does; a turn silent for 120 s is checked through
122
+ `apiEndpoints.pendingApprovals` and given up on once over.
123
+ - Stable `data-testid`s for host tests: `filigran-chat-panel`,
124
+ `filigran-chat-transcript`, `filigran-chat-user-message`,
125
+ `filigran-chat-assistant-message`, `filigran-chat-composer`,
126
+ `filigran-chat-approval`, `filigran-chat-disclaimer`.
127
+ - `onReady` tells the host when the panel has chosen its agent and read back
128
+ the conversation on screen; calls through the handle made before then wait
129
+ for it, and all of them run in order (a new conversation's agent is in
130
+ place before the send that follows it).
131
+ - `onBeforeSend` sees every send first, from the composer or the handle: it
132
+ can take a send over, or reword it and add fields to its body. It can also
133
+ return `displayText`: the backend receives `text`, and the user's bubble
134
+ (live, steered or held) shows `displayText`, for a host that wraps what the
135
+ user typed in context of its own. A message held while a turn ran is asked
136
+ about again as it leaves (`turnRunning: false`, `held: true`), each send
137
+ held with it on its own; taken over then, what the user typed for it goes
138
+ back to the composer, after what they typed since, and a message all of
139
+ whose sends are taken over is settled `'returned'`.
140
+ - `hideConversationList` hides the conversation list of inline and fullscreen
141
+ modes, for a rail or a task pane: the header, thread and composer hold from
142
+ 320 px wide, and the header's history menu stands in for the list (open,
143
+ delete, Clear all), while a referenced conversation still opens in the panel.
144
+ - `onHeldSendSettled(text, outcome)` tells the host what became of a message
145
+ held until the turn ended: `'sent'`, `'failed'`, `'dropped'`, `'returned'`
146
+ (given back to the composer by Stop, or by `onBeforeSend` taking it over as
147
+ it leaves; told once the composer shows it, so `getDraft()` reads it) or
148
+ `'cancelled'`.
149
+ - `onTurnRunningChange(running, info)` tells the host when a turn starts or
150
+ stops running, own or relayed, and with `false` how it ended
151
+ (`info.ended`: `'answered'`, `'stopped'` or `'failed'`): see
152
+ [Host extensions](README.md#host-extensions) and
153
+ [Turns that outlive their stream](README.md#turns-that-outlive-their-stream).
154
+ - `ChatMessage.answeredBy` (`{ agentId, agentName, agentIcon }`) and
155
+ `ChatMessage.mentionFailed`, from the `answered_by` and `mention_failed` of
156
+ each `done` frame (intermediate ones included) and of each restored entry,
157
+ for a host's own rendering of a turn several agents answer. The panel's own
158
+ rendering is unchanged.
159
+ - A `session_expired` frame on the message stream or the event stream ends
160
+ the turn on screen at once, keeping what it wrote.
161
+ - `@filigran/chatbot/markdown` also exports the answer-marker helpers the
162
+ panel reads markers with: `splitAnswerMarkers`, `stripAnswerMarkers`,
163
+ `markerKinds` and the `AnswerPart` type (and `ChatArtifactKind`), still free
164
+ of React and side effects, so a host renders, previews or strips markers
165
+ exactly as the panel does.
166
+ - `onSessionEnded(reason)` tells the host the user's session ended: a
167
+ `session_expired` frame on either stream, or a `401` whose body's
168
+ `detail.code` is `session_expired` on any request the panel makes, with the
169
+ frame's or the `detail`'s `reason`. It comes once per set of
170
+ `requestHeaders`.
171
+ - `ChatMessage.raw`: the backend's own object for the message, a plain copy
172
+ (the restored, paged or relayed entry; the `done` frame of a live answer,
173
+ or the stored end after a provisional one), for the fields the panel does
174
+ not read.
175
+ - `@filigran/chatbot/workspaces`, a React-free entry point with the panel's
176
+ grouping of a conversation list by workspace: `parseWorkspaces` (which,
177
+ given `userId`, tells the caller's own workspaces by `user_id` where the
178
+ backend sends no `is_own`), `sortWorkspaces`, `groupConversations`
179
+ (generic over the host's conversation and workspace types),
180
+ `fileableWorkspaces`, `groupIsOpen`, `groupCanToggle`, `ELSEWHERE_GROUP`
181
+ and the `WorkspaceGroup`, `GroupableConversation` and `ChatWorkspace` types.
182
+ - `prepareMarkdown` on `@filigran/chatbot/markdown`: the panel's markdown
183
+ normalisation in its order, for a host's own markdown component.
184
+ - `waitingGameUrl` says where the waiting panel's invitation to play leads
185
+ (an http(s) URL or a path of the host's origin), over
186
+ `{agentDashboardUrl}/chat?arcade=play`.
187
+ - New exported types: `ChatPanelHandle`, `ChatPanelMode`, `ChatSendOptions`,
188
+ `ChatSendOutcome`, `ChatSendDraft`, `ChatSendDecision`, `ChatHeldOutcome`,
189
+ `ChatTurnEnd`, `ChatTurnOutcome`, `ChatMarkerRenderer`, `ChatMarkerContext`,
190
+ `ChatCodeBlockRenderer`, `ChatCodeBlockContext`, `ChatStreamEventInfo`,
191
+ `ChatMessageSlotContext`, `ChatAnsweredBy` and `AgentStatusState`.
192
+
193
+ ### Changed
194
+
195
+ - The panel ships its own translations (es, fr, ja, beside English) and
196
+ speaks `locale`'s language without the host translating anything. `t` is
197
+ now an override: the host's wording wins where it translates a key, the
198
+ panel's catalog answers where it returns the key. A host translating every
199
+ key, as before, sees no change. `ChatToggleButton` takes `locale` too.
200
+ A host that passes `t` but no `locale` keeps the panel in English where its
201
+ `t` returns the key; a host passing neither now gets the browser's
202
+ language where the panel has it, rather than English.
203
+ - A stopped answer says `Stopped`, rather than vanishing when nothing was
204
+ written yet.
205
+ - The Send, attach, close and view-switcher buttons have accessible names.
206
+ - Files dragged over the thread attach on drop (wherever uploads are on), and
207
+ the drop does not reach the host's own drop zones.
208
+ - A conversation being read back shows a skeleton rather than the welcome
209
+ screen.
210
+ - A conversation whose restore fails because the backend is unreachable or
211
+ answers 502, 503 or 504 keeps its id, rather than being dropped for a new
212
+ chat, and is read back once the backend answers (retried with backoff, and
213
+ at once when the browser is back online or the page is shown again, never
214
+ over a turn in progress); any other error still resets it.
215
+ - A conversation whose restore answers 401 or 403 (expired credentials) keeps
216
+ its id too, rather than being dropped for a new chat (a host's `?c=`
217
+ survives): the panel shows the response's `detail` (or `This conversation
218
+ could not be loaded.`) with a Retry button, and reads the conversation back
219
+ as soon as `requestHeaders` change.
220
+ - A message the backend refuses before streaming (a non-2xx answer) shows the
221
+ response's `detail` (a string, or the `message` or `detail` of an object)
222
+ instead of `Unable to connect. Please check the configuration.`, which stays
223
+ for a refusal that says nothing; `send` still resolves `'failed'`.
224
+ - The stylesheet declares Tailwind's theme variables (`--color-*`,
225
+ `--radius-*`, `--shadow-*`, ...) on the panel root only, no longer on
226
+ `:root` and `:host`, so loading it never redeclares a host's own variables;
227
+ the `@property` fallbacks of browsers without it are kept to the panel's
228
+ elements too. A host element outside the panel no longer inherits them.
229
+ - A fenced code block's language label shows the whole language
230
+ (`objective-c`, not `objective`).
231
+ - A long agent name in the header is cut short rather than pushing the
232
+ header's controls off a narrow panel.
233
+ - A steer the backend refuses shows the refusal's `detail` above the composer,
234
+ the text going back to the composer as before; a `409` (the turn has just
235
+ ended, an ordinary race) shows nothing. A steer stored that the backend
236
+ says no turn will answer (`"answered": false`) stays, with a notice that it
237
+ will be read with the next message, until that send.
238
+ - The composer's footer under a message that waits for the running turn
239
+ (a reference, a mention) says `This message waits for the current response`,
240
+ a new key, in place of `Referenced conversations wait for the current
241
+ response`.
242
+
243
+ ### Fixed
244
+
245
+ - A conversation read back after the user has already sent a message no
246
+ longer replaces that turn: its history joins the thread in place once the
247
+ turn ends (a stored message missing from the screen after the one stored
248
+ before it), and its agent's name shows at once.
249
+ - Sending no longer throws on a page served over plain http, where
250
+ `crypto.randomUUID` does not exist.
251
+
252
+ ### Host opt-in
253
+
254
+ - `apiEndpoints.cancel` (no default): Stop asks the backend to end the turn and
255
+ waits for the stream's `cancelled` frame, instead of dropping the stream. An
256
+ answer of `{ "turn": "none" }` lets go of the turn at once and reads the
257
+ conversation back, unless the Stop went before the panel's own send started
258
+ its turn: it then carries `"awaiting_send": true`, and on `none` stays
259
+ pending and is sent again, without the flag, as the turn starts; any other
260
+ answer reached a turn, and nothing goes again. A refused Stop (e.g. `503`)
261
+ keeps the turn running and says why above the composer; the status line
262
+ keeps what the turn reported since the click.
263
+ - `apiEndpoints.skipWait` (no default): `Continue now` on an agent's wait.
264
+ - `apiEndpoints.events` (no default): the panel follows the conversation's
265
+ event stream and renders turns started in another tab or before a reload,
266
+ user messages sent elsewhere, new titles and approvals answered elsewhere;
267
+ a panel whose own stream drops mid-turn picks the turn up from it, keeping
268
+ the segments it closed. Stop during the handover ends the turn. A relayed
269
+ turn the stream let go of while the page was hidden holds sends, and runs
270
+ on for `onTurnRunningChange`, until the reopened stream replays it, or, 5 s
271
+ after the page shows again or at once on a refused stream, until the
272
+ conversation read back says how it ended. Every frame of a turn names it
273
+ (`turn_id`): on the event stream, and on the message stream from its
274
+ `turn_started` on; an event stream frame of a turn naming none is not read,
275
+ and the console warns of it once per frame type. See
276
+ [Turns that outlive their stream](README.md#turns-that-outlive-their-stream)
277
+ for reconnection, the client id and replays.
278
+ - With `cancel`, `skipWait` or `events` named, a send made while a turn ended
279
+ provisionally still winds down is held, as during a turn, until its stored
280
+ end or its stream's close (the panel's own, for its own turn; another turn
281
+ starting ends it too), at most 5 s: the backend still holds the
282
+ conversation, and the message would join the ending turn unanswered. A
283
+ relayed turn winds down across the event stream's reopening.
284
+ - With `cancel`, `skipWait` or `events` named, a new chat's conversation is
285
+ created before its first message, so Stop, "Continue now" and steering
286
+ reach the backend from the first turn.
287
+ - `apiEndpoints.messagesPage` (no default): "Load earlier messages" fetches
288
+ older messages from the backend a page at a time, when the restore says
289
+ there are some (`has_more`), and keeps the reader where they were.
290
+ - `apiEndpoints.clearAll` (no default): the conversation list and the
291
+ header's history menu offer "Clear all conversations", confirmed first.
292
+ - `apiEndpoints.tts` (no default): read aloud uses the backend's voice, a
293
+ long answer in several bounded requests played in turn, and the browser's
294
+ voice for what is left when the route fails.
295
+
11
296
  ## 3.14.0 — 2026-10-19
12
297
 
13
298
  ### Added