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