@matterfact/embed 0.6.0 → 0.8.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.
Files changed (52) hide show
  1. package/README.md +99 -1
  2. package/dist/chunk-PNSYFXXU.js +791 -0
  3. package/dist/chunk-PNSYFXXU.js.map +1 -0
  4. package/dist/chunk-R2ZEJARX.js +776 -0
  5. package/dist/chunk-R2ZEJARX.js.map +1 -0
  6. package/dist/{chunk-4Q2ROXLR.js → chunk-UD7CAQXV.js} +2 -2
  7. package/dist/{chunk-7I37ZFAJ.js → chunk-UQCETVRF.js} +2 -2
  8. package/dist/chunk-W52Q7G4J.js +3 -0
  9. package/dist/chunk-W52Q7G4J.js.map +7 -0
  10. package/dist/context-ARBB2XD6.js +3 -0
  11. package/dist/{context-YX2KXFLI.js.map → context-ARBB2XD6.js.map} +1 -1
  12. package/dist/{context-WU2F5CBC.js → context-MVGSYIMB.js} +2 -2
  13. package/dist/embed.js +1 -1
  14. package/dist/embed.js.map +4 -4
  15. package/dist/index.cjs +986 -159
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +140 -31
  18. package/dist/index.d.ts +140 -31
  19. package/dist/index.js +542 -130
  20. package/dist/index.js.map +1 -1
  21. package/dist/react.cjs +1012 -165
  22. package/dist/react.cjs.map +1 -1
  23. package/dist/react.d.cts +33 -1
  24. package/dist/react.d.ts +33 -1
  25. package/dist/react.js +561 -132
  26. package/dist/react.js.map +1 -1
  27. package/dist/{snapshot-MUXE7KXX.js → snapshot-GL4YBMXD.js} +3 -3
  28. package/dist/{snapshot-MUXE7KXX.js.map → snapshot-GL4YBMXD.js.map} +1 -1
  29. package/dist/{snapshot-JOGZWESK.js → snapshot-UGTXZVB6.js} +2 -2
  30. package/examples/embed-demo/.env.example +5 -1
  31. package/examples/embed-demo/README.md +37 -0
  32. package/examples/embed-demo/package.json +1 -1
  33. package/examples/embed-demo/src/App.tsx +82 -44
  34. package/examples/embed-demo/src/HoistDemo.tsx +390 -0
  35. package/examples/embed-demo/src/config.ts +34 -4
  36. package/examples/embed-demo/src/main.tsx +7 -0
  37. package/examples/embed-demo/src/mockXH.ts +492 -0
  38. package/examples/embed-demo/src/placement.tsx +43 -0
  39. package/examples/embed-demo/src/styles.css +147 -2
  40. package/examples/embed-demo/vite.config.ts +2 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-AXKXNYRT.js +0 -381
  43. package/dist/chunk-AXKXNYRT.js.map +0 -1
  44. package/dist/chunk-D7R6WVHG.js +0 -2
  45. package/dist/chunk-D7R6WVHG.js.map +0 -7
  46. package/dist/chunk-RS6RMZ77.js +0 -396
  47. package/dist/chunk-RS6RMZ77.js.map +0 -1
  48. package/dist/context-YX2KXFLI.js +0 -3
  49. /package/dist/{chunk-4Q2ROXLR.js.map → chunk-UD7CAQXV.js.map} +0 -0
  50. /package/dist/{chunk-7I37ZFAJ.js.map → chunk-UQCETVRF.js.map} +0 -0
  51. /package/dist/{context-WU2F5CBC.js.map → context-MVGSYIMB.js.map} +0 -0
  52. /package/dist/{snapshot-JOGZWESK.js.map → snapshot-UGTXZVB6.js.map} +0 -0
@@ -0,0 +1,791 @@
1
+ "use client";
2
+
3
+ // src/redact.ts
4
+ var PII = [
5
+ /\b[\w.+-]+@[\w-]+\.[\w.]{2,}\b/g,
6
+ // email
7
+ /\b(?:\d[ -]?){13,19}\b/g,
8
+ // card-ish
9
+ /\b\d{3}[- ]?\d{2}[- ]?\d{4}\b/g,
10
+ // ssn — dashed, spaced, OR bare 9 digits
11
+ /\b\d{9,}\b/g,
12
+ // long bare digit runs: account / MRN / routing numbers
13
+ /(?:\+?\d{1,2}[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b/g,
14
+ // us phone
15
+ /\b(?:sk|pk|rk|ak)_(?:live|test)_[A-Za-z0-9]{8,}\b/g,
16
+ // stripe-style api keys
17
+ /\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g
18
+ // jwt
19
+ ];
20
+ function redact(text) {
21
+ let out = text;
22
+ for (const re of PII) out = out.replace(re, "[redacted]");
23
+ return out;
24
+ }
25
+
26
+ // src/adapters/hoist.ts
27
+ function detectHoist(read) {
28
+ let rt;
29
+ try {
30
+ rt = read();
31
+ } catch {
32
+ return null;
33
+ }
34
+ if (!rt) return null;
35
+ const worthwhile = !!rt.route.name || rt.grids.length > 0 || rt.charts.length > 0 || rt.nav.length > 0;
36
+ return worthwhile ? rt : null;
37
+ }
38
+ function quote(s, max = 120) {
39
+ const clean = redact(s.replace(/\s+/g, " ").trim());
40
+ const capped = clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
41
+ return `"${capped.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
42
+ }
43
+ function cell(value) {
44
+ const s = value == null ? "" : String(value);
45
+ return `- cell ${quote(s)}`;
46
+ }
47
+ function pushRow(row, cols, out, pad) {
48
+ out.push(`${pad}- row:`);
49
+ for (const c of cols) out.push(`${pad} ${cell(row[c.field])}`);
50
+ }
51
+ function renderGrid(grid, rowCap, out, depth) {
52
+ const pad = " ".repeat(depth);
53
+ out.push(`${pad}- table ${quote(grid.title || "grid")}:`);
54
+ out.push(`${pad} - rowgroup "columns":`);
55
+ for (const c of grid.columns)
56
+ out.push(`${pad} - columnheader ${quote(c.header || c.field)}`);
57
+ if (grid.selected.length) {
58
+ out.push(`${pad} - rowgroup "selected":`);
59
+ for (const r of grid.selected) pushRow(r, grid.columns, out, `${pad} `);
60
+ }
61
+ const shown = grid.rows.slice(0, Math.max(0, rowCap));
62
+ out.push(`${pad} - rowgroup "rows":`);
63
+ for (const r of shown) pushRow(r, grid.columns, out, `${pad} `);
64
+ out.push(
65
+ `${pad} - text: ${quote(`showing ${shown.length} of ${grid.totalCount} rows`)}`
66
+ );
67
+ }
68
+ function seriesSummary(name, data) {
69
+ if (!data.length) return `series ${name}: 0 points`;
70
+ let min = data[0];
71
+ let max = data[0];
72
+ for (const n of data) {
73
+ if (n < min) min = n;
74
+ if (n > max) max = n;
75
+ }
76
+ const last = data[data.length - 1];
77
+ return `series ${name}: ${data.length} points, min ${min}, max ${max}, last ${last}`;
78
+ }
79
+ function renderChart(chart, out, depth) {
80
+ const pad = " ".repeat(depth);
81
+ out.push(`${pad}- figure ${quote(chart.title || "chart")}:`);
82
+ for (const s of chart.series) {
83
+ out.push(`${pad} - text: ${quote(seriesSummary(s.name, s.data))}`);
84
+ }
85
+ }
86
+ function renderNav(groups, out, depth) {
87
+ const pad = " ".repeat(depth);
88
+ for (const g of groups) {
89
+ if (!g.items.length) continue;
90
+ out.push(`${pad}- navigation ${quote(g.label)}:`);
91
+ for (const d of g.items) {
92
+ const mark = d.current ? " [current]" : "";
93
+ out.push(`${pad} - link ${quote(d.label)}${mark} \u2192 ${redact(d.path)}`);
94
+ }
95
+ }
96
+ }
97
+ function flattenDestinations(rt) {
98
+ return rt.nav.flatMap((g) => g.items);
99
+ }
100
+ function buildNavigateTool(rt, policy) {
101
+ const destinations = flattenDestinations(rt);
102
+ const bySection = rt.nav.filter((g) => g.items.length).map((g) => `${g.label} (${g.items.map((d) => d.label).join(", ")})`).join("; ");
103
+ return {
104
+ name: "hoist.navigate",
105
+ description: redact(
106
+ `Navigate the user to a destination in the app. "destination" must be one of the destinations currently available, grouped by section: ${bySection || "(none available)"}.`
107
+ ),
108
+ inputSchema: {
109
+ type: "object",
110
+ properties: {
111
+ destination: {
112
+ type: "string",
113
+ enum: destinations.map((d) => d.label)
114
+ },
115
+ params: { type: "object" }
116
+ },
117
+ required: ["destination"]
118
+ },
119
+ readOnly: false,
120
+ // 'confirm' policy ⇒ the widget must show a confirm card every call; 'auto' ⇒ it
121
+ // may call without asking. See the confirm field's doc comment on HostTool.
122
+ confirm: policy === "auto" ? "auto" : "required"
123
+ };
124
+ }
125
+ function buildHoistContext(rt) {
126
+ const selected = rt.grids.reduce((n, g) => n + g.selected.length, 0);
127
+ const view = rt.route.name || rt.route.path || "this view";
128
+ const bits = [`Viewing ${view}`];
129
+ if (selected) bits.push(`${selected} selected`);
130
+ if (rt.grids.length) bits.push(`${rt.grids.length} grid(s)`);
131
+ if (rt.charts.length) bits.push(`${rt.charts.length} chart(s)`);
132
+ return {
133
+ route: rt.route.name ? redact(rt.route.name) : void 0,
134
+ description: redact(`${bits.join(", ")}.`)
135
+ };
136
+ }
137
+ function renderHoistSnapshot(rt, rowCap) {
138
+ const out = [];
139
+ renderNav(rt.nav, out, 0);
140
+ let truncated = false;
141
+ for (const g of rt.grids) {
142
+ if (g.rows.length > rowCap) truncated = true;
143
+ renderGrid(g, rowCap, out, 0);
144
+ }
145
+ for (const c of rt.charts) renderChart(c, out, 0);
146
+ return { yaml: out.join("\n"), truncated };
147
+ }
148
+
149
+ // src/adapters/hoist-runtime.ts
150
+ function readHoistConfig(win) {
151
+ const cfg = win?.matterfact?.hoist;
152
+ const ex = cfg?.excludeModels;
153
+ return {
154
+ rows: typeof cfg?.rows === "number" && cfg.rows >= 0 ? cfg.rows : 50,
155
+ excludeModels: Array.isArray(ex) ? ex : []
156
+ };
157
+ }
158
+ function getModelsByClass(XH, name) {
159
+ const models = typeof XH?.getModels === "function" ? XH.getModels(name) : [];
160
+ return Array.isArray(models) ? models : [];
161
+ }
162
+ function className(m) {
163
+ return m?.constructor?.name ?? "";
164
+ }
165
+ function modelId(m) {
166
+ return String(m?.xhId ?? m?.id ?? m?.modelId ?? "");
167
+ }
168
+ function headerOf(c) {
169
+ return String(c?.headerName ?? c?.displayName ?? c?.field ?? c?.colId ?? "");
170
+ }
171
+ function collectHeaders(cols, prefix, out) {
172
+ for (const c of Array.isArray(cols) ? cols : []) {
173
+ const children = c?.children;
174
+ if (Array.isArray(children) && children.length) {
175
+ collectHeaders(children, [...prefix, headerOf(c)], out);
176
+ } else {
177
+ const id = String(c?.colId ?? c?.field ?? "");
178
+ if (id) out.set(id, [...prefix, headerOf(c)].filter(Boolean).join(" "));
179
+ }
180
+ }
181
+ }
182
+ function readColumns(g) {
183
+ const leaves = typeof g?.getVisibleLeafColumns === "function" ? g.getVisibleLeafColumns() : Array.isArray(g?.columns) ? g.columns : [];
184
+ const headers = /* @__PURE__ */ new Map();
185
+ collectHeaders(Array.isArray(g?.columns) ? g.columns : [], [], headers);
186
+ return (Array.isArray(leaves) ? leaves : []).map((c) => {
187
+ const field = String(c?.colId ?? c?.field ?? "");
188
+ return { field, header: headers.get(field) || headerOf(c) };
189
+ }).filter((c) => c.field);
190
+ }
191
+ function readRow(rec, cols) {
192
+ const row = {};
193
+ for (const c of cols)
194
+ row[c.field] = typeof rec?.get === "function" ? rec.get(c.field) : rec?.data?.[c.field];
195
+ return row;
196
+ }
197
+ function readGrid(g) {
198
+ const columns = readColumns(g);
199
+ const records = Array.isArray(g?.store?.records) ? g.store.records : [];
200
+ return {
201
+ id: modelId(g),
202
+ title: String(g?.title ?? ""),
203
+ columns,
204
+ selected: (Array.isArray(g?.selectedRecords) ? g.selectedRecords : []).map(
205
+ (r) => readRow(r, columns)
206
+ ),
207
+ rows: records.map((r) => readRow(r, columns)),
208
+ totalCount: typeof g?.store?.allCount === "number" ? g.store.allCount : records.length
209
+ };
210
+ }
211
+ function hasContent(g) {
212
+ return g.totalCount > 0 || g.selected.length > 0;
213
+ }
214
+ function readChart(c) {
215
+ const series = c?.series ?? c?.highchartsConfig?.series ?? [];
216
+ return {
217
+ id: modelId(c),
218
+ title: String(c?.title ?? c?.highchartsConfig?.title?.text ?? ""),
219
+ series: (Array.isArray(series) ? series : []).map((s) => ({
220
+ name: String(s?.name ?? ""),
221
+ data: (Array.isArray(s?.data) ? s.data : []).map((d) => typeof d === "number" ? d : Number(d?.y ?? d?.[1])).filter((n) => Number.isFinite(n))
222
+ }))
223
+ };
224
+ }
225
+ function humanize(seg) {
226
+ return seg.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()).trim();
227
+ }
228
+ function childrenOf(node) {
229
+ const k = node?.children;
230
+ return k ? Array.isArray(k) ? k : Array.from(k) : [];
231
+ }
232
+ function routeIndex(XH, params) {
233
+ const router = XH?.router;
234
+ const root = router?.rootNode ?? router?.config?.rootNode;
235
+ const out = /* @__PURE__ */ new Map();
236
+ if (!router || typeof router.buildPath !== "function" || !root) return out;
237
+ const walk = (node, prefix) => {
238
+ for (const c of childrenOf(node)) {
239
+ const seg = String(c?.name ?? "");
240
+ if (!seg) continue;
241
+ const name = prefix ? `${prefix}.${seg}` : seg;
242
+ let path = "";
243
+ try {
244
+ path = router.buildPath(name, params);
245
+ } catch {
246
+ path = "";
247
+ }
248
+ if (path && !out.has(seg)) out.set(seg, { path, name });
249
+ walk(c, name);
250
+ }
251
+ };
252
+ walk(root, "");
253
+ return out;
254
+ }
255
+ function readSitemap(XH, excluded, origin) {
256
+ const params = XH?.routerState?.params ?? {};
257
+ const currentName = String(XH?.routerState?.name ?? "");
258
+ const routes = routeIndex(XH, params);
259
+ if (!routes.size) return [];
260
+ const groups = [];
261
+ for (const tc of getModelsByClass(XH, "TabContainerModel")) {
262
+ if (excluded(tc)) continue;
263
+ const tabs = Array.isArray(tc?.tabs) ? tc.tabs : [];
264
+ const activeTabId = String(tc?.activeTabId ?? "");
265
+ const items = [];
266
+ let companyScoped = false;
267
+ for (const t of tabs) {
268
+ const id = String(t?.id ?? "");
269
+ const r = routes.get(id);
270
+ if (!id || !r) continue;
271
+ if (r.name.includes(".company.ticker.")) companyScoped = true;
272
+ items.push({
273
+ // Redact HERE, at the source — not in a downstream renderer. This is the
274
+ // one place a Hoist tab TITLE (host-page text, so potentially PII-shaped)
275
+ // becomes a `HoistDestination.label`, and that label is what EVERY
276
+ // consumer uses verbatim: renderNav's YAML, buildNavigateTool's
277
+ // description AND its inputSchema.enum, and executeHostTool's
278
+ // `.find(d => d.label === destination)` lookup. Redacting once here keeps
279
+ // all of them consistent by construction — an enum built from an
280
+ // ALREADY-redacted label can never leak PII a sibling renderer scrubbed
281
+ // (see Important #1), and a destination arg matching the redacted string
282
+ // (the only thing the agent ever saw) still resolves.
283
+ label: redact(String(t?.title ?? "") || humanize(id)),
284
+ // ABSOLUTE, against the HOST page's origin — not the router's bare path.
285
+ // This path's only job is to be a link the agent can hand the user, and
286
+ // the agent's answer renders inside our widget iframe, which is on OUR
287
+ // origin: a bare "/research" there resolves to app.matterfact.com/research
288
+ // (a dead link to the wrong app), which is exactly what shipped before.
289
+ // The adapter runs in the host page, so its own origin is the right one.
290
+ path: origin && r.path.startsWith("/") ? origin + r.path : r.path,
291
+ // The fully-qualified route name — what hoist.navigate hands XH.navigate.
292
+ // Always present: `r` only exists here because routes.get(id) resolved.
293
+ name: r.name,
294
+ current: id === activeTabId || r.name === currentName
295
+ });
296
+ }
297
+ if (!items.length) continue;
298
+ const ticker = String(params.ticker ?? "");
299
+ const label = companyScoped ? ticker ? `Company \xB7 ${ticker}` : "Company" : "Site";
300
+ groups.push({ label, items });
301
+ }
302
+ return groups;
303
+ }
304
+ function readHoistRuntime(win = typeof window !== "undefined" ? window : void 0) {
305
+ const XH = win?.XH;
306
+ if (!XH) return null;
307
+ const { excludeModels } = readHoistConfig(win);
308
+ const excluded = (m) => excludeModels.includes(className(m)) || excludeModels.includes(modelId(m));
309
+ const rs = XH.routerState ?? {};
310
+ return {
311
+ route: {
312
+ name: String(rs.name ?? ""),
313
+ path: String(rs.path ?? ""),
314
+ params: rs.params ?? {}
315
+ },
316
+ grids: getModelsByClass(XH, "GridModel").filter((m) => !excluded(m)).map(readGrid).filter(hasContent),
317
+ charts: getModelsByClass(XH, "ChartModel").filter((m) => !excluded(m)).map(readChart).filter((c) => c.series.length > 0),
318
+ nav: readSitemap(
319
+ XH,
320
+ excluded,
321
+ String(
322
+ win?.location?.origin ?? ""
323
+ )
324
+ )
325
+ };
326
+ }
327
+ function readActionsConfig(win) {
328
+ const navigate = win?.matterfact?.hoist?.actions?.navigate;
329
+ return {
330
+ navigate: navigate === "confirm" || navigate === "auto" ? navigate : "off"
331
+ };
332
+ }
333
+ function advertiseTools(win = typeof window !== "undefined" ? window : void 0) {
334
+ try {
335
+ const XH = win?.XH;
336
+ if (!XH) return [];
337
+ const { navigate } = readActionsConfig(win);
338
+ if (navigate === "off") return [];
339
+ const rt = readHoistRuntime(win);
340
+ if (!rt) return [];
341
+ return [buildNavigateTool(rt, navigate)];
342
+ } catch {
343
+ return [];
344
+ }
345
+ }
346
+ function executeHostTool(call, win = typeof window !== "undefined" ? window : void 0) {
347
+ try {
348
+ if (call.name !== "hoist.navigate") {
349
+ return { ok: false, error: "unknown tool" };
350
+ }
351
+ if (readActionsConfig(win).navigate === "off") {
352
+ return { ok: false, error: "navigation is disabled" };
353
+ }
354
+ const destination = String(call.args?.destination ?? "");
355
+ const rt = readHoistRuntime(win);
356
+ const item = rt ? flattenDestinations(rt).find((d) => d.label === destination) : void 0;
357
+ if (!item) return { ok: false, error: "unknown destination" };
358
+ const XH = win?.XH;
359
+ const params = {
360
+ ...XH?.routerState?.params ?? {},
361
+ ...call.args?.params ?? {}
362
+ };
363
+ if (typeof XH?.navigate === "function") {
364
+ XH.navigate(item.name, params);
365
+ } else if (typeof XH?.router?.navigate === "function") {
366
+ XH.router.navigate(item.name, params);
367
+ } else {
368
+ return { ok: false, error: "navigation unavailable" };
369
+ }
370
+ return { ok: true, result: redact(`navigated to ${destination}`) };
371
+ } catch (e) {
372
+ return {
373
+ ok: false,
374
+ error: redact(e instanceof Error ? e.message : "navigate failed")
375
+ };
376
+ }
377
+ }
378
+
379
+ // src/context.ts
380
+ var widgetOrigin = "";
381
+ var MAX_ACTIVITY = 40;
382
+ var send = null;
383
+ var activitySeq = 0;
384
+ var activity = [];
385
+ var lastUrl = "";
386
+ var pageContextOn = true;
387
+ var contextProvider;
388
+ var navFire = null;
389
+ var origPushState = null;
390
+ var origReplaceState = null;
391
+ var snapshotModule = null;
392
+ function isPrivate(el) {
393
+ if (el.closest("[data-mf-private]")) return true;
394
+ const tag = el.tagName;
395
+ if (tag === "INPUT") {
396
+ const t = el.type;
397
+ if (t === "password" || t === "hidden") return true;
398
+ }
399
+ return false;
400
+ }
401
+ function artifactIdFromPath(pathname) {
402
+ const m = pathname.match(/^\/(?:embed\/)?artifacts\/(.+?)\/?$/);
403
+ return m ? decodeURIComponent(m[1]) : null;
404
+ }
405
+ function embeddedMatterfactEntities() {
406
+ if (!widgetOrigin) return [];
407
+ const out = [];
408
+ for (const frame of Array.from(document.querySelectorAll("iframe"))) {
409
+ let u;
410
+ try {
411
+ u = new URL(frame.getAttribute("src") || "", location.href);
412
+ } catch {
413
+ continue;
414
+ }
415
+ if (u.origin !== widgetOrigin) continue;
416
+ const id = artifactIdFromPath(u.pathname);
417
+ if (!id) continue;
418
+ if (out.some((e) => e.id === id)) continue;
419
+ out.push({
420
+ kind: "artifact",
421
+ id,
422
+ label: redact(frame.getAttribute("title") || "") || id
423
+ });
424
+ }
425
+ return out;
426
+ }
427
+ function opaqueFrameCount() {
428
+ return Array.from(document.querySelectorAll("iframe")).filter((f) => {
429
+ try {
430
+ const u = new URL(f.getAttribute("src") || "", location.href);
431
+ return u.origin !== location.origin && u.origin !== widgetOrigin;
432
+ } catch {
433
+ return false;
434
+ }
435
+ }).length;
436
+ }
437
+ function buildPageContext(declared) {
438
+ const found = embeddedMatterfactEntities();
439
+ const opaque = opaqueFrameCount();
440
+ return {
441
+ // Path only. A query string is where session ids, tokens and email addresses
442
+ // live; it is not ours to take.
443
+ url: location.origin + location.pathname,
444
+ path: location.pathname,
445
+ title: redact(document.title),
446
+ locale: document.documentElement.lang || void 0,
447
+ ...declared,
448
+ // After the spread: what we FOUND is additive to what the host DECLARED, never a
449
+ // replacement. A host that declares its own entities still gets the artifacts we
450
+ // spotted, and vice versa.
451
+ entities: [...declared.entities ?? [], ...found],
452
+ data: {
453
+ ...declared.data ?? {},
454
+ ...opaque ? { opaque_frames: opaque } : {}
455
+ }
456
+ };
457
+ }
458
+ function readPageContext() {
459
+ return buildPageContext(
460
+ window.matterfact?.context ?? {}
461
+ );
462
+ }
463
+ async function resolveDeclaredContext() {
464
+ const mf = window.matterfact;
465
+ if (contextProvider) {
466
+ const c = await contextProvider();
467
+ if (c) return c;
468
+ }
469
+ if (mf?.getPageContext) {
470
+ const c = await mf.getPageContext();
471
+ if (c) return c;
472
+ }
473
+ const hoist = detectHoist(() => readHoistRuntime());
474
+ if (hoist) return buildHoistContext(hoist);
475
+ return mf?.context ?? {};
476
+ }
477
+ async function publishContext() {
478
+ if (!pageContextOn) return;
479
+ const declared = await resolveDeclaredContext();
480
+ send?.({ type: "host.context", context: buildPageContext(declared) });
481
+ }
482
+ function provideContext() {
483
+ void publishContext();
484
+ }
485
+ function grantsFromIframeSrcs(srcs, origin) {
486
+ const out = [];
487
+ for (const raw of srcs) {
488
+ let u;
489
+ try {
490
+ u = new URL(raw || "", location.href);
491
+ } catch {
492
+ continue;
493
+ }
494
+ if (u.origin !== origin) continue;
495
+ const id = artifactIdFromPath(u.pathname);
496
+ if (!id) continue;
497
+ const owner = u.searchParams.get("owner") || "";
498
+ const token = u.searchParams.get("t") || "";
499
+ if (!owner || !token) continue;
500
+ if (out.some((g) => g.id === id)) continue;
501
+ out.push({ id, owner, token });
502
+ }
503
+ return out;
504
+ }
505
+ function readArtifactGrants() {
506
+ if (!widgetOrigin) return [];
507
+ const srcs = Array.from(document.querySelectorAll("iframe")).map(
508
+ (f) => f.getAttribute("src") || ""
509
+ );
510
+ return grantsFromIframeSrcs(srcs, widgetOrigin);
511
+ }
512
+ function publishArtifactGrants() {
513
+ send?.({ type: "host.artifactGrants", grants: readArtifactGrants() });
514
+ }
515
+ function pushActivity(e) {
516
+ activity.push({ ...e, seq: ++activitySeq, ts: Date.now() });
517
+ if (activity.length > MAX_ACTIVITY) activity.shift();
518
+ send?.({ type: "host.activity", events: [activity[activity.length - 1]] });
519
+ }
520
+ function controlLabel(el) {
521
+ const aria = el.getAttribute("aria-label");
522
+ if (aria) return aria;
523
+ const id = el.id;
524
+ if (id) {
525
+ const forLabel = document.querySelector(`label[for="${CSS.escape(id)}"]`);
526
+ const t = forLabel?.textContent?.trim();
527
+ if (t) return t;
528
+ }
529
+ const wrapping = el.closest("label")?.textContent?.trim();
530
+ if (wrapping) return wrapping;
531
+ const placeholder = el.getAttribute("placeholder");
532
+ if (placeholder) return placeholder;
533
+ return "";
534
+ }
535
+ var FORM_CONTROLS = /* @__PURE__ */ new Set(["INPUT", "SELECT", "TEXTAREA"]);
536
+ function describe(el) {
537
+ const role = el.getAttribute("role") || el.tagName.toLowerCase();
538
+ const label = el.getAttribute("aria-label") || (FORM_CONTROLS.has(el.tagName) ? controlLabel(el) : el.innerText?.trim().slice(0, 60)) || el.getAttribute("title") || "";
539
+ return label ? `${role} "${redact(label)}"` : role;
540
+ }
541
+ var ACTIONABLE = 'a[href],button,input,select,textarea,summary,[role="button"],[role="link"],[role="menuitem"],[role="tab"],[role="option"],[role="checkbox"],[role="switch"],[role="radio"],[onclick],[tabindex]';
542
+ function onClick(ev) {
543
+ const target = ev.target;
544
+ if (!target || !(target instanceof Element)) return;
545
+ const label = target.closest("label");
546
+ const labelled = label ? label.getAttribute("for") && document.getElementById(label.getAttribute("for")) || label.querySelector("input,select,textarea") : null;
547
+ const actionable = labelled ?? target.closest(ACTIONABLE);
548
+ if (!actionable || isPrivate(actionable)) return;
549
+ pushActivity({ type: "click", summary: `clicked ${describe(actionable)}` });
550
+ }
551
+ function onSubmit(ev) {
552
+ const el = ev.target;
553
+ if (!el || isPrivate(el)) return;
554
+ pushActivity({ type: "submit", summary: `submitted ${describe(el)}` });
555
+ }
556
+ function onChange(ev) {
557
+ const el = ev.target;
558
+ if (!el || !(el instanceof Element) || isPrivate(el)) return;
559
+ const tag = el.tagName;
560
+ const type = el.type;
561
+ if (tag === "INPUT" && (type === "password" || type === "hidden")) return;
562
+ let summary = `changed ${describe(el)}`;
563
+ if (tag === "SELECT") {
564
+ const opt = el.selectedOptions[0]?.text;
565
+ if (opt) summary = `${describe(el)} \u2192 "${redact(opt)}"`;
566
+ } else if (type === "checkbox" || type === "radio") {
567
+ summary = `${el.checked ? "checked" : "unchecked"} ${describe(el)}`;
568
+ }
569
+ pushActivity({ type: "input", summary });
570
+ }
571
+ function watchNavigation() {
572
+ const fire = () => {
573
+ const url = location.pathname;
574
+ if (url === lastUrl) return;
575
+ lastUrl = url;
576
+ pushActivity({ type: "nav", summary: `navigated to ${url}` });
577
+ void publishContext();
578
+ publishArtifactGrants();
579
+ send?.({
580
+ type: "host.tools",
581
+ tools: advertiseTools(typeof window !== "undefined" ? window : void 0)
582
+ });
583
+ };
584
+ navFire = fire;
585
+ for (const name of ["pushState", "replaceState"]) {
586
+ const orig = history[name];
587
+ if (name === "pushState") origPushState = orig;
588
+ else origReplaceState = orig;
589
+ history[name] = function(...args) {
590
+ const r = orig.apply(this, args);
591
+ fire();
592
+ return r;
593
+ };
594
+ }
595
+ window.addEventListener("popstate", fire);
596
+ }
597
+ var lastFocus = {};
598
+ async function readFocus() {
599
+ const focus = {};
600
+ const sel = window.getSelection?.();
601
+ if (sel && !sel.isCollapsed) {
602
+ const anchor = sel.anchorNode?.parentElement ?? null;
603
+ if (!anchor || !anchor.closest("[data-mf-private]")) {
604
+ const text = sel.toString().trim().slice(0, 500);
605
+ if (text) focus.selection = redact(text);
606
+ }
607
+ }
608
+ const active = document.activeElement;
609
+ if (active && active !== document.body && !isPrivate(active) && !active.closest("[data-mf-private]")) {
610
+ const { snapshot } = await loadSnapshotModule();
611
+ void snapshot;
612
+ focus.focused = {
613
+ label: redact(describeControl(active)),
614
+ role: active.getAttribute("role") || active.tagName.toLowerCase()
615
+ };
616
+ }
617
+ const doc = document.documentElement;
618
+ const scrollable = doc.scrollHeight - doc.clientHeight;
619
+ focus.scroll = scrollable > 0 ? Math.round(doc.scrollTop / scrollable * 100) / 100 : 0;
620
+ return focus;
621
+ }
622
+ function describeControl(el) {
623
+ return describe(el);
624
+ }
625
+ async function loadSnapshotModule() {
626
+ const m = await import("./snapshot-UGTXZVB6.js");
627
+ snapshotModule = m;
628
+ return m;
629
+ }
630
+ var focusTimer = null;
631
+ function scheduleFocus() {
632
+ if (focusTimer) return;
633
+ focusTimer = setTimeout(async () => {
634
+ focusTimer = null;
635
+ lastFocus = await readFocus();
636
+ send?.({ type: "host.focus", focus: lastFocus });
637
+ }, 250);
638
+ }
639
+ var snapshotSeq = 0;
640
+ async function sendSnapshot(emit) {
641
+ if (!pageContextOn) return;
642
+ const hoist = detectHoist(() => readHoistRuntime());
643
+ if (hoist) {
644
+ const { rows } = readHoistConfig(
645
+ typeof window !== "undefined" ? window : void 0
646
+ );
647
+ const { yaml: yaml2, truncated: truncated2 } = renderHoistSnapshot(hoist, rows);
648
+ emit({
649
+ type: "host.snapshot",
650
+ snapshot: { yaml: redact(yaml2), seq: ++snapshotSeq, truncated: truncated2 }
651
+ });
652
+ lastFocus = { ...lastFocus, visibleRefs: [] };
653
+ emit({ type: "host.focus", focus: lastFocus });
654
+ return;
655
+ }
656
+ const { snapshot } = await loadSnapshotModule();
657
+ const { yaml, truncated, visibleRefs } = snapshot();
658
+ emit({
659
+ type: "host.snapshot",
660
+ snapshot: { yaml: redact(yaml), seq: ++snapshotSeq, truncated }
661
+ });
662
+ lastFocus = { ...lastFocus, visibleRefs };
663
+ emit({ type: "host.focus", focus: lastFocus });
664
+ }
665
+ async function sendRegion(ref, emit) {
666
+ if (!pageContextOn) return;
667
+ const { snapshotRegion, resolveRef } = await loadSnapshotModule();
668
+ const el = resolveRef(ref);
669
+ if (!el) {
670
+ emit({
671
+ type: "host.region",
672
+ ref,
673
+ yaml: "(this element is no longer on the page)"
674
+ });
675
+ return;
676
+ }
677
+ const { yaml } = snapshotRegion(el);
678
+ emit({ type: "host.region", ref, yaml: redact(yaml) });
679
+ }
680
+ async function callTool(call, emit) {
681
+ if (!pageContextOn) {
682
+ emit({
683
+ type: "host.toolResult",
684
+ callId: call.callId,
685
+ ok: false,
686
+ error: "host tools are disabled"
687
+ });
688
+ return;
689
+ }
690
+ const r = executeHostTool(
691
+ call,
692
+ typeof window !== "undefined" ? window : void 0
693
+ );
694
+ emit({
695
+ type: "host.toolResult",
696
+ callId: call.callId,
697
+ ok: r.ok,
698
+ result: r.result,
699
+ error: r.error
700
+ });
701
+ }
702
+ function stop() {
703
+ document.removeEventListener("click", onClick, { capture: true });
704
+ document.removeEventListener("submit", onSubmit, { capture: true });
705
+ document.removeEventListener("change", onChange, { capture: true });
706
+ document.removeEventListener("selectionchange", scheduleFocus);
707
+ document.removeEventListener("focusin", scheduleFocus, { capture: true });
708
+ window.removeEventListener("scroll", scheduleFocus, { capture: true });
709
+ if (navFire) {
710
+ window.removeEventListener("popstate", navFire);
711
+ navFire = null;
712
+ }
713
+ if (origPushState) {
714
+ history.pushState = origPushState;
715
+ origPushState = null;
716
+ }
717
+ if (origReplaceState) {
718
+ history.replaceState = origReplaceState;
719
+ origReplaceState = null;
720
+ }
721
+ if (focusTimer) {
722
+ clearTimeout(focusTimer);
723
+ focusTimer = null;
724
+ }
725
+ send = null;
726
+ contextProvider = void 0;
727
+ activity.length = 0;
728
+ lastFocus = {};
729
+ widgetOrigin = "";
730
+ snapshotModule?.clearRefs();
731
+ }
732
+ function start(emit, origin, pageContext = true, provider) {
733
+ stop();
734
+ send = emit;
735
+ widgetOrigin = origin || "";
736
+ lastUrl = location.pathname;
737
+ pageContextOn = pageContext;
738
+ contextProvider = provider;
739
+ if (pageContextOn) {
740
+ void publishContext();
741
+ publishArtifactGrants();
742
+ watchNavigation();
743
+ document.addEventListener("click", onClick, {
744
+ capture: true,
745
+ passive: true
746
+ });
747
+ document.addEventListener("submit", onSubmit, {
748
+ capture: true,
749
+ passive: true
750
+ });
751
+ document.addEventListener("change", onChange, {
752
+ capture: true,
753
+ passive: true
754
+ });
755
+ document.addEventListener("selectionchange", scheduleFocus, {
756
+ passive: true
757
+ });
758
+ document.addEventListener("focusin", scheduleFocus, {
759
+ capture: true,
760
+ passive: true
761
+ });
762
+ window.addEventListener("scroll", scheduleFocus, {
763
+ capture: true,
764
+ passive: true
765
+ });
766
+ }
767
+ const theme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
768
+ emit({ type: "host.theme", mode: theme });
769
+ if (pageContextOn) {
770
+ const tools = advertiseTools(
771
+ typeof window !== "undefined" ? window : void 0
772
+ );
773
+ if (tools.length) emit({ type: "host.tools", tools });
774
+ }
775
+ }
776
+
777
+ export {
778
+ redact,
779
+ isPrivate,
780
+ artifactIdFromPath,
781
+ buildPageContext,
782
+ readPageContext,
783
+ provideContext,
784
+ grantsFromIframeSrcs,
785
+ sendSnapshot,
786
+ sendRegion,
787
+ callTool,
788
+ stop,
789
+ start
790
+ };
791
+ //# sourceMappingURL=chunk-PNSYFXXU.js.map