@batalabs/virlow-mcp-core 3.84.2 → 3.85.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.
@@ -0,0 +1,463 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WIDGET_PARTS = exports.WIDGET_RESOURCES = exports.TOOL_WIDGETS = exports.CONTEXT_CARD_FACTS = exports.KIND_COLORS = exports.VIRLOW_APP_URL = exports.MEMORY_CONTEXT_URI = exports.SAVED_MEMORY_URI = exports.WIDGET_MIME_TYPE = void 0;
4
+ exports.widgetToolMeta = widgetToolMeta;
5
+ exports.resourceMeta = resourceMeta;
6
+ exports.readWidget = readWidget;
7
+ exports.widgetHtml = widgetHtml;
8
+ /** The MCP Apps resource type. ChatGPT reads it too. */
9
+ exports.WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';
10
+ /**
11
+ * Versioned, because a host treats the URI as the cache key for the page:
12
+ * ChatGPT keeps what it fetched for a URI, so changed HTML under the same URI
13
+ * may never be seen. Bump the version whenever the page changes.
14
+ * v2: the card performs the MCP Apps handshake even when window.openai exists.
15
+ */
16
+ exports.SAVED_MEMORY_URI = 'ui://widget/saved-memory-v2.html';
17
+ exports.MEMORY_CONTEXT_URI = 'ui://widget/memory-context-v2.html';
18
+ /** Where "Open in Virlow" goes. The web app has no per-memory route, so it
19
+ * opens the app itself. */
20
+ exports.VIRLOW_APP_URL = 'https://app.virlow.com';
21
+ /**
22
+ * The thirteen kind colours, light and dark. A copy of KIND_COLORS in the
23
+ * web app (src/lib/entityKinds.ts), which cannot be imported here: that
24
+ * module pulls in lucide-react. A test holds the two to the same values.
25
+ */
26
+ exports.KIND_COLORS = {
27
+ person: { light: '#4F6D8F', dark: '#8FB0D0' },
28
+ organization: { light: '#C2603E', dark: '#E08A67' },
29
+ project: { light: '#A27B17', dark: '#F2C66D' },
30
+ tool: { light: '#5E7B4A', dark: '#9DBB85' },
31
+ place: { light: '#7A5A8C', dark: '#B79BC8' },
32
+ event: { light: '#CB2A61', dark: '#F6638A' },
33
+ topic: { light: '#0C9184', dark: '#82DCCF' },
34
+ product: { light: '#9E2185', dark: '#EA78CD' },
35
+ media: { light: '#6E4A2E', dark: '#C8A07E' },
36
+ pet: { light: '#C16B10', dark: '#F59C4D' },
37
+ group: { light: '#10953A', dark: '#6AD177' },
38
+ goal: { light: '#5D47B2', dark: '#AC8FFA' },
39
+ other: { light: '#6B665C', dark: '#9A9488' },
40
+ };
41
+ /** How many facts the context card shows; the text result keeps them all. */
42
+ exports.CONTEXT_CARD_FACTS = 5;
43
+ const VIEW_URI = {
44
+ saved: exports.SAVED_MEMORY_URI,
45
+ context: exports.MEMORY_CONTEXT_URI,
46
+ };
47
+ /** Which tools show which card. Tools not named here have no UI. */
48
+ exports.TOOL_WIDGETS = {
49
+ add_memory: 'saved',
50
+ update_memory: 'saved',
51
+ memory_context: 'context',
52
+ };
53
+ /** The tool-side `_meta` linking a tool to its card: the MCP Apps key, the
54
+ * flat `ui/resourceUri` spelling older MCP Apps hosts read (the ext-apps
55
+ * SDK's registerAppTool writes both), and ChatGPT's alias. Visibility is
56
+ * stated rather than left to the default: the model calls these tools and
57
+ * the card may call tools too (add_memory's Undo calls delete_memory, and
58
+ * `openai/widgetAccessible` is ChatGPT's older spelling of that). */
59
+ function widgetToolMeta(tool) {
60
+ const view = exports.TOOL_WIDGETS[tool];
61
+ if (!view)
62
+ return undefined;
63
+ const uri = VIEW_URI[view];
64
+ return {
65
+ ui: { resourceUri: uri, visibility: ['model', 'app'] },
66
+ 'ui/resourceUri': uri,
67
+ 'openai/outputTemplate': uri,
68
+ ...(tool === 'add_memory' && { 'openai/widgetAccessible': true }),
69
+ };
70
+ }
71
+ /**
72
+ * The resource-side `_meta`. Empty CSP lists are the secure default (no
73
+ * network); they are stated rather than omitted so a reviewer reads the
74
+ * decision. `openai/widgetCSP` is ChatGPT's older spelling of the same
75
+ * thing; redirect_domains lets "Open in Virlow" go straight to the app.
76
+ */
77
+ function resourceMeta(view) {
78
+ return {
79
+ ui: {
80
+ prefersBorder: true,
81
+ csp: { connectDomains: [], resourceDomains: [], frameDomains: [] },
82
+ },
83
+ 'openai/widgetDescription': view === 'saved'
84
+ ? 'Shows the memory just saved to Virlow, with Undo and a link to the app. The card already shows the fact, so a one-line confirmation is enough.'
85
+ : 'Shows a short summary of what Virlow has saved about this user or project.',
86
+ 'openai/widgetCSP': {
87
+ connect_domains: [],
88
+ resource_domains: [],
89
+ redirect_domains: [exports.VIRLOW_APP_URL],
90
+ },
91
+ 'openai/widgetPrefersBorder': true,
92
+ };
93
+ }
94
+ /** Everything the server needs to list and serve the two resources. */
95
+ exports.WIDGET_RESOURCES = [
96
+ {
97
+ name: 'saved-memory-card',
98
+ uri: exports.SAVED_MEMORY_URI,
99
+ title: 'Saved to Virlow',
100
+ description: 'Inline card shown after a memory is saved or updated.',
101
+ view: 'saved',
102
+ },
103
+ {
104
+ name: 'memory-context-card',
105
+ uri: exports.MEMORY_CONTEXT_URI,
106
+ title: 'What Virlow knows',
107
+ description: 'Inline card summarising the memories returned by memory_context.',
108
+ view: 'context',
109
+ },
110
+ ];
111
+ /** A `resources/read` result for one card. */
112
+ function readWidget(view) {
113
+ const uri = VIEW_URI[view];
114
+ return {
115
+ contents: [{ uri, mimeType: exports.WIDGET_MIME_TYPE, text: widgetHtml(view), _meta: resourceMeta(view) }],
116
+ };
117
+ }
118
+ // ---------- the page ----------
119
+ /** The Virlow mark from public/favicon.svg: the italic v and its dot. */
120
+ const MARK_SVG = '<svg class="mark" viewBox="0 0 64 64" width="20" height="20" aria-hidden="true" focusable="false">' +
121
+ '<rect width="64" height="64" rx="12" fill="#fbfaf7"/>' +
122
+ '<g transform="translate(6.172854 51.207268) scale(2.201335896 -2.201335896)">' +
123
+ '<path d="M7.8 -0.4Q6.8 -0.4 6.4 -0.2Q6.1 0.1 6.1 0.4Q6.1 0.6 6.1 0.8Q6.1 0.9 6.1 1.1Q6.1 1.3 6.1 1.7Q6 3.9 5.7 6.3Q5.3 8.7 4.6 10.8Q3.9 12.9 3.1 14.3Q2.9 14.5 2.7 14.7Q2.5 14.8 2.3 14.8Q2 14.8 1.7 14.7Q1.4 14.5 1 14.2Q0.8 13.9 0.5 13.9Q0.4 13.9 0.1 14.1Q-0.1 14.3 -0.1 14.7Q-0.1 14.9 0 15.1Q1 16.5 2.3 17.2Q3.6 17.8 5.1 17.8Q7.1 17.8 8.2 15.9Q9 14.5 9.5 12.6Q9.9 10.8 10.1 8.7Q10.3 6.6 10.4 4.5Q10.4 4.1 10 3.1Q9.7 2.2 9.3 1.7Q10.3 2.3 11.4 3.3Q12.5 4.3 13.4 5.6Q14.4 6.9 15 8.3Q15.6 9.7 15.6 11.1Q15.6 12 15.4 12.6Q15.2 13.3 14.9 14Q14.7 14.7 14.7 15.5Q14.7 16.6 15.4 17.2Q16.1 17.8 17.2 17.8Q18.2 17.8 18.9 17.2Q19.5 16.6 19.8 15.8Q20.1 14.9 20.1 13.9Q20.1 12.4 19.4 10.7Q18.7 9 17.5 7.3Q16.2 5.7 14.8 4.2Q13.3 2.7 11.8 1.5Q10.3 0.4 9 -0.3Q9 -0.4 8.8 -0.4Q8.6 -0.4 7.8 -0.4Z" fill="#1f1d1a"/>' +
124
+ '<path d="M17.7 2.6Q17.7 3.9 18.5 4.9Q19.4 5.8 20.8 5.8Q22.1 5.8 22.8 5Q23.5 4.2 23.5 3Q23.5 1.6 22.6 0.6Q21.7 -0.4 20.4 -0.4Q19 -0.4 18.3 0.5Q17.7 1.4 17.7 2.6Z" fill="#775825"/>' +
125
+ '</g></svg>';
126
+ function kindVars(mode) {
127
+ return Object.entries(exports.KIND_COLORS)
128
+ .map(([kind, c]) => `--k-${kind}:${c[mode]};`)
129
+ .join('');
130
+ }
131
+ const LIGHT = 'color-scheme:light;--ink:#1F1D1A;--ink-2:#5E5A52;--ink-3:#6B665C;--rule:rgba(0,0,0,0.12);' +
132
+ '--chip:rgba(0,0,0,0.05);--btn:#4F6D8F;--btn-ink:#FFFFFF;--btn-hover:#33506E;--ghost-hover:rgba(0,0,0,0.05);' +
133
+ '--ring:#33506E;--danger:#B3402E;--ok:#4C783C;' +
134
+ kindVars('light');
135
+ const DARK = 'color-scheme:dark;--ink:#EDE7DD;--ink-2:#B8B0A4;--ink-3:#9A9488;--rule:rgba(255,255,255,0.16);' +
136
+ '--chip:rgba(255,255,255,0.07);--btn:#8FB0D0;--btn-ink:#14181D;--btn-hover:#B8D1E8;--ghost-hover:rgba(255,255,255,0.08);' +
137
+ '--ring:#B8D1E8;--danger:#E07A68;--ok:#9DBB85;' +
138
+ kindVars('dark');
139
+ /** Kept to body and body-small sizes, system fonts, no inner scrolling: the
140
+ * Apps SDK rules for an inline card. Long text is clamped, not scrolled. */
141
+ const CSS = `
142
+ :root{${LIGHT}}
143
+ :root[data-theme="dark"]{${DARK}}
144
+ @media (prefers-color-scheme: dark){:root:not([data-theme="light"]){${DARK}}}
145
+ *{box-sizing:border-box}
146
+ html,body{margin:0;padding:0;background:transparent;overflow:hidden}
147
+ body{font:14px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:var(--ink);-webkit-font-smoothing:antialiased}
148
+ .card{padding:14px 16px;display:flex;flex-direction:column;gap:8px}
149
+ .head{display:flex;align-items:center;gap:8px;min-width:0}
150
+ .mark{flex:none;border-radius:5px;box-shadow:0 0 0 1px var(--rule)}
151
+ .status{font-size:13px;font-weight:600;color:var(--ink-2);outline:none}
152
+ .status.removed{color:var(--ink-3)}
153
+ .ns{margin-left:auto;font-size:12px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:45%}
154
+ .label{font-weight:600;margin:0;overflow-wrap:anywhere}
155
+ .fact{margin:0;color:var(--ink-2);overflow-wrap:anywhere;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;overflow:hidden}
156
+ .removed-body .label,.removed-body .fact{text-decoration:line-through;color:var(--ink-3)}
157
+ .chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}
158
+ .chip{display:inline-flex;align-items:center;gap:6px;max-width:100%;padding:2px 9px 2px 7px;border-radius:999px;background:var(--chip);font-size:12px;color:var(--ink)}
159
+ .chip span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
160
+ .dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--k-other)}
161
+ ${Object.keys(exports.KIND_COLORS)
162
+ .map((k) => `.chip[data-kind="${k}"] .dot{background:var(--k-${k})}`)
163
+ .join('\n')}
164
+ .facts{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:6px}
165
+ .facts li{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;overflow-wrap:anywhere;color:var(--ink-2)}
166
+ .facts b{color:var(--ink);font-weight:600}
167
+ .note{margin:0;font-size:12px;color:var(--ink-3)}
168
+ .error{margin:0;font-size:12px;color:var(--danger)}
169
+ .actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:2px}
170
+ button{font:inherit;font-size:13px;font-weight:600;border-radius:8px;padding:6px 12px;cursor:pointer;border:1px solid transparent}
171
+ button:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
172
+ button[disabled]{opacity:.6;cursor:default}
173
+ .primary{background:var(--btn);color:var(--btn-ink)}
174
+ .primary:hover:not([disabled]){background:var(--btn-hover)}
175
+ .secondary{background:transparent;color:var(--ink);border-color:var(--rule)}
176
+ .secondary:hover:not([disabled]){background:var(--ghost-hover)}
177
+ .empty{margin:0;color:var(--ink-3)}
178
+ `;
179
+ /**
180
+ * The card's script. Plain ES2017 in a string, because it ships inside the
181
+ * HTML rather than through the package build. It speaks to both bridges at
182
+ * once, never one instead of the other:
183
+ *
184
+ * - The MCP Apps bridge, JSON-RPC over postMessage, whenever the page is in a
185
+ * frame: the card sends ui/initialize, then ui/notifications/initialized,
186
+ * receives ui/notifications/tool-result, reports its height with
187
+ * ui/notifications/size-changed, and calls tools/call and ui/open-link.
188
+ * Every MCP Apps host needs this, ChatGPT included: it delivers the tool
189
+ * result to an mcp-app frame only after the handshake.
190
+ * - ChatGPT's `window.openai`, when present, as an extension: toolOutput
191
+ * (the structuredContent, possibly before the handshake completes),
192
+ * widgetState / setWidgetState for the Undo state, theme, the
193
+ * `openai:set_globals` event, and callTool / openExternal as the fallback
194
+ * while the standard bridge is not up.
195
+ */
196
+ const SCRIPT = `
197
+ (function () {
198
+ 'use strict';
199
+ var APP_URL = ${JSON.stringify(exports.VIRLOW_APP_URL)};
200
+ var KIND_LABEL = {person:'person',organization:'organization',project:'project',tool:'tool',place:'place',event:'event',topic:'topic',product:'product',media:'media',pet:'pet',group:'group',goal:'goal',other:'other'};
201
+ var root = document.getElementById('root');
202
+ var view = document.body.getAttribute('data-view') === 'context' ? 'context' : 'saved';
203
+ var state = { data: null, removed: false, busy: false, error: '' };
204
+ var bridge = null;
205
+
206
+ function openai() { return window.openai || null; }
207
+
208
+ function el(tag, cls, text) {
209
+ var node = document.createElement(tag);
210
+ if (cls) node.className = cls;
211
+ if (text != null) node.textContent = String(text);
212
+ return node;
213
+ }
214
+
215
+ function applyTheme(theme) {
216
+ if (theme === 'light' || theme === 'dark') document.documentElement.setAttribute('data-theme', theme);
217
+ }
218
+
219
+ function header(status, ns, removed) {
220
+ var head = el('div', 'head');
221
+ var mark = document.createElement('span');
222
+ mark.innerHTML = ${JSON.stringify(MARK_SVG)};
223
+ head.appendChild(mark.firstChild);
224
+ var s = el('span', 'status' + (removed ? ' removed' : ''), status);
225
+ s.id = 'status';
226
+ s.setAttribute('tabindex', '-1');
227
+ s.setAttribute('role', 'status');
228
+ s.setAttribute('aria-live', 'polite');
229
+ head.appendChild(s);
230
+ if (ns) {
231
+ var n = el('span', 'ns', ns);
232
+ n.title = 'Project: ' + ns;
233
+ head.appendChild(n);
234
+ }
235
+ return head;
236
+ }
237
+
238
+ function chips(entities, max) {
239
+ var list = el('ul', 'chips');
240
+ list.setAttribute('aria-label', 'About');
241
+ var shown = entities.slice(0, max);
242
+ for (var i = 0; i < shown.length; i++) {
243
+ var e = shown[i];
244
+ var kind = KIND_LABEL[e.kind] ? e.kind : 'other';
245
+ var li = el('li', 'chip');
246
+ li.setAttribute('data-kind', kind);
247
+ li.title = e.name + ' (' + kind + ')';
248
+ li.appendChild(el('span', 'dot')).setAttribute('aria-hidden', 'true');
249
+ var name = el('span', null, e.name);
250
+ li.appendChild(name);
251
+ var sr = el('span', 'sr', ', ' + kind);
252
+ sr.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)';
253
+ li.appendChild(sr);
254
+ list.appendChild(li);
255
+ }
256
+ if (entities.length > max) list.appendChild(el('li', 'chip', '+' + (entities.length - max)));
257
+ return list;
258
+ }
259
+
260
+ function button(label, cls, onClick, aria) {
261
+ var b = el('button', cls, label);
262
+ b.type = 'button';
263
+ if (aria) b.setAttribute('aria-label', aria);
264
+ b.addEventListener('click', onClick);
265
+ return b;
266
+ }
267
+
268
+ // The standard bridge first once it is up, window.openai while it is not
269
+ // (or in a ChatGPT that never answers ui/initialize).
270
+ function openApp() {
271
+ if (bridge && bridge.ready) { bridge.request('ui/open-link', { url: APP_URL }).catch(function () {}); return; }
272
+ var host = openai();
273
+ if (host && typeof host.openExternal === 'function') { host.openExternal({ href: APP_URL }); return; }
274
+ window.open(APP_URL, '_blank', 'noopener');
275
+ }
276
+
277
+ function callTool(name, args) {
278
+ if (bridge && bridge.ready) return bridge.request('tools/call', { name: name, arguments: args });
279
+ var host = openai();
280
+ if (host && typeof host.callTool === 'function') return Promise.resolve(host.callTool(name, args));
281
+ return Promise.reject(new Error('This host cannot run tools from a card.'));
282
+ }
283
+
284
+ function persist() {
285
+ var host = openai();
286
+ if (host && typeof host.setWidgetState === 'function') {
287
+ try { host.setWidgetState({ removed: state.removed }); } catch (e) { /* the card still shows it */ }
288
+ }
289
+ }
290
+
291
+ function undo() {
292
+ var d = state.data;
293
+ if (!d || !d.id || state.busy || state.removed) return;
294
+ state.busy = true;
295
+ state.error = '';
296
+ render();
297
+ callTool('delete_memory', { id: d.id })
298
+ .then(function (result) {
299
+ if (result && result.isError) throw new Error('refused');
300
+ state.removed = true;
301
+ persist();
302
+ })
303
+ .catch(function () { state.error = 'Could not remove it. Try again.'; })
304
+ .then(function () {
305
+ state.busy = false;
306
+ render();
307
+ var s = document.getElementById('status');
308
+ if (state.removed && s) s.focus();
309
+ });
310
+ }
311
+
312
+ function renderSaved(d) {
313
+ var card = el('div', 'card' + (state.removed ? ' removed-body' : ''));
314
+ var status = state.removed ? 'Removed from Virlow' : d.updated ? 'Updated in Virlow' : 'Saved to Virlow';
315
+ card.appendChild(header(status, d.namespace, state.removed));
316
+ card.appendChild(el('p', 'label', d.label));
317
+ card.appendChild(el('p', 'fact', d.text));
318
+ if (d.entities && d.entities.length) card.appendChild(chips(d.entities, 8));
319
+ if (state.removed) card.appendChild(el('p', 'note', 'Moved to the Trash. You can restore it in the Virlow app.'));
320
+ if (state.error) card.appendChild(el('p', 'error', state.error));
321
+ var actions = el('div', 'actions');
322
+ // Undo deletes the memory, so it is offered only for a new one: on an
323
+ // update it would delete what was there before, not reverse the change.
324
+ if (!d.updated && !state.removed) {
325
+ var u = button(state.busy ? 'Removing...' : 'Undo', 'primary', undo, 'Undo: remove this memory from Virlow');
326
+ if (state.busy) u.disabled = true;
327
+ actions.appendChild(u);
328
+ }
329
+ actions.appendChild(button('Open in Virlow', 'secondary', openApp, 'Open Virlow in a new tab'));
330
+ card.appendChild(actions);
331
+ return card;
332
+ }
333
+
334
+ function renderContext(d) {
335
+ var card = el('div', 'card');
336
+ card.appendChild(header('What Virlow knows', d.namespace, false));
337
+ var facts = d.facts || [];
338
+ if (facts.length) {
339
+ var list = el('ul', 'facts');
340
+ for (var i = 0; i < facts.length; i++) {
341
+ var li = el('li');
342
+ li.appendChild(el('b', null, facts[i].label));
343
+ li.appendChild(document.createTextNode(': ' + facts[i].text));
344
+ list.appendChild(li);
345
+ }
346
+ card.appendChild(list);
347
+ } else {
348
+ card.appendChild(el('p', 'empty', 'Nothing saved yet.'));
349
+ }
350
+ var people = (d.entities || []).filter(function (e) { return e.kind === 'person' || e.kind === 'project'; });
351
+ if (people.length) card.appendChild(chips(people, 6));
352
+ if (d.paused) card.appendChild(el('p', 'note', 'Saving new memories is paused.'));
353
+ var actions = el('div', 'actions');
354
+ actions.appendChild(button('Open in Virlow', 'secondary', openApp, 'Open Virlow in a new tab'));
355
+ card.appendChild(actions);
356
+ return card;
357
+ }
358
+
359
+ function render() {
360
+ root.textContent = '';
361
+ var d = state.data;
362
+ if (!d || typeof d !== 'object') return;
363
+ root.appendChild(view === 'context' ? renderContext(d) : renderSaved(d));
364
+ if (bridge) bridge.resized();
365
+ }
366
+
367
+ function fromOpenai() {
368
+ var host = openai();
369
+ if (!host) return;
370
+ applyTheme(host.theme);
371
+ if (host.toolOutput && typeof host.toolOutput === 'object') state.data = host.toolOutput;
372
+ var ws = host.widgetState;
373
+ if (ws && ws.removed === true) state.removed = true;
374
+ render();
375
+ }
376
+
377
+ function startBridge() {
378
+ var nextId = 1;
379
+ var pending = {};
380
+ var lastHeight = 0;
381
+ function post(msg) { window.parent.postMessage(msg, '*'); }
382
+ bridge = {
383
+ request: function (method, params) {
384
+ return new Promise(function (resolve, reject) {
385
+ var id = nextId++;
386
+ pending[id] = { resolve: resolve, reject: reject };
387
+ post({ jsonrpc: '2.0', id: id, method: method, params: params });
388
+ });
389
+ },
390
+ notify: function (method, params) { post({ jsonrpc: '2.0', method: method, params: params || {} }); },
391
+ // Set once the host answers ui/initialize. Nothing but that request
392
+ // goes to the host before then, as the MCP Apps lifecycle requires.
393
+ ready: false,
394
+ resized: function () {
395
+ if (!bridge.ready) return;
396
+ var h = Math.ceil(document.documentElement.getBoundingClientRect().height);
397
+ if (h && h !== lastHeight) { lastHeight = h; bridge.notify('ui/notifications/size-changed', { height: h }); }
398
+ }
399
+ };
400
+ window.addEventListener('message', function (event) {
401
+ if (event.source !== window.parent) return;
402
+ var m = event.data;
403
+ if (!m || m.jsonrpc !== '2.0') return;
404
+ if (m.method === undefined && m.id != null && pending[m.id]) {
405
+ var p = pending[m.id];
406
+ delete pending[m.id];
407
+ if (m.error) p.reject(new Error(m.error.message || 'error')); else p.resolve(m.result);
408
+ return;
409
+ }
410
+ var params = m.params || {};
411
+ if (m.method === 'ui/notifications/tool-result') {
412
+ if (params.structuredContent) { state.data = params.structuredContent; render(); }
413
+ } else if (m.method === 'ui/notifications/host-context-changed') {
414
+ applyTheme(params.theme);
415
+ } else if ((m.method === 'ui/resource-teardown' || m.method === 'ping') && m.id != null) {
416
+ post({ jsonrpc: '2.0', id: m.id, result: {} });
417
+ } else if (m.id != null) {
418
+ post({ jsonrpc: '2.0', id: m.id, error: { code: -32601, message: 'Method not found' } });
419
+ }
420
+ });
421
+ bridge.request('ui/initialize', {
422
+ appInfo: { name: 'virlow-card', version: '1' },
423
+ appCapabilities: {},
424
+ protocolVersion: '2026-01-26'
425
+ }).then(function (result) {
426
+ if (result && result.hostContext) applyTheme(result.hostContext.theme);
427
+ bridge.ready = true;
428
+ bridge.notify('ui/notifications/initialized');
429
+ // The host sizes the frame from these reports alone: without one an
430
+ // inline card can stay at zero height.
431
+ bridge.resized();
432
+ if (typeof ResizeObserver === 'function') {
433
+ new ResizeObserver(function () { bridge.resized(); }).observe(document.documentElement);
434
+ }
435
+ }).catch(function () {});
436
+ }
437
+
438
+ // Both, not either. ChatGPT injects window.openai into an MCP Apps
439
+ // (text/html;profile=mcp-app) frame too, but as an extension: the tool
440
+ // result reaches the card over the bridge, and only after the card has
441
+ // sent ui/initialize. A card that saw window.openai and skipped the
442
+ // bridge never initialized, never received its data and never reported a
443
+ // height, so ChatGPT showed nothing at all.
444
+ if (openai()) {
445
+ window.addEventListener('openai:set_globals', fromOpenai);
446
+ fromOpenai();
447
+ }
448
+ if (window.parent && window.parent !== window) {
449
+ startBridge();
450
+ }
451
+ })();
452
+ `;
453
+ /** The whole page for one card. */
454
+ function widgetHtml(view) {
455
+ return ('<!doctype html><html lang="en"><head><meta charset="utf-8">' +
456
+ '<meta name="viewport" content="width=device-width,initial-scale=1">' +
457
+ `<title>${view === 'saved' ? 'Saved to Virlow' : 'What Virlow knows'}</title>` +
458
+ `<style>${CSS}</style></head>` +
459
+ `<body data-view="${view}"><div id="root"></div><script>${SCRIPT}</script></body></html>`);
460
+ }
461
+ /** The pieces, for the widget tests: they run the script against the page
462
+ * body in jsdom without a host. */
463
+ exports.WIDGET_PARTS = { css: CSS, script: SCRIPT };
@@ -212,6 +212,9 @@ export declare class MemoryStore {
212
212
  /** The namespace of a memory this session has seen, without a sync;
213
213
  * undefined for a global memory or one not cached yet. */
214
214
  namespaceOfCached(id: string): string | undefined;
215
+ /** One memory as this session has it, without a sync; undefined when it
216
+ * is not cached. */
217
+ cached(id: string): CachedMemory | undefined;
215
218
  /** Requires a prior sync() in the same unlock to have populated the cache. */
216
219
  list(namespace?: string): CachedMemory[];
217
220
  update(id: string, changes: {
@@ -1 +1 @@
1
- {"version":3,"file":"memories.d.ts","sourceRoot":"","sources":["../src/memories.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memories.d.ts","sourceRoot":"","sources":["../src/memories.ts"],"names":[],"mappings":"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"}
package/dist/memories.js CHANGED
@@ -651,6 +651,11 @@ export class MemoryStore {
651
651
  namespaceOfCached(id) {
652
652
  return this.cache.get(id)?.namespace;
653
653
  }
654
+ /** One memory as this session has it, without a sync; undefined when it
655
+ * is not cached. */
656
+ cached(id) {
657
+ return this.cache.get(id);
658
+ }
654
659
  /** Requires a prior sync() in the same unlock to have populated the cache. */
655
660
  list(namespace) {
656
661
  this.requireSession();