@matterfact/embed 0.11.1 → 0.11.2
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/README.md +17 -497
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,518 +44,38 @@ Ships types. In the Next.js App Router it needs a `'use client'` boundary.
|
|
|
44
44
|
`type="module"` is required — the loader is an ES module. `src` (where the loader is
|
|
45
45
|
served) and `data-origin` (where the chat is served) are different things.
|
|
46
46
|
|
|
47
|
-
## Where it renders
|
|
48
|
-
|
|
49
|
-
By default: a floating launcher in the corner, draggable, with a right-click menu.
|
|
50
|
-
|
|
51
|
-
Prefer it in **your own** side panel, drawer or tab? Add `inline` — it renders where you
|
|
52
|
-
put it and fills that space, sized by your CSS. No launcher is drawn and it never resizes
|
|
53
|
-
itself; showing/hiding your panel is the open/close control.
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
<aside className="w-[380px] border-l">
|
|
57
|
-
<MatterfactAgent inline publishableKey="pk_live_…" />
|
|
58
|
-
</aside>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Script-tag equivalent: `data-container="#your-panel"`.
|
|
62
|
-
|
|
63
47
|
## Embedding an artifact
|
|
64
48
|
|
|
65
|
-
Drop a matterfact **artifact** (a chart, table, dashboard you built in matterfact) straight
|
|
66
|
-
into your page:
|
|
67
|
-
|
|
68
49
|
```tsx
|
|
69
50
|
import { MatterfactArtifact } from '@matterfact/embed/react';
|
|
70
51
|
|
|
71
|
-
<MatterfactArtifact slug="
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
`slug` and `owner` identify the artifact; `token` is the read-only embed token you mint for
|
|
75
|
-
it (the **Share → Embed** dialog in matterfact generates the whole snippet, token included).
|
|
76
|
-
It renders chrome-free in a sandboxed iframe and fills its container — size it with
|
|
77
|
-
`className`/`style`.
|
|
78
|
-
|
|
79
|
-
**The payoff when the agent is on the same page:** embed a `<MatterfactAgent>` too and the
|
|
80
|
-
agent automatically sees the artifact you embedded and can answer questions about the data
|
|
81
|
-
behind it — no wiring. The agent only ever gets read access to that artifact's own data, and
|
|
82
|
-
the token never reaches the model. Nothing extra to configure.
|
|
83
|
-
|
|
84
|
-
Script-tag / non-React hosts can use the iframe directly (the Share dialog shows this form):
|
|
85
|
-
|
|
86
|
-
```html
|
|
87
|
-
<iframe
|
|
88
|
-
src="https://app.matterfact.com/embed/artifacts/tsla-liquidity?owner=you@firm.com&t=…"
|
|
89
|
-
width="100%"
|
|
90
|
-
height="600"
|
|
91
|
-
style="border:0"
|
|
92
|
-
></iframe>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Linking your app's matterfact content
|
|
96
|
-
|
|
97
|
-
A co-embedded `<MatterfactArtifact>` (above) is found automatically. But your app may
|
|
98
|
-
render matterfact content another way — a report/dossier you pulled from matterfact and
|
|
99
|
-
render yourself, or artifacts spread across many routes. Tell the agent where that content
|
|
100
|
-
lives with a **site map**: your routes, each tagged with what kind of matterfact content
|
|
101
|
-
it carries. The agent uses it to reason about — and navigate toward — content the user
|
|
102
|
-
isn't currently looking at.
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
<MatterfactAgent
|
|
106
|
-
sitemap={[
|
|
107
|
-
{
|
|
108
|
-
path: '/company/:ticker/dossier',
|
|
109
|
-
label: 'Company Dossier',
|
|
110
|
-
content: { kind: 'mf-document', doctype: 'company_dossier' },
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
path: '/talent/exec-departures',
|
|
114
|
-
label: 'Exec Departures',
|
|
115
|
-
content: { kind: 'mf-artifact', slug: 'exec-departure-tracker' },
|
|
116
|
-
},
|
|
117
|
-
{ path: '/screener', label: 'Screener', content: { kind: 'host-data' } },
|
|
118
|
-
]}
|
|
119
|
-
/>
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
```js
|
|
123
|
-
// script tag — same shape
|
|
124
|
-
window.matterfact = {
|
|
125
|
-
sitemap: [
|
|
126
|
-
/* … */
|
|
127
|
-
],
|
|
128
|
-
};
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Each entry is **classification only** — a route pattern, a label, and a `content` tag:
|
|
132
|
-
|
|
133
|
-
- `{ kind: 'mf-artifact', slug }` — a matterfact artifact rendered on that route.
|
|
134
|
-
- `{ kind: 'mf-document', doctype }` — a matterfact document/report (dossier, etc.).
|
|
135
|
-
- `{ kind: 'host-data' }` — your own data; not matterfact content.
|
|
136
|
-
|
|
137
|
-
**Never put a capability token in the site map.** It is app-wide and rides on every turn;
|
|
138
|
-
the concrete artifact/document on the _current_ page arrives separately (the live artifact
|
|
139
|
-
iframe, or per-page context — see below), where the token belongs.
|
|
140
|
-
|
|
141
|
-
### Documents on the current page
|
|
142
|
-
|
|
143
|
-
For a matterfact **document** the user is looking at, hand the agent its id via page
|
|
144
|
-
context (the same `context.entities` you already declare — see _What the agent can see_):
|
|
145
|
-
|
|
146
|
-
```tsx
|
|
147
|
-
<MatterfactAgent
|
|
148
|
-
getPageContext={() => ({
|
|
149
|
-
route: '/company/:ticker/dossier',
|
|
150
|
-
entities: [
|
|
151
|
-
{
|
|
152
|
-
kind: 'document',
|
|
153
|
-
id: currentDoc.mfDocId, // the matterfact MF_DOC_ID
|
|
154
|
-
label: currentDoc.title,
|
|
155
|
-
data: { href: location.href }, // optional: enables the panel's "open on page" deeplink
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
})}
|
|
159
|
-
/>
|
|
52
|
+
<MatterfactArtifact slug="…" owner="you@firm.com" token="…" widgetOrigin="https://app.matterfact.com" />;
|
|
160
53
|
```
|
|
161
54
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
its own systems by `MF_DOC_ID`; it never reads your database.
|
|
55
|
+
Renders a matterfact chart/table/dashboard chrome-free in your page. Embed a
|
|
56
|
+
`<MatterfactAgent>` on the same page and it automatically sees the artifact and can answer
|
|
57
|
+
about the data behind it — no wiring, and the token never reaches the model.
|
|
166
58
|
|
|
167
|
-
|
|
59
|
+
## Documentation
|
|
168
60
|
|
|
169
|
-
The
|
|
170
|
-
|
|
61
|
+
The full integration guide — placement, page context, linking your matterfact content,
|
|
62
|
+
host tools, telemetry, authentication (incl. Microsoft Entra), and debugging — is available
|
|
63
|
+
to partners after signing in, in the **Embedding** section of the matterfact docs:
|
|
171
64
|
|
|
172
|
-
**
|
|
173
|
-
can open any of them from any page, even routes where you don't embed the `<MatterfactArtifact>`
|
|
174
|
-
iframe:
|
|
65
|
+
**[app.matterfact.com/docs](https://app.matterfact.com/docs)** *(partner sign-in required)*
|
|
175
66
|
|
|
176
|
-
|
|
177
|
-
<MatterfactAgent
|
|
178
|
-
artifacts={[
|
|
179
|
-
{ slug: 'exec-departure-tracker', owner: 'you@firm.com', token: '…', label: 'Talent Bank' },
|
|
180
|
-
{ slug: 'global-datacenter-buildout', owner: 'you@firm.com', token: '…', label: 'Data Centers' },
|
|
181
|
-
]}
|
|
182
|
-
/>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
```js
|
|
186
|
-
// script tag — same shape
|
|
187
|
-
window.matterfact = { artifacts: [{ slug: '…', owner: '…', token: '…', label: '…' }] };
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
This replaces the old workaround of mounting hidden, zero-size `<MatterfactArtifact>` iframes
|
|
191
|
-
just to make a board's grant reachable. The `token` stays in this host-page config and in the
|
|
192
|
-
grant channel — it never enters the chat transcript or reaches the model.
|
|
193
|
-
|
|
194
|
-
**A resolver for content you look up per-request — `resolve`.** When your set of documents or
|
|
195
|
-
boards is per-user or per-ticker (too large to declare), give the agent a function that turns a
|
|
196
|
-
classification into a concrete reference. matterfact defines the tool contract (name, arguments,
|
|
197
|
-
result shape, and how the agent uses it — so it costs you no agent-instruction budget); you
|
|
198
|
-
supply the implementation:
|
|
199
|
-
|
|
200
|
-
```tsx
|
|
201
|
-
<MatterfactAgent
|
|
202
|
-
resolve={{
|
|
203
|
-
// doctype + a key (e.g. a ticker) -> the matterfact document id, from YOUR lookup.
|
|
204
|
-
document: async ({ doctype, key }) => {
|
|
205
|
-
const row = await myHub.lookup(doctype, key);
|
|
206
|
-
return { id: row.MF_DOC_ID, label: `${key} ${doctype}`, href: hrefFor(doctype, key) };
|
|
207
|
-
},
|
|
208
|
-
// a slug -> a read grant, when you resolve boards dynamically instead of declaring them.
|
|
209
|
-
artifact: async ({ slug }) => myHub.artifactGrant(slug),
|
|
210
|
-
}}
|
|
211
|
-
/>
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
The agent calls `mf.resolveDocument` / `mf.resolveArtifact` when the site map shows the content
|
|
215
|
-
lives on another route, gets back an id (and, for a document, an optional `href` deeplink), and
|
|
216
|
-
cites it as a chip — exactly as for on-page content. **matterfact never queries your data**: your
|
|
217
|
-
function runs the lookup, matterfact only ever receives the id it returns. `resolve` is a host
|
|
218
|
-
tool (see [Host tools](#host-tools)), so your app's `host_tools` policy must allow `mf.*` — it
|
|
219
|
-
does by default.
|
|
220
|
-
|
|
221
|
-
## Signing users in
|
|
222
|
-
|
|
223
|
-
By default the widget runs its own sign-in in a popup (the only way an embedded frame can
|
|
224
|
-
get a real first-party session). If your app already holds an identity this matterfact
|
|
225
|
-
deployment trusts, hand the token over for silent sign-in:
|
|
226
|
-
|
|
227
|
-
```tsx
|
|
228
|
-
<MatterfactAgent getAuthToken={() => auth.currentUser?.getIdToken() ?? null} />
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
```js
|
|
232
|
-
// script tag
|
|
233
|
-
window.matterfact = { getEmbedAuthToken: () => getToken() };
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
We verify the token either way — this skips a redundant login, it doesn't bypass auth.
|
|
237
|
-
|
|
238
|
-
### Microsoft Entra (single-tenant)
|
|
239
|
-
|
|
240
|
-
On a single-tenant Entra deployment there are **two app registrations**, and mixing them up
|
|
241
|
-
is the thing that catches everyone:
|
|
242
|
-
|
|
243
|
-
- the **matterfact** app registration — you created it in your tenant and gave us its
|
|
244
|
-
client id + secret. It _exposes_ the scope.
|
|
245
|
-
- the app you're **embedding into** (e.g. your portal) — a **different** registration with
|
|
246
|
-
its **own** client id. It's the one your users sign into.
|
|
247
|
-
|
|
248
|
-
The token you pass to `getAuthToken` must be an **access token** for the matterfact app's
|
|
249
|
-
scope — **not** your app's ID token. An ID token is addressed to your app, not matterfact's,
|
|
250
|
-
and is rejected.
|
|
251
|
-
|
|
252
|
-
A one-time setup on the matterfact registration (expose the scope, accept v2 tokens,
|
|
253
|
-
pre-authorise your embedding app) is run by your Entra admin — the script and full
|
|
254
|
-
walkthrough are in the [integration guide](https://app.matterfact.com/docs/api). Once it's
|
|
255
|
-
done, your embedding app requests that scope with MSAL and hands the access token over:
|
|
256
|
-
|
|
257
|
-
```tsx
|
|
258
|
-
import { useMsal } from '@azure/msal-react';
|
|
259
|
-
|
|
260
|
-
const { instance, accounts } = useMsal(); // configured with YOUR app's client id
|
|
261
|
-
|
|
262
|
-
const getAuthToken = async () => {
|
|
263
|
-
const { accessToken } = await instance.acquireTokenSilent({
|
|
264
|
-
scopes: ['api://<matterfact-client-id>/embed.access'],
|
|
265
|
-
account: accounts[0],
|
|
266
|
-
});
|
|
267
|
-
return accessToken; // access token, not the ID token
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
<MatterfactAgent publishableKey="pk_live_…" getAuthToken={getAuthToken} />;
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
The access token must carry an `email` / `preferred_username` / `upn` claim — the identity
|
|
274
|
-
is keyed on it. Until the scope is set up, the default sign-in popup works with no
|
|
275
|
-
configuration.
|
|
276
|
-
|
|
277
|
-
## What the agent can see
|
|
278
|
-
|
|
279
|
-
Nothing until a user opens the chat; then a semantic snapshot of the **visible** page.
|
|
280
|
-
Never captured, with no setting to enable it: keystrokes, input/textarea values, passwords,
|
|
281
|
-
cookies, storage, the raw DOM, screenshots, network traffic, or anything inside
|
|
282
|
-
`data-mf-private`. PII is scrubbed in the browser before anything leaves the page. The
|
|
283
|
-
exact per-turn payload is inspectable in the widget's "what can it see?" panel.
|
|
284
|
-
|
|
285
|
-
Declare what your page is, and it rides on every turn:
|
|
286
|
-
|
|
287
|
-
```js
|
|
288
|
-
window.matterfact = {
|
|
289
|
-
context: {
|
|
290
|
-
route: '/orders/:id',
|
|
291
|
-
description: 'Acme order 8813 — 3 items, unpaid',
|
|
292
|
-
entities: [{ kind: 'x:order', id: '8813', label: 'Acme order' }],
|
|
293
|
-
},
|
|
294
|
-
};
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### Keeping context fresh (virtualized SPAs)
|
|
298
|
-
|
|
299
|
-
The static `context` above is read on load and on URL navigation. In a **virtualized SPA**,
|
|
300
|
-
what the agent should know changes without the path changing — a team switch, a filter edit,
|
|
301
|
-
a row selection — and a static object goes stale until the next navigation. Give a **pull
|
|
302
|
-
callback** instead and the widget reads it **fresh at the start of every turn** — symmetric
|
|
303
|
-
with `getAuthToken`:
|
|
304
|
-
|
|
305
|
-
```tsx
|
|
306
|
-
<MatterfactAgent
|
|
307
|
-
publishableKey="pk_live_…"
|
|
308
|
-
getPageContext={() => ({
|
|
309
|
-
route: '/portfolios/:id',
|
|
310
|
-
description: `Growth Sleeve${selected ? ` — focused on ${selected}` : ''}`,
|
|
311
|
-
entities: holdings.map((h) => ({
|
|
312
|
-
kind: 'x:ticker',
|
|
313
|
-
id: h.ticker,
|
|
314
|
-
label: h.ticker,
|
|
315
|
-
})),
|
|
316
|
-
data: { selected },
|
|
317
|
-
})}
|
|
318
|
-
/>
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
```js
|
|
322
|
-
// script tag — same idea, a global callback (supersedes window.matterfact.context)
|
|
323
|
-
window.matterfact = {
|
|
324
|
-
getPageContext: () => ({ route: '/portfolios/:id', description: '…' }),
|
|
325
|
-
};
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
May be async (read a store, await a selector). Auto-detected fields (url/path/title, embedded
|
|
329
|
-
artifacts) are still merged on top. Resolution order, first non-null wins: the React
|
|
330
|
-
`getPageContext` prop → `window.matterfact.getPageContext()` → the static `window.matterfact.context`.
|
|
331
|
-
See [`examples/embed-demo`](examples/embed-demo) (React) and [`examples/host-panel.html`](examples/host-panel.html) (script tag).
|
|
332
|
-
|
|
333
|
-
Want none of this? `pageContext={false}` (or `data-page-context="off"` on the script
|
|
334
|
-
tag) turns off page observation entirely — not just the declared context object above,
|
|
335
|
-
but the snapshot, the focus/activity stream, and artifact grants too. The loader never
|
|
336
|
-
installs the observers that would collect any of it, and a request to send a snapshot
|
|
337
|
-
gets nothing back. Default is on: seeing the page is the point of the widget.
|
|
338
|
-
|
|
339
|
-
```tsx
|
|
340
|
-
<MatterfactAgent publishableKey="pk_live_…" pageContext={false} />
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Hoist apps
|
|
344
|
-
|
|
345
|
-
If the host page is an [XH Hoist](https://xh.io/) SPA — `window.XH` is present — the widget
|
|
346
|
-
automatically extracts rich page context and a snapshot from it. No `window.matterfact.context`,
|
|
347
|
-
no `getPageContext`, no host code at all: the grids, charts, and navigation already mounted in
|
|
348
|
-
the page are the source of truth. On any other page this is skipped entirely and the widget falls
|
|
349
|
-
back to the generic DOM snapshot described above.
|
|
350
|
-
|
|
351
|
-
This affects two different messages the widget sends, independently:
|
|
352
|
-
|
|
353
|
-
- The per-turn **context** (route + one-line description) still prefers an explicit
|
|
354
|
-
`getPageContext` / `window.matterfact.context` when the host declares one — Hoist is only the
|
|
355
|
-
fallback when neither is set.
|
|
356
|
-
- The on-demand **snapshot** (what the agent sees when it looks at the page) is Hoist-first,
|
|
357
|
-
unconditionally, ahead of the generic DOM walk — whether or not the host also declares context.
|
|
358
|
-
|
|
359
|
-
What gets extracted:
|
|
360
|
-
|
|
361
|
-
- **Route + description** — `XH.routerState`, turned into a one-line summary, e.g. `"Viewing
|
|
362
|
-
default.company.tsr, 1 selected, 1 grid(s)."`
|
|
363
|
-
- **Grids** — each mounted `GridModel` that has rows or a selection (an empty, not-yet-loaded
|
|
364
|
-
grid — e.g. an off-view tab — is skipped), as a table: columns (a grouped column is flattened
|
|
365
|
-
to its visible leaves and prefixed with its group, e.g. `"5Y Value"`), the current selection,
|
|
366
|
-
and up to `rows` data rows with a total count.
|
|
367
|
-
- **Charts** — each mounted `ChartModel` with at least one series, summarised per series (point
|
|
368
|
-
count, min, max, last) — never the raw series.
|
|
369
|
-
- **Site map** — each mounted `TabContainerModel`, joined to the router's routes: the app's
|
|
370
|
-
navigable destinations, grouped by nav level (top tabs, a per-record sidebar, …), with the
|
|
371
|
-
current one marked. This leads the snapshot, since it barely changes turn to turn.
|
|
372
|
-
|
|
373
|
-
It reads roughly like the ARIA snapshot above, with its own block types:
|
|
374
|
-
|
|
375
|
-
```
|
|
376
|
-
- navigation "Site":
|
|
377
|
-
- link "Companies" → /app/companies
|
|
378
|
-
- link "Company Analysis" [current] → /app/company
|
|
379
|
-
- table "Peer TSR":
|
|
380
|
-
- rowgroup "columns":
|
|
381
|
-
- columnheader "Ticker"
|
|
382
|
-
- columnheader "1Y TSR"
|
|
383
|
-
- rowgroup "rows":
|
|
384
|
-
- row:
|
|
385
|
-
- cell "SITE"
|
|
386
|
-
- cell "12.3%"
|
|
387
|
-
- text: "showing 1 of 412 rows"
|
|
388
|
-
- figure "TSR vs Index":
|
|
389
|
-
- text: "series SITE: 4 points, min 1, max 10, last 10"
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Configuring it
|
|
393
|
-
|
|
394
|
-
```js
|
|
395
|
-
window.matterfact = {
|
|
396
|
-
hoist: {
|
|
397
|
-
rows: 50, // rows sent per grid before truncating (default 50)
|
|
398
|
-
excludeModels: [], // class names ('ChartModel') or specific model ids to omit
|
|
399
|
-
actions: { navigate: 'off' }, // see "Letting the agent navigate" below
|
|
400
|
-
},
|
|
401
|
-
};
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
| Key | Type | Default | What |
|
|
405
|
-
| ------------------ | ------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
406
|
-
| `rows` | `number` | `50` | Rows sent per grid before truncating. The full row count still rides along. |
|
|
407
|
-
| `excludeModels` | `string[]` | `[]` | Model class names (drops every instance) or specific model ids to omit — applies to grids, charts, and nav tab containers alike. |
|
|
408
|
-
| `actions.navigate` | `'off' \| 'confirm' \| 'auto'` | `'off'` | Whether the agent can drive navigation — see below. |
|
|
409
|
-
|
|
410
|
-
### Letting the agent navigate
|
|
411
|
-
|
|
412
|
-
Set `actions.navigate` and the widget advertises a `hoist.navigate` tool bounded to exactly the
|
|
413
|
-
destinations in the CURRENT site map — the agent can't ask for somewhere that isn't reachable
|
|
414
|
-
from where the user actually is, and every call is re-validated against the live site map (and
|
|
415
|
-
the live policy), never whatever the widget last cached, before it runs:
|
|
416
|
-
|
|
417
|
-
- `'off'` (default) — the tool isn't advertised at all.
|
|
418
|
-
- `'confirm'` — advertised, and the widget shows an approval card the user must accept before
|
|
419
|
-
each call.
|
|
420
|
-
- `'auto'` — advertised and runs without asking.
|
|
421
|
-
|
|
422
|
-
```tsx
|
|
423
|
-
<MatterfactAgent publishableKey="pk_live_…" actions={{ navigate: 'confirm' }} />
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
```js
|
|
427
|
-
// script tag — same idea, the global the React prop above is sugar for
|
|
428
|
-
window.matterfact = { hoist: { actions: { navigate: 'confirm' } } };
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
Changing `actions` on `<MatterfactAgent>` remounts the widget (a fresh chat session) so the new
|
|
432
|
-
policy is advertised immediately — it's identity-defining config, not a live callback like
|
|
433
|
-
`getPageContext`.
|
|
434
|
-
|
|
435
|
-
Everything else about page observation still applies here: values are redacted before anything
|
|
436
|
-
leaves the page, row counts are capped, and `pageContext={false}` turns this off along with
|
|
437
|
-
everything else (see above).
|
|
438
|
-
|
|
439
|
-
## Host tools
|
|
440
|
-
|
|
441
|
-
`hoist.navigate` above is one instance of a general mechanism: **the host page can offer the
|
|
442
|
-
agent tools to call**. There are three kinds, distinguished by who owns the contract and who
|
|
443
|
-
runs the code:
|
|
444
|
-
|
|
445
|
-
| Prefix | Contract | Runs where | Examples |
|
|
446
|
-
| ---------- | ---------- | ------------------- | ------------------------------------------------ |
|
|
447
|
-
| `hoist.*` | matterfact | the widget's adapter | `hoist.navigate` (above) |
|
|
448
|
-
| `mf.*` | matterfact | **your** function | `mf.resolveDocument`, `mf.resolveArtifact` (above) |
|
|
449
|
-
| `app.*` | **you** | **your** function | anything you declare |
|
|
450
|
-
|
|
451
|
-
`hoist.*` and `mf.*` you get by turning on the features above. `app.*` is your own tools, which
|
|
452
|
-
you declare with the `tools` prop:
|
|
453
|
-
|
|
454
|
-
```tsx
|
|
455
|
-
<MatterfactAgent
|
|
456
|
-
tools={[
|
|
457
|
-
{
|
|
458
|
-
name: 'exportBook', // advertised to the agent as `app.exportBook`
|
|
459
|
-
description: 'Export the current book to CSV.',
|
|
460
|
-
inputSchema: { type: 'object', properties: { scope: { type: 'string' } } },
|
|
461
|
-
confirm: 'required', // 'required' (default) shows an approval card; 'auto' runs without asking
|
|
462
|
-
handler: async ({ scope }) => myApp.exportBook(scope),
|
|
463
|
-
},
|
|
464
|
-
]}
|
|
465
|
-
/>
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
```js
|
|
469
|
-
// script tag — same shape
|
|
470
|
-
window.matterfact = { tools: [{ name: '…', description: '…', handler: async () => {} }] };
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
Names are namespaced into `app.` automatically, so a host tool can never collide with a
|
|
474
|
-
matterfact one. A tool's `handler` result and any error it throws are **redacted before leaving
|
|
475
|
-
the page**; a handler that throws, rejects, or hangs (beyond 30s) becomes a normal "not
|
|
476
|
-
completed" result the agent reads and adapts to — never a crash. Like `actions`, changing the
|
|
477
|
-
_shape_ of your tools (a name, description, schema, or confirm policy) remounts the widget so the
|
|
478
|
-
new set is advertised; changing only a handler's body does not, so an inline `handler` closure
|
|
479
|
-
won't churn the chat session.
|
|
480
|
-
|
|
481
|
-
### Who decides which tools are exposed
|
|
482
|
-
|
|
483
|
-
Advertising a tool is necessary but not sufficient: **which tools an app may actually expose is
|
|
484
|
-
a server-side setting** on your embed app (`host_tools`: `off` / `allowlist` / `all`, plus an
|
|
485
|
-
allow-list of names). The default allows matterfact's own `hoist.*` and `mf.*` and denies
|
|
486
|
-
arbitrary `app.*` until an operator grants it — so `resolve` works out of the box, and turning on
|
|
487
|
-
your first `app.*` tool is a deliberate decision made in the admin console, not something a page
|
|
488
|
-
can do on its own. Ask your matterfact contact to enable the `app.*` names you need.
|
|
489
|
-
|
|
490
|
-
### Telemetry — `onEvent`
|
|
491
|
-
|
|
492
|
-
One hook pipes the whole widget into your own observability — lifecycle (`ready`, `open`,
|
|
493
|
-
`close`, `navigate`, `auth`, `error`), each chat turn (`chat`), and every tool call (`tool`):
|
|
494
|
-
|
|
495
|
-
```tsx
|
|
496
|
-
<MatterfactAgent
|
|
497
|
-
onEvent={(e) => myTelemetry.track(`mf.${e.type}`, e)}
|
|
498
|
-
/>
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
Fire-and-forget and fully isolated — a throw or a slow callback here never blocks or breaks the
|
|
502
|
-
widget, a tool call, or a run. Chat events are content-free (phase + opaque chat id, never
|
|
503
|
-
message text). `onToolEvent` still works as a tool-only subset for back-compat.
|
|
504
|
-
|
|
505
|
-
**→ Full event reference: [`docs/telemetry.md`](docs/telemetry.md).**
|
|
506
|
-
|
|
507
|
-
## Microphone / dictation
|
|
508
|
-
|
|
509
|
-
The composer supports voice dictation; the loader grants the iframe `allow="microphone"`.
|
|
510
|
-
If your site sends a `Permissions-Policy` response header that denies `microphone`, the
|
|
511
|
-
browser blocks the widget's mic regardless — add
|
|
512
|
-
`microphone=(self "https://app.matterfact.com")` (or your widget origin) to that header.
|
|
513
|
-
|
|
514
|
-
## Debugging a live embed
|
|
515
|
-
|
|
516
|
-
Add `?mfdev=1` to your page's URL to open a panel showing exactly what the widget sends —
|
|
517
|
-
on a real deployment, no dev build. It grants nothing.
|
|
518
|
-
|
|
519
|
-
Building the host page yourself and don't want to depend on a query param? `dev={true}`
|
|
520
|
-
does the same thing programmatically — either trigger works, and they're OR'd together.
|
|
521
|
-
There's no script-tag attribute for it on purpose: the URL trigger exists precisely so an
|
|
522
|
-
engineer can flip it on a live deployment without redeploying the customer's site; the
|
|
523
|
-
prop is for a host that wants it wired into their own debug tooling instead.
|
|
524
|
-
|
|
525
|
-
```tsx
|
|
526
|
-
<MatterfactAgent
|
|
527
|
-
publishableKey="pk_live_…"
|
|
528
|
-
dev={process.env.NODE_ENV !== 'production'}
|
|
529
|
-
/>
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
## Security model
|
|
533
|
-
|
|
534
|
-
- The publishable key is public and useless alone; the boundary is the user holding a
|
|
535
|
-
matterfact identity obtained through sign-in.
|
|
536
|
-
- Origin pinning is a misconfiguration guard, not a security boundary.
|
|
537
|
-
- The page snapshot is untrusted reference data to the agent — grounding, never
|
|
538
|
-
instructions.
|
|
67
|
+
Don't have access yet? Ask your matterfact contact.
|
|
539
68
|
|
|
540
69
|
## Examples
|
|
541
70
|
|
|
542
|
-
Runnable examples ship inside this package (under `examples/`)
|
|
543
|
-
cloning anything:
|
|
544
|
-
|
|
545
|
-
- **`examples/embed-demo/`** — a tiny Vite + React app that mounts `<MatterfactAgent inline>`
|
|
546
|
-
in a side panel next to an embedded `<MatterfactArtifact>`, configured entirely via `.env`. Also
|
|
547
|
-
ships an opt-in mock Hoist app (`?hoist=1`) to try the automatic Hoist-app path above with no
|
|
548
|
-
real Hoist deployment — see its own README.
|
|
549
|
-
Browse it on [unpkg](https://unpkg.com/browse/@matterfact/embed/examples/embed-demo/), or
|
|
550
|
-
after `npm install @matterfact/embed` find it at
|
|
551
|
-
`node_modules/@matterfact/embed/examples/embed-demo/` — copy it out, `npm install`, `npm run dev`.
|
|
552
|
-
- **`examples/host-panel.html`** — the inline-placement pattern as a single HTML file (script-tag loader).
|
|
553
|
-
- **`examples/demo-host/`** — a zero-dependency host site proving the script-tag loader end to end.
|
|
554
|
-
|
|
555
|
-
## Full guide
|
|
71
|
+
Runnable examples ship inside this package (under `examples/`):
|
|
556
72
|
|
|
557
|
-
|
|
558
|
-
|
|
73
|
+
- **`examples/embed-demo/`** — a Vite + React app mounting an inline agent next to an
|
|
74
|
+
embedded artifact, with host tools and a live `onEvent` activity log. Find it at
|
|
75
|
+
`node_modules/@matterfact/embed/examples/embed-demo/` after installing — copy it out,
|
|
76
|
+
`npm install`, `npm run dev`.
|
|
77
|
+
- **`examples/host-panel.html`** — the inline pattern as a single HTML file (script-tag loader).
|
|
78
|
+
- **`examples/demo-host/`** — a zero-dependency site proving the script-tag loader end to end.
|
|
559
79
|
|
|
560
80
|
## License
|
|
561
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matterfact/embed",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "The matterfact embeddable agent: mount the chat in a cross-origin iframe (a <script> loader for any site, or a <MatterfactAgent> React component), give the agent eyes on the host page, and embed matterfact artifacts inline with <MatterfactArtifact>.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"private": false,
|