@pixerate/editor 0.53.0 → 0.55.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.
- package/dist/agent/index.cjs +20 -12
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -3
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.js +18 -10
- package/dist/agent/index.js.map +1 -1
- package/dist/animation/index.cjs +16 -2
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.d.cts +4 -226
- package/dist/animation/index.d.ts +4 -226
- package/dist/animation/index.js +15 -1
- package/dist/animation-editor/index.cjs +2221 -0
- package/dist/animation-editor/index.cjs.map +1 -0
- package/dist/animation-editor/index.d.cts +1117 -0
- package/dist/animation-editor/index.d.ts +1117 -0
- package/dist/animation-editor/index.js +2221 -0
- package/dist/animation-editor/index.js.map +1 -0
- package/dist/bezier-BfwIbVTl.d.cts +548 -0
- package/dist/bezier-DYBvLgUH.d.ts +548 -0
- package/dist/bridge/cli.cjs +36 -0
- package/dist/bridge/cli.cjs.map +1 -0
- package/dist/bridge/cli.d.cts +1 -0
- package/dist/bridge/cli.d.ts +1 -0
- package/dist/bridge/cli.js +36 -0
- package/dist/bridge/cli.js.map +1 -0
- package/dist/bridge/index.cjs +16 -0
- package/dist/bridge/index.cjs.map +1 -0
- package/dist/bridge/index.d.cts +65 -0
- package/dist/bridge/index.d.ts +65 -0
- package/dist/bridge/index.js +16 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge-client-C5OziCRK.d.cts +148 -0
- package/dist/bridge-client-C5dRjrz5.d.ts +148 -0
- package/dist/canvas/index.cjs +1564 -557
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +411 -7
- package/dist/canvas/index.d.ts +411 -7
- package/dist/canvas/index.js +1559 -552
- package/dist/canvas/index.js.map +1 -1
- package/dist/chart/index.cjs +34 -76
- package/dist/chart/index.cjs.map +1 -1
- package/dist/chart/index.d.cts +9 -38
- package/dist/chart/index.d.ts +9 -38
- package/dist/chart/index.js +23 -65
- package/dist/chart/index.js.map +1 -1
- package/dist/chunk-AWKPSOSX.js +401 -0
- package/dist/chunk-AWKPSOSX.js.map +1 -0
- package/dist/chunk-B6DSIEXA.js +96 -0
- package/dist/chunk-B6DSIEXA.js.map +1 -0
- package/dist/chunk-EGFUVXKK.cjs +96 -0
- package/dist/chunk-EGFUVXKK.cjs.map +1 -0
- package/dist/{chunk-AMWXSYCI.js → chunk-GPWNYQ7R.js} +2 -370
- package/dist/chunk-GPWNYQ7R.js.map +1 -0
- package/dist/{chunk-TO22LX34.js → chunk-GTMJ4Y3S.js} +2208 -2920
- package/dist/chunk-GTMJ4Y3S.js.map +1 -0
- package/dist/chunk-H5K77C7Y.cjs +3848 -0
- package/dist/chunk-H5K77C7Y.cjs.map +1 -0
- package/dist/chunk-K73CKYUM.cjs +458 -0
- package/dist/chunk-K73CKYUM.cjs.map +1 -0
- package/dist/chunk-MLREO6J3.cjs +737 -0
- package/dist/chunk-MLREO6J3.cjs.map +1 -0
- package/dist/chunk-NONDTTH7.js +5979 -0
- package/dist/chunk-NONDTTH7.js.map +1 -0
- package/dist/chunk-NV22EMOZ.cjs +401 -0
- package/dist/chunk-NV22EMOZ.cjs.map +1 -0
- package/dist/{chunk-YNSK4MGT.cjs → chunk-O2UH7LZE.cjs} +1549 -2261
- package/dist/chunk-O2UH7LZE.cjs.map +1 -0
- package/dist/chunk-PTMIGXZ2.js +467 -0
- package/dist/chunk-PTMIGXZ2.js.map +1 -0
- package/dist/chunk-QIWFEEWG.cjs +467 -0
- package/dist/chunk-QIWFEEWG.cjs.map +1 -0
- package/dist/chunk-VWZB6BCY.js +737 -0
- package/dist/chunk-VWZB6BCY.js.map +1 -0
- package/dist/chunk-WWJNC7SX.js +3848 -0
- package/dist/chunk-WWJNC7SX.js.map +1 -0
- package/dist/chunk-XCXQDPID.cjs +5979 -0
- package/dist/chunk-XCXQDPID.cjs.map +1 -0
- package/dist/color-E9pwm--w.d.cts +33 -0
- package/dist/color-E9pwm--w.d.ts +33 -0
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +3 -3
- package/dist/diagram/index.cjs +1167 -0
- package/dist/diagram/index.cjs.map +1 -0
- package/dist/diagram/index.d.cts +810 -0
- package/dist/diagram/index.d.ts +810 -0
- package/dist/diagram/index.js +1167 -0
- package/dist/diagram/index.js.map +1 -0
- package/dist/{easing-DTmX99QG.d.cts → easing-CNGk-W6d.d.cts} +1 -1
- package/dist/{easing-DTmX99QG.d.ts → easing-CNGk-W6d.d.ts} +1 -1
- package/dist/generation/index.d.cts +2 -2
- package/dist/generation/index.d.ts +2 -2
- package/dist/image-editor/index.d.cts +1 -1
- package/dist/image-editor/index.d.ts +1 -1
- package/dist/parameters/index.d.cts +1 -1
- package/dist/parameters/index.d.ts +1 -1
- package/dist/pathfinder-BZL8aSu0.d.cts +469 -0
- package/dist/pathfinder-CNtsbCRK.d.ts +469 -0
- package/dist/player-Dg0FT0UI.d.cts +193 -0
- package/dist/player-W-5DX3Go.d.ts +193 -0
- package/dist/presence-Cb6WhcrQ.d.cts +77 -0
- package/dist/presence-Cb6WhcrQ.d.ts +77 -0
- package/dist/{registry-CSlH4iUn.d.ts → registry-7AZFuand.d.ts} +1 -1
- package/dist/{registry-B4l-lYts.d.cts → registry-DIpIaCkH.d.cts} +1 -1
- package/dist/svg-CLg2zqZA.d.cts +847 -0
- package/dist/svg-CPK82TtU.d.ts +847 -0
- package/dist/tools/index.cjs +10 -2
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.d.cts +3 -74
- package/dist/tools/index.d.ts +3 -74
- package/dist/tools/index.js +12 -4
- package/dist/types-BULrFyuQ.d.cts +316 -0
- package/dist/types-BULrFyuQ.d.ts +316 -0
- package/dist/vector/index.cjs +1328 -6752
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +7 -847
- package/dist/vector/index.d.ts +7 -847
- package/dist/vector/index.js +645 -6069
- package/dist/vector/index.js.map +1 -1
- package/package.json +19 -1
- package/dist/chunk-AMWXSYCI.js.map +0 -1
- package/dist/chunk-TO22LX34.js.map +0 -1
- package/dist/chunk-XFYUFVAX.cjs +0 -826
- package/dist/chunk-XFYUFVAX.cjs.map +0 -1
- package/dist/chunk-YNSK4MGT.cjs.map +0 -1
- package/dist/pathfinder-B9xh_pCZ.d.ts +0 -1013
- package/dist/pathfinder-QdG70sr9.d.cts +0 -1013
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TOOL_BRIDGE_DEFAULT_PORT,
|
|
3
|
+
TOOL_BRIDGE_PROTOCOL,
|
|
4
|
+
ToolRegistry,
|
|
5
|
+
createMcpServer
|
|
6
|
+
} from "./chunk-PTMIGXZ2.js";
|
|
7
|
+
|
|
8
|
+
// src/bridge/index.ts
|
|
9
|
+
import { createServer } from "http";
|
|
10
|
+
import { createInterface } from "readline";
|
|
11
|
+
|
|
12
|
+
// src/bridge/websocket.ts
|
|
13
|
+
import { createHash } from "crypto";
|
|
14
|
+
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
15
|
+
var MAX_MESSAGE = 16 * 1024 * 1024;
|
|
16
|
+
function acceptWebSocket(req, socket) {
|
|
17
|
+
const key = req.headers["sec-websocket-key"];
|
|
18
|
+
if (req.headers.upgrade?.toLowerCase() !== "websocket" || typeof key !== "string" || req.headers["sec-websocket-version"] !== "13") {
|
|
19
|
+
rejectUpgrade(socket, 400, "Bad Request");
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const accept = createHash("sha1").update(key + GUID).digest("base64");
|
|
23
|
+
socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
24
|
+
Upgrade: websocket\r
|
|
25
|
+
Connection: Upgrade\r
|
|
26
|
+
Sec-WebSocket-Accept: ${accept}\r
|
|
27
|
+
\r
|
|
28
|
+
`);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
function rejectUpgrade(socket, status, message) {
|
|
32
|
+
socket.write(`HTTP/1.1 ${status} ${message}\r
|
|
33
|
+
Connection: close\r
|
|
34
|
+
Content-Type: text/plain\r
|
|
35
|
+
Content-Length: ${Buffer.byteLength(message)}\r
|
|
36
|
+
\r
|
|
37
|
+
${message}`);
|
|
38
|
+
socket.destroy();
|
|
39
|
+
}
|
|
40
|
+
function frame(opcode, payload) {
|
|
41
|
+
const length = payload.length;
|
|
42
|
+
const head = length < 126 ? Buffer.alloc(2) : length < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);
|
|
43
|
+
head[0] = 128 | opcode;
|
|
44
|
+
if (length < 126) head[1] = length;
|
|
45
|
+
else if (length < 65536) {
|
|
46
|
+
head[1] = 126;
|
|
47
|
+
head.writeUInt16BE(length, 2);
|
|
48
|
+
} else {
|
|
49
|
+
head[1] = 127;
|
|
50
|
+
head.writeBigUInt64BE(BigInt(length), 2);
|
|
51
|
+
}
|
|
52
|
+
return Buffer.concat([head, payload]);
|
|
53
|
+
}
|
|
54
|
+
function wrapWebSocket(socket) {
|
|
55
|
+
let buffer = Buffer.alloc(0);
|
|
56
|
+
let fragments = [];
|
|
57
|
+
let open = true;
|
|
58
|
+
const messageListeners = [];
|
|
59
|
+
const closeListeners = [];
|
|
60
|
+
let closeInfo = null;
|
|
61
|
+
const finish = (code, reason) => {
|
|
62
|
+
if (closeInfo) return;
|
|
63
|
+
closeInfo = { code, reason };
|
|
64
|
+
open = false;
|
|
65
|
+
closeListeners.forEach((l) => l(code, reason));
|
|
66
|
+
};
|
|
67
|
+
const close = (code = 1e3, reason = "") => {
|
|
68
|
+
if (!open) return;
|
|
69
|
+
open = false;
|
|
70
|
+
const body = Buffer.alloc(2 + Buffer.byteLength(reason));
|
|
71
|
+
body.writeUInt16BE(code, 0);
|
|
72
|
+
body.write(reason, 2);
|
|
73
|
+
socket.write(frame(8, body));
|
|
74
|
+
socket.end();
|
|
75
|
+
setTimeout(() => socket.destroy(), 1e3).unref?.();
|
|
76
|
+
finish(code, reason);
|
|
77
|
+
};
|
|
78
|
+
socket.on("data", (chunk) => {
|
|
79
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
80
|
+
while (buffer.length >= 2) {
|
|
81
|
+
const fin = (buffer[0] & 128) !== 0;
|
|
82
|
+
const opcode = buffer[0] & 15;
|
|
83
|
+
const masked = (buffer[1] & 128) !== 0;
|
|
84
|
+
let length = buffer[1] & 127;
|
|
85
|
+
let offset = 2;
|
|
86
|
+
if (length === 126) {
|
|
87
|
+
if (buffer.length < 4) return;
|
|
88
|
+
length = buffer.readUInt16BE(2);
|
|
89
|
+
offset = 4;
|
|
90
|
+
} else if (length === 127) {
|
|
91
|
+
if (buffer.length < 10) return;
|
|
92
|
+
const big = buffer.readBigUInt64BE(2);
|
|
93
|
+
if (big > BigInt(MAX_MESSAGE)) return close(1009, "Message too big.");
|
|
94
|
+
length = Number(big);
|
|
95
|
+
offset = 10;
|
|
96
|
+
}
|
|
97
|
+
if (!masked) return close(1002, "Unmasked frame.");
|
|
98
|
+
if (buffer.length < offset + 4 + length) return;
|
|
99
|
+
const mask = buffer.subarray(offset, offset + 4);
|
|
100
|
+
const payload = Buffer.from(buffer.subarray(offset + 4, offset + 4 + length));
|
|
101
|
+
for (let i = 0; i < payload.length; i++) payload[i] = payload[i] ^ mask[i % 4];
|
|
102
|
+
buffer = buffer.subarray(offset + 4 + length);
|
|
103
|
+
if (opcode === 8) {
|
|
104
|
+
const code = payload.length >= 2 ? payload.readUInt16BE(0) : 1005;
|
|
105
|
+
const reason = payload.length > 2 ? payload.subarray(2).toString("utf8") : "";
|
|
106
|
+
if (open) {
|
|
107
|
+
open = false;
|
|
108
|
+
socket.write(frame(8, payload.subarray(0, 2)));
|
|
109
|
+
socket.end();
|
|
110
|
+
}
|
|
111
|
+
return finish(code, reason);
|
|
112
|
+
}
|
|
113
|
+
if (opcode === 9) {
|
|
114
|
+
socket.write(frame(10, payload));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (opcode === 10) continue;
|
|
118
|
+
if (opcode === 2) return close(1003, "Only text messages are supported.");
|
|
119
|
+
if (opcode === 1 || opcode === 0) {
|
|
120
|
+
fragments.push(payload);
|
|
121
|
+
if (fragments.reduce((n, f) => n + f.length, 0) > MAX_MESSAGE) return close(1009, "Message too big.");
|
|
122
|
+
if (fin) {
|
|
123
|
+
const text = Buffer.concat(fragments).toString("utf8");
|
|
124
|
+
fragments = [];
|
|
125
|
+
messageListeners.forEach((l) => l(text));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
socket.on("close", () => finish(1006, ""));
|
|
131
|
+
socket.on("error", () => finish(1006, ""));
|
|
132
|
+
return {
|
|
133
|
+
get open() {
|
|
134
|
+
return open;
|
|
135
|
+
},
|
|
136
|
+
send(text) {
|
|
137
|
+
if (open) socket.write(frame(1, Buffer.from(text, "utf8")));
|
|
138
|
+
},
|
|
139
|
+
close,
|
|
140
|
+
terminate() {
|
|
141
|
+
if (open) close(1001, "Going away.");
|
|
142
|
+
socket.destroy();
|
|
143
|
+
},
|
|
144
|
+
onMessage(listener) {
|
|
145
|
+
messageListeners.push(listener);
|
|
146
|
+
},
|
|
147
|
+
onClose(listener) {
|
|
148
|
+
closeListeners.push(listener);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/bridge/index.ts
|
|
154
|
+
function isLocalOrigin(origin) {
|
|
155
|
+
try {
|
|
156
|
+
const { hostname } = new URL(origin);
|
|
157
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
var EDITOR_ARG = {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: 'Editor id (e.g. "e1") or part of its name, from list_editors. Default: the most recently active editor that has this tool.'
|
|
165
|
+
};
|
|
166
|
+
var BRIDGE_INSTRUCTIONS = "Tools come from editors open in a browser. Call list_editors to see them; when several are open, pass `editor` (an id or part of a name) to pick one, otherwise the most recently active editor that has the tool answers. If no editor is connected, ask the user to open the app and connect it to the bridge.";
|
|
167
|
+
async function createToolBridge(options) {
|
|
168
|
+
const log = options.log ?? ((message) => process.stderr.write(`[${options.name}] ${message}
|
|
169
|
+
`));
|
|
170
|
+
const port = options.port ?? TOOL_BRIDGE_DEFAULT_PORT;
|
|
171
|
+
const retryMs = options.retryMs ?? 2e3;
|
|
172
|
+
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
173
|
+
const extraOrigins = new Set(options.origins ?? []);
|
|
174
|
+
const editors = /* @__PURE__ */ new Map();
|
|
175
|
+
const pending = /* @__PURE__ */ new Map();
|
|
176
|
+
let editorCounter = 0;
|
|
177
|
+
let activity = 0;
|
|
178
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
179
|
+
let callCounter = 0;
|
|
180
|
+
let listenError = null;
|
|
181
|
+
let closed = false;
|
|
182
|
+
let retry;
|
|
183
|
+
const send = (editor, message) => editor.socket.send(JSON.stringify(message));
|
|
184
|
+
const registry = new ToolRegistry();
|
|
185
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
186
|
+
const toolsChanged = () => listeners.forEach((l) => l());
|
|
187
|
+
const describeEditors = () => [...editors.values()].map((e) => `${e.id} (${e.name})`).join(", ");
|
|
188
|
+
function pickEditor(tool, ref) {
|
|
189
|
+
if (listenError) throw new Error(listenError);
|
|
190
|
+
const all = [...editors.values()];
|
|
191
|
+
if (!all.length) throw new Error(`No editor is connected to ws://127.0.0.1:${actualPort()}. Ask the user to open the app and connect it to the bridge.`);
|
|
192
|
+
const having = all.filter((e) => e.tools.some((t) => t.name === tool));
|
|
193
|
+
if (typeof ref === "string" && ref) {
|
|
194
|
+
const byId = editors.get(ref);
|
|
195
|
+
const matches = byId ? [byId] : all.filter((e) => e.name.toLowerCase().includes(ref.toLowerCase()));
|
|
196
|
+
if (matches.length !== 1) throw new Error(`No single editor matches "${ref}". Connected: ${describeEditors()}.`);
|
|
197
|
+
const editor = matches[0];
|
|
198
|
+
if (!editor.tools.some((t) => t.name === tool))
|
|
199
|
+
throw new Error(`${editor.id} (${editor.name}) has no tool "${tool}"${having.length ? `; ${having.map((e) => e.id).join(", ")} do` : ""}.`);
|
|
200
|
+
return editor;
|
|
201
|
+
}
|
|
202
|
+
if (!having.length) throw new Error(`No connected editor has the tool "${tool}". Connected: ${describeEditors()}.`);
|
|
203
|
+
return having.sort((a, b) => b.activeSeq - a.activeSeq)[0];
|
|
204
|
+
}
|
|
205
|
+
function call(editor, name, args, signal) {
|
|
206
|
+
const id = `c${++callCounter}`;
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
const giveUp = (reason) => {
|
|
209
|
+
if (!pending.delete(id)) return;
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
send(editor, { t: "cancel", id, reason });
|
|
212
|
+
reject(new Error(reason));
|
|
213
|
+
};
|
|
214
|
+
const timer = setTimeout(() => giveUp(`${editor.id} (${editor.name}) did not answer within ${Math.round(timeoutMs / 1e3)} s (is its tab in the background?).`), timeoutMs);
|
|
215
|
+
pending.set(id, { editor, resolve, reject, timer });
|
|
216
|
+
signal?.addEventListener("abort", () => giveUp("Cancelled."), { once: true });
|
|
217
|
+
send(editor, { t: "call", id, name, arguments: args });
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const listEditors = {
|
|
221
|
+
name: "list_editors",
|
|
222
|
+
title: "List connected editors",
|
|
223
|
+
description: "Lists the editors open in a browser and connected to this bridge: id, name, kind, how many tools each has and how long since it was last active. Pass an id or part of a name as `editor` to other tools to pick one.",
|
|
224
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
225
|
+
annotations: { title: "List connected editors", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
226
|
+
handler: () => {
|
|
227
|
+
if (listenError) throw new Error(listenError);
|
|
228
|
+
return { editors: infos() };
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const forwardedTools = () => {
|
|
232
|
+
const seen = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const editor of editors.values()) for (const tool of editor.tools) if (!seen.has(tool.name) && tool.name !== "list_editors") seen.set(tool.name, tool);
|
|
234
|
+
return [...seen.values()].map((tool) => {
|
|
235
|
+
const schema = tool.inputSchema;
|
|
236
|
+
return {
|
|
237
|
+
...tool,
|
|
238
|
+
title: tool.title ?? tool.annotations?.title ?? tool.name,
|
|
239
|
+
description: tool.description ?? tool.name,
|
|
240
|
+
inputSchema: { ...schema, type: "object", properties: { editor: EDITOR_ARG, ...schema.properties ?? {} } },
|
|
241
|
+
handler: async (args, context) => {
|
|
242
|
+
const { editor: ref, ...rest } = args;
|
|
243
|
+
const editor = pickEditor(tool.name, ref);
|
|
244
|
+
const result = await call(editor, tool.name, rest, context.signal);
|
|
245
|
+
editor.activeAt = Date.now();
|
|
246
|
+
editor.activeSeq = ++activity;
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
registry.register({
|
|
253
|
+
id: "bridge",
|
|
254
|
+
tools: () => [listEditors, ...forwardedTools()],
|
|
255
|
+
subscribe: (notify) => {
|
|
256
|
+
listeners.add(notify);
|
|
257
|
+
return () => listeners.delete(notify);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const infos = () => [...editors.values()].map((e) => ({ id: e.id, name: e.name, ...e.kind ? { kind: e.kind } : {}, tools: e.tools.length, idleSeconds: Math.round((Date.now() - e.activeAt) / 1e3) }));
|
|
261
|
+
const http = createServer((_req, res) => {
|
|
262
|
+
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
263
|
+
res.end("This is a WebSocket endpoint for editors (connectToolBridge).");
|
|
264
|
+
});
|
|
265
|
+
http.on("upgrade", (req, socket) => {
|
|
266
|
+
const origin = req.headers.origin;
|
|
267
|
+
if (origin && !extraOrigins.has(origin) && !isLocalOrigin(origin)) {
|
|
268
|
+
log(`refused an editor from ${origin}`);
|
|
269
|
+
return rejectUpgrade(socket, 403, "Origin not allowed");
|
|
270
|
+
}
|
|
271
|
+
if (options.token) {
|
|
272
|
+
const token = new URL(req.url ?? "/", "http://localhost").searchParams.get("token");
|
|
273
|
+
if (token !== options.token) {
|
|
274
|
+
log("refused an editor with a wrong token");
|
|
275
|
+
return rejectUpgrade(socket, 401, "Bad token");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!acceptWebSocket(req, socket)) return;
|
|
279
|
+
const ws = wrapWebSocket(socket);
|
|
280
|
+
sockets.add(ws);
|
|
281
|
+
let editor = null;
|
|
282
|
+
ws.onMessage((text) => {
|
|
283
|
+
let message;
|
|
284
|
+
try {
|
|
285
|
+
message = JSON.parse(text);
|
|
286
|
+
} catch {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (message.t === "hello") {
|
|
290
|
+
if (message.v !== TOOL_BRIDGE_PROTOCOL) {
|
|
291
|
+
log(`refused an editor speaking protocol v${String(message.v)}`);
|
|
292
|
+
return ws.close(1008, `This bridge speaks protocol v${TOOL_BRIDGE_PROTOCOL}; update @pixerate/editor.`);
|
|
293
|
+
}
|
|
294
|
+
editor = { id: `e${++editorCounter}`, name: String(message.name || "Editor"), ...message.kind ? { kind: message.kind } : {}, socket: ws, tools: Array.isArray(message.tools) ? message.tools : [], activeAt: Date.now(), activeSeq: ++activity };
|
|
295
|
+
editors.set(editor.id, editor);
|
|
296
|
+
ws.send(JSON.stringify({ t: "welcome", v: TOOL_BRIDGE_PROTOCOL, id: editor.id }));
|
|
297
|
+
log(`editor ${editor.id} connected: ${editor.name} (${editor.tools.length} tools)`);
|
|
298
|
+
toolsChanged();
|
|
299
|
+
} else if (message.t === "tools" && editor) {
|
|
300
|
+
editor.tools = Array.isArray(message.tools) ? message.tools : [];
|
|
301
|
+
toolsChanged();
|
|
302
|
+
} else if (message.t === "result") {
|
|
303
|
+
const waiting = pending.get(message.id);
|
|
304
|
+
if (!waiting || waiting.editor !== editor) return;
|
|
305
|
+
pending.delete(message.id);
|
|
306
|
+
clearTimeout(waiting.timer);
|
|
307
|
+
waiting.resolve(message.result);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
ws.onClose(() => {
|
|
311
|
+
sockets.delete(ws);
|
|
312
|
+
if (!editor) return;
|
|
313
|
+
editors.delete(editor.id);
|
|
314
|
+
for (const [id, waiting] of pending) {
|
|
315
|
+
if (waiting.editor !== editor) continue;
|
|
316
|
+
pending.delete(id);
|
|
317
|
+
clearTimeout(waiting.timer);
|
|
318
|
+
waiting.reject(new Error(`${editor.id} (${editor.name}) disconnected before answering.`));
|
|
319
|
+
}
|
|
320
|
+
log(`editor ${editor.id} disconnected`);
|
|
321
|
+
toolsChanged();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
const actualPort = () => {
|
|
325
|
+
const address = http.address();
|
|
326
|
+
return address && typeof address === "object" ? address.port : port;
|
|
327
|
+
};
|
|
328
|
+
const listen = () => http.listen(port, "127.0.0.1");
|
|
329
|
+
const listening = new Promise((resolve) => {
|
|
330
|
+
http.on("listening", () => {
|
|
331
|
+
if (listenError) log(`port ${port} is free now`);
|
|
332
|
+
listenError = null;
|
|
333
|
+
log(`waiting for editors on ws://127.0.0.1:${actualPort()}`);
|
|
334
|
+
resolve();
|
|
335
|
+
});
|
|
336
|
+
http.on("error", (error) => {
|
|
337
|
+
const taken = error.code === "EADDRINUSE";
|
|
338
|
+
const again = taken && retryMs > 0 && !closed;
|
|
339
|
+
const message = taken ? `Port ${port} is in use (another bridge?). ${again ? "This bridge keeps trying it; editors can connect once it is free. Or start" : "Start"} this one on another port.` : String(error);
|
|
340
|
+
if (message !== listenError) log(message);
|
|
341
|
+
listenError = message;
|
|
342
|
+
if (again) retry = setTimeout(listen, retryMs);
|
|
343
|
+
resolve();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
listen();
|
|
347
|
+
let output = null;
|
|
348
|
+
const write = (message) => output?.write(`${JSON.stringify(message)}
|
|
349
|
+
`);
|
|
350
|
+
const mcp = createMcpServer(registry, {
|
|
351
|
+
name: options.name,
|
|
352
|
+
version: options.version ?? "0.1.0",
|
|
353
|
+
instructions: [options.instructions, BRIDGE_INSTRUCTIONS].filter(Boolean).join("\n\n"),
|
|
354
|
+
send: write
|
|
355
|
+
});
|
|
356
|
+
let lines = null;
|
|
357
|
+
if (options.stdio !== false) {
|
|
358
|
+
const io = options.stdio ?? { input: process.stdin, output: process.stdout };
|
|
359
|
+
output = io.output;
|
|
360
|
+
lines = createInterface({ input: io.input });
|
|
361
|
+
lines.on("line", (line) => {
|
|
362
|
+
if (!line.trim()) return;
|
|
363
|
+
void mcp.handle(line).then((response) => {
|
|
364
|
+
if (response) write(response);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
await listening;
|
|
369
|
+
return {
|
|
370
|
+
get port() {
|
|
371
|
+
return actualPort();
|
|
372
|
+
},
|
|
373
|
+
mcp,
|
|
374
|
+
registry,
|
|
375
|
+
editors: infos,
|
|
376
|
+
async close() {
|
|
377
|
+
closed = true;
|
|
378
|
+
clearTimeout(retry);
|
|
379
|
+
for (const [id, waiting] of pending) {
|
|
380
|
+
clearTimeout(waiting.timer);
|
|
381
|
+
waiting.reject(new Error("The bridge closed."));
|
|
382
|
+
pending.delete(id);
|
|
383
|
+
}
|
|
384
|
+
for (const socket of [...sockets]) socket.terminate();
|
|
385
|
+
editors.clear();
|
|
386
|
+
mcp.close();
|
|
387
|
+
lines?.close();
|
|
388
|
+
if (http.listening) {
|
|
389
|
+
const done = new Promise((resolve) => http.close(() => resolve()));
|
|
390
|
+
http.closeAllConnections();
|
|
391
|
+
await done;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export {
|
|
398
|
+
isLocalOrigin,
|
|
399
|
+
createToolBridge
|
|
400
|
+
};
|
|
401
|
+
//# sourceMappingURL=chunk-AWKPSOSX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridge/index.ts","../src/bridge/websocket.ts"],"sourcesContent":["/// <reference types=\"node\" />\n/**\n * `@pixerate/editor/bridge` (Node only): one MCP server process that drives editors open in\n * browser tabs. The MCP client (Claude Desktop, Claude Code, an IDE) talks MCP over stdio;\n * pages connect their `ToolRegistry` over a localhost WebSocket with `connectToolBridge`\n * (`@pixerate/editor/tools`) and answer the calls. The bridge forwards every tool the pages\n * expose, adds an `editor` argument to pick one when several are open, and `list_editors`.\n *\n * Only 127.0.0.1 is listened on; browser origins must be local (or allowed explicitly), and\n * an optional token keeps other local pages out. MCP `notifications/cancelled` cancels the\n * call in the page. Ported from Ursula's `createRemoteBridge`, built on `createMcpServer`.\n *\n * ```ts\n * import { createToolBridge } from \"@pixerate/editor/bridge\";\n * await createToolBridge({ name: \"my-app\", token: process.env.BRIDGE_TOKEN });\n * ```\n *\n * or run `npx pixerate-mcp-bridge --token <secret>` and add it to the MCP client's config.\n */\nimport { createServer, type Server as HttpServer } from 'node:http';\nimport { createInterface } from 'node:readline';\nimport { TOOL_BRIDGE_DEFAULT_PORT, TOOL_BRIDGE_PROTOCOL, type ToolBridgeEditorMessage, type ToolBridgeServerMessage } from '../tools/bridge-client.js';\nimport { createMcpServer, type JsonRpcNotification, type JsonRpcResponse, type McpServer } from '../tools/mcp-server.js';\nimport { ToolRegistry, type AgentTool, type McpToolDefinition, type McpToolResult } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\nimport { acceptWebSocket, rejectUpgrade, wrapWebSocket, type BridgeSocket } from './websocket.js';\n\nexport { TOOL_BRIDGE_DEFAULT_PORT, TOOL_BRIDGE_PROTOCOL } from '../tools/bridge-client.js';\n\nexport interface ToolBridgeOptions {\n\t/** The MCP server's name. */\n\tname: string;\n\tversion?: string;\n\t/** Said to the model in the MCP `initialize` result, before the bridge's own usage notes. */\n\tinstructions?: string;\n\t/** WebSocket port editors connect to (default 4477; 0 picks a free one). */\n\tport?: number;\n\t/** Shared secret pages must send as `?token=`. Recommended: any local page can otherwise connect. */\n\ttoken?: string;\n\t/** Browser origins allowed besides localhost ones (e.g. \"https://app.example.com\"). */\n\torigins?: readonly string[];\n\t/** How long a page has to answer a call (ms, default 60 000). */\n\ttimeoutMs?: number;\n\t/**\n\t * While another process holds the port, how often to try again (ms, default 2000; 0 never).\n\t * Until it gets the port, tools say why.\n\t */\n\tretryMs?: number;\n\t/**\n\t * Where MCP messages come from and go to. Default: stdio (one JSON message per line on\n\t * stdin / stdout). `false` serves no transport; call `bridge.mcp.handle` yourself.\n\t */\n\tstdio?: { input: NodeJS.ReadableStream; output: NodeJS.WritableStream } | false;\n\t/** Diagnostics (default: stderr, since stdout carries MCP). */\n\tlog?: (message: string) => void;\n}\n\n/** An editor as `list_editors` reports it. */\nexport interface ToolBridgeEditorInfo {\n\tid: string;\n\tname: string;\n\tkind?: string;\n\ttools: number;\n\t/** Seconds since the editor last connected or answered a call. */\n\tidleSeconds: number;\n}\n\nexport interface ToolBridge {\n\t/** The WebSocket port editors connect to. */\n\treadonly port: number;\n\t/** The MCP server (handle messages yourself when `stdio` is false). */\n\treadonly mcp: McpServer;\n\t/** The registry the MCP server serves: `list_editors` plus every tool the pages expose. */\n\treadonly registry: ToolRegistry;\n\t/** The connected editors. */\n\teditors(): ToolBridgeEditorInfo[];\n\t/** Stops listening, disconnects editors, fails pending calls and closes MCP. */\n\tclose(): Promise<void>;\n}\n\ninterface Editor {\n\tid: string;\n\tname: string;\n\tkind?: string;\n\tsocket: BridgeSocket;\n\ttools: McpToolDefinition[];\n\tactiveAt: number;\n\t/** Bumped on connect and on every answered call; picks the most recently active editor. */\n\tactiveSeq: number;\n}\n\ninterface Pending {\n\teditor: Editor;\n\tresolve: (result: McpToolResult) => void;\n\treject: (error: Error) => void;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\n/** Whether a browser origin is this machine (localhost, 127.0.0.1, [::1], *.localhost). */\nexport function isLocalOrigin(origin: string): boolean {\n\ttry {\n\t\tconst { hostname } = new URL(origin);\n\t\treturn hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]' || hostname.endsWith('.localhost');\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst EDITOR_ARG: JsonSchema = {\n\ttype: 'string',\n\tdescription: 'Editor id (e.g. \"e1\") or part of its name, from list_editors. Default: the most recently active editor that has this tool.'\n};\n\nconst BRIDGE_INSTRUCTIONS =\n\t'Tools come from editors open in a browser. Call list_editors to see them; when several are open, pass `editor` (an id or part of a name) to pick one, otherwise the most recently active editor that has the tool answers. If no editor is connected, ask the user to open the app and connect it to the bridge.';\n\n/**\n * Starts a bridge: listens for editors on 127.0.0.1 and serves their tools, plus\n * `list_editors`, over MCP. Resolves once the port is bound (or known to be taken).\n */\nexport async function createToolBridge(options: ToolBridgeOptions): Promise<ToolBridge> {\n\tconst log = options.log ?? ((message: string) => process.stderr.write(`[${options.name}] ${message}\\n`));\n\tconst port = options.port ?? TOOL_BRIDGE_DEFAULT_PORT;\n\tconst retryMs = options.retryMs ?? 2000;\n\tconst timeoutMs = options.timeoutMs ?? 60_000;\n\tconst extraOrigins = new Set(options.origins ?? []);\n\tconst editors = new Map<string, Editor>();\n\tconst pending = new Map<string, Pending>();\n\tlet editorCounter = 0;\n\tlet activity = 0;\n\tconst sockets = new Set<BridgeSocket>();\n\tlet callCounter = 0;\n\tlet listenError: string | null = null;\n\tlet closed = false;\n\tlet retry: ReturnType<typeof setTimeout> | undefined;\n\n\tconst send = (editor: Editor, message: ToolBridgeServerMessage) => editor.socket.send(JSON.stringify(message));\n\tconst registry = new ToolRegistry();\n\tconst listeners = new Set<() => void>();\n\tconst toolsChanged = () => listeners.forEach((l) => l());\n\n\t// ---------------------------------------------------------------- routing\n\tconst describeEditors = () => [...editors.values()].map((e) => `${e.id} (${e.name})`).join(', ');\n\tfunction pickEditor(tool: string, ref: unknown): Editor {\n\t\tif (listenError) throw new Error(listenError);\n\t\tconst all = [...editors.values()];\n\t\tif (!all.length) throw new Error(`No editor is connected to ws://127.0.0.1:${actualPort()}. Ask the user to open the app and connect it to the bridge.`);\n\t\tconst having = all.filter((e) => e.tools.some((t) => t.name === tool));\n\t\tif (typeof ref === 'string' && ref) {\n\t\t\tconst byId = editors.get(ref);\n\t\t\tconst matches = byId ? [byId] : all.filter((e) => e.name.toLowerCase().includes(ref.toLowerCase()));\n\t\t\tif (matches.length !== 1) throw new Error(`No single editor matches \"${ref}\". Connected: ${describeEditors()}.`);\n\t\t\tconst editor = matches[0]!;\n\t\t\tif (!editor.tools.some((t) => t.name === tool))\n\t\t\t\tthrow new Error(`${editor.id} (${editor.name}) has no tool \"${tool}\"${having.length ? `; ${having.map((e) => e.id).join(', ')} do` : ''}.`);\n\t\t\treturn editor;\n\t\t}\n\t\tif (!having.length) throw new Error(`No connected editor has the tool \"${tool}\". Connected: ${describeEditors()}.`);\n\t\treturn having.sort((a, b) => b.activeSeq - a.activeSeq)[0]!;\n\t}\n\n\tfunction call(editor: Editor, name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpToolResult> {\n\t\tconst id = `c${++callCounter}`;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst giveUp = (reason: string) => {\n\t\t\t\tif (!pending.delete(id)) return;\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tsend(editor, { t: 'cancel', id, reason });\n\t\t\t\treject(new Error(reason));\n\t\t\t};\n\t\t\tconst timer = setTimeout(() => giveUp(`${editor.id} (${editor.name}) did not answer within ${Math.round(timeoutMs / 1000)} s (is its tab in the background?).`), timeoutMs);\n\t\t\tpending.set(id, { editor, resolve, reject, timer });\n\t\t\tsignal?.addEventListener('abort', () => giveUp('Cancelled.'), { once: true });\n\t\t\tsend(editor, { t: 'call', id, name, arguments: args });\n\t\t});\n\t}\n\n\tconst listEditors: AgentTool = {\n\t\tname: 'list_editors',\n\t\ttitle: 'List connected editors',\n\t\tdescription:\n\t\t\t'Lists the editors open in a browser and connected to this bridge: id, name, kind, how many tools each has and how long since it was last active. Pass an id or part of a name as `editor` to other tools to pick one.',\n\t\tinputSchema: { type: 'object', properties: {}, additionalProperties: false },\n\t\tannotations: { title: 'List connected editors', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n\t\thandler: () => {\n\t\t\tif (listenError) throw new Error(listenError);\n\t\t\treturn { editors: infos() };\n\t\t}\n\t};\n\n\t/** One tool per name across editors; the first editor's definition wins, with `editor` added. */\n\tconst forwardedTools = (): AgentTool[] => {\n\t\tconst seen = new Map<string, McpToolDefinition>();\n\t\tfor (const editor of editors.values()) for (const tool of editor.tools) if (!seen.has(tool.name) && tool.name !== 'list_editors') seen.set(tool.name, tool);\n\t\treturn [...seen.values()].map((tool) => {\n\t\t\tconst schema = tool.inputSchema as JsonSchema & { properties?: Record<string, JsonSchema> };\n\t\t\treturn {\n\t\t\t\t...tool,\n\t\t\t\ttitle: tool.title ?? tool.annotations?.title ?? tool.name,\n\t\t\t\tdescription: tool.description ?? tool.name,\n\t\t\t\tinputSchema: { ...schema, type: 'object', properties: { editor: EDITOR_ARG, ...(schema.properties ?? {}) } },\n\t\t\t\thandler: async (args: Record<string, any>, context: { signal?: AbortSignal }) => {\n\t\t\t\t\tconst { editor: ref, ...rest } = args;\n\t\t\t\t\tconst editor = pickEditor(tool.name, ref);\n\t\t\t\t\tconst result = await call(editor, tool.name, rest, context.signal);\n\t\t\t\t\teditor.activeAt = Date.now();\n\t\t\t\t\teditor.activeSeq = ++activity;\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t} as AgentTool;\n\t\t});\n\t};\n\n\tregistry.register({\n\t\tid: 'bridge',\n\t\ttools: () => [listEditors, ...forwardedTools()],\n\t\tsubscribe: (notify) => {\n\t\t\tlisteners.add(notify);\n\t\t\treturn () => listeners.delete(notify);\n\t\t}\n\t});\n\n\tconst infos = (): ToolBridgeEditorInfo[] =>\n\t\t[...editors.values()].map((e) => ({ id: e.id, name: e.name, ...(e.kind ? { kind: e.kind } : {}), tools: e.tools.length, idleSeconds: Math.round((Date.now() - e.activeAt) / 1000) }));\n\n\t// ------------------------------------------------------------- WebSocket\n\tconst http: HttpServer = createServer((_req, res) => {\n\t\tres.writeHead(426, { 'Content-Type': 'text/plain' });\n\t\tres.end('This is a WebSocket endpoint for editors (connectToolBridge).');\n\t});\n\thttp.on('upgrade', (req, socket) => {\n\t\tconst origin = req.headers.origin;\n\t\t// No origin: not a browser (a local script, a test). Browsers always send one.\n\t\tif (origin && !extraOrigins.has(origin) && !isLocalOrigin(origin)) {\n\t\t\tlog(`refused an editor from ${origin}`);\n\t\t\treturn rejectUpgrade(socket, 403, 'Origin not allowed');\n\t\t}\n\t\tif (options.token) {\n\t\t\tconst token = new URL(req.url ?? '/', 'http://localhost').searchParams.get('token');\n\t\t\tif (token !== options.token) {\n\t\t\t\tlog('refused an editor with a wrong token');\n\t\t\t\treturn rejectUpgrade(socket, 401, 'Bad token');\n\t\t\t}\n\t\t}\n\t\tif (!acceptWebSocket(req, socket)) return;\n\t\tconst ws = wrapWebSocket(socket);\n\t\tsockets.add(ws);\n\t\tlet editor: Editor | null = null;\n\t\tws.onMessage((text) => {\n\t\t\tlet message: ToolBridgeEditorMessage;\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (message.t === 'hello') {\n\t\t\t\tif (message.v !== TOOL_BRIDGE_PROTOCOL) {\n\t\t\t\t\tlog(`refused an editor speaking protocol v${String(message.v)}`);\n\t\t\t\t\treturn ws.close(1008, `This bridge speaks protocol v${TOOL_BRIDGE_PROTOCOL}; update @pixerate/editor.`);\n\t\t\t\t}\n\t\t\t\teditor = { id: `e${++editorCounter}`, name: String(message.name || 'Editor'), ...(message.kind ? { kind: message.kind } : {}), socket: ws, tools: Array.isArray(message.tools) ? message.tools : [], activeAt: Date.now(), activeSeq: ++activity };\n\t\t\t\teditors.set(editor.id, editor);\n\t\t\t\tws.send(JSON.stringify({ t: 'welcome', v: TOOL_BRIDGE_PROTOCOL, id: editor.id } satisfies ToolBridgeServerMessage));\n\t\t\t\tlog(`editor ${editor.id} connected: ${editor.name} (${editor.tools.length} tools)`);\n\t\t\t\ttoolsChanged();\n\t\t\t} else if (message.t === 'tools' && editor) {\n\t\t\t\teditor.tools = Array.isArray(message.tools) ? message.tools : [];\n\t\t\t\ttoolsChanged();\n\t\t\t} else if (message.t === 'result') {\n\t\t\t\tconst waiting = pending.get(message.id);\n\t\t\t\tif (!waiting || waiting.editor !== editor) return;\n\t\t\t\tpending.delete(message.id);\n\t\t\t\tclearTimeout(waiting.timer);\n\t\t\t\twaiting.resolve(message.result);\n\t\t\t}\n\t\t});\n\t\tws.onClose(() => {\n\t\t\tsockets.delete(ws);\n\t\t\tif (!editor) return;\n\t\t\teditors.delete(editor.id);\n\t\t\tfor (const [id, waiting] of pending) {\n\t\t\t\tif (waiting.editor !== editor) continue;\n\t\t\t\tpending.delete(id);\n\t\t\t\tclearTimeout(waiting.timer);\n\t\t\t\twaiting.reject(new Error(`${editor.id} (${editor.name}) disconnected before answering.`));\n\t\t\t}\n\t\t\tlog(`editor ${editor.id} disconnected`);\n\t\t\ttoolsChanged();\n\t\t});\n\t});\n\n\tconst actualPort = () => {\n\t\tconst address = http.address();\n\t\treturn address && typeof address === 'object' ? address.port : port;\n\t};\n\tconst listen = () => http.listen(port, '127.0.0.1');\n\tconst listening = new Promise<void>((resolve) => {\n\t\thttp.on('listening', () => {\n\t\t\tif (listenError) log(`port ${port} is free now`);\n\t\t\tlistenError = null;\n\t\t\tlog(`waiting for editors on ws://127.0.0.1:${actualPort()}`);\n\t\t\tresolve();\n\t\t});\n\t\thttp.on('error', (error: NodeJS.ErrnoException) => {\n\t\t\tconst taken = error.code === 'EADDRINUSE';\n\t\t\tconst again = taken && retryMs > 0 && !closed;\n\t\t\tconst message = taken\n\t\t\t\t? `Port ${port} is in use (another bridge?). ${again ? 'This bridge keeps trying it; editors can connect once it is free. Or start' : 'Start'} this one on another port.`\n\t\t\t\t: String(error);\n\t\t\tif (message !== listenError) log(message);\n\t\t\tlistenError = message;\n\t\t\tif (again) retry = setTimeout(listen, retryMs);\n\t\t\t// Still serve MCP, so the agent hears why nothing connects.\n\t\t\tresolve();\n\t\t});\n\t});\n\tlisten();\n\n\t// ------------------------------------------------------------------- MCP\n\tlet output: NodeJS.WritableStream | null = null;\n\tconst write = (message: JsonRpcResponse | JsonRpcNotification) => output?.write(`${JSON.stringify(message)}\\n`);\n\tconst mcp = createMcpServer(registry, {\n\t\tname: options.name,\n\t\tversion: options.version ?? '0.1.0',\n\t\tinstructions: [options.instructions, BRIDGE_INSTRUCTIONS].filter(Boolean).join('\\n\\n'),\n\t\tsend: write\n\t});\n\tlet lines: ReturnType<typeof createInterface> | null = null;\n\tif (options.stdio !== false) {\n\t\tconst io = options.stdio ?? { input: process.stdin, output: process.stdout };\n\t\toutput = io.output;\n\t\tlines = createInterface({ input: io.input });\n\t\tlines.on('line', (line) => {\n\t\t\tif (!line.trim()) return;\n\t\t\tvoid mcp.handle(line).then((response) => {\n\t\t\t\tif (response) write(response);\n\t\t\t});\n\t\t});\n\t}\n\tawait listening;\n\n\treturn {\n\t\tget port() {\n\t\t\treturn actualPort();\n\t\t},\n\t\tmcp,\n\t\tregistry,\n\t\teditors: infos,\n\t\tasync close() {\n\t\t\tclosed = true;\n\t\t\tclearTimeout(retry);\n\t\t\tfor (const [id, waiting] of pending) {\n\t\t\t\tclearTimeout(waiting.timer);\n\t\t\t\twaiting.reject(new Error('The bridge closed.'));\n\t\t\t\tpending.delete(id);\n\t\t\t}\n\t\t\tfor (const socket of [...sockets]) socket.terminate();\n\t\t\teditors.clear();\n\t\t\tmcp.close();\n\t\t\tlines?.close();\n\t\t\tif (http.listening) {\n\t\t\t\tconst done = new Promise<void>((resolve) => http.close(() => resolve()));\n\t\t\t\thttp.closeAllConnections();\n\t\t\t\tawait done;\n\t\t\t}\n\t\t}\n\t};\n}\n","/// <reference types=\"node\" />\n// A small RFC 6455 WebSocket server on node:http, enough for the tool bridge: text frames\n// (fragmented or not), ping / pong, close, and client masking as browsers send it. No\n// compression or binary messages. Keeps the bridge free of a `ws` dependency.\nimport { createHash } from 'node:crypto';\nimport type { IncomingMessage } from 'node:http';\nimport type { Duplex } from 'node:stream';\n\nconst GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';\n/** Largest message accepted from an editor (16 MiB): tool results can carry images. */\nconst MAX_MESSAGE = 16 * 1024 * 1024;\n\nexport interface BridgeSocket {\n\tsend(text: string): void;\n\tclose(code?: number, reason?: string): void;\n\t/** Closes without waiting for the other side (shutting down). */\n\tterminate(): void;\n\tonMessage(listener: (text: string) => void): void;\n\tonClose(listener: (code: number, reason: string) => void): void;\n\treadonly open: boolean;\n}\n\n/** Answers the HTTP upgrade handshake; returns false (and writes a 400) for a bad request. */\nexport function acceptWebSocket(req: IncomingMessage, socket: Duplex): boolean {\n\tconst key = req.headers['sec-websocket-key'];\n\tif (req.headers.upgrade?.toLowerCase() !== 'websocket' || typeof key !== 'string' || req.headers['sec-websocket-version'] !== '13') {\n\t\trejectUpgrade(socket, 400, 'Bad Request');\n\t\treturn false;\n\t}\n\tconst accept = createHash('sha1').update(key + GUID).digest('base64');\n\tsocket.write(`HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: ${accept}\\r\\n\\r\\n`);\n\treturn true;\n}\n\n/** Refuses an upgrade with an HTTP status (the browser sees the connection fail). */\nexport function rejectUpgrade(socket: Duplex, status: number, message: string): void {\n\tsocket.write(`HTTP/1.1 ${status} ${message}\\r\\nConnection: close\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(message)}\\r\\n\\r\\n${message}`);\n\tsocket.destroy();\n}\n\nfunction frame(opcode: number, payload: Buffer): Buffer {\n\tconst length = payload.length;\n\tconst head = length < 126 ? Buffer.alloc(2) : length < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);\n\thead[0] = 0x80 | opcode;\n\tif (length < 126) head[1] = length;\n\telse if (length < 65536) {\n\t\thead[1] = 126;\n\t\thead.writeUInt16BE(length, 2);\n\t} else {\n\t\thead[1] = 127;\n\t\thead.writeBigUInt64BE(BigInt(length), 2);\n\t}\n\treturn Buffer.concat([head, payload]);\n}\n\n/** Wraps an upgraded socket as a text-message WebSocket. */\nexport function wrapWebSocket(socket: Duplex): BridgeSocket {\n\tlet buffer = Buffer.alloc(0);\n\tlet fragments: Buffer[] = [];\n\tlet open = true;\n\tconst messageListeners: Array<(text: string) => void> = [];\n\tconst closeListeners: Array<(code: number, reason: string) => void> = [];\n\tlet closeInfo: { code: number; reason: string } | null = null;\n\n\tconst finish = (code: number, reason: string) => {\n\t\tif (closeInfo) return;\n\t\tcloseInfo = { code, reason };\n\t\topen = false;\n\t\tcloseListeners.forEach((l) => l(code, reason));\n\t};\n\tconst close = (code = 1000, reason = '') => {\n\t\tif (!open) return;\n\t\topen = false;\n\t\tconst body = Buffer.alloc(2 + Buffer.byteLength(reason));\n\t\tbody.writeUInt16BE(code, 0);\n\t\tbody.write(reason, 2);\n\t\tsocket.write(frame(0x8, body));\n\t\tsocket.end();\n\t\t// A peer that never answers the close must not keep the server open.\n\t\tsetTimeout(() => socket.destroy(), 1000).unref?.();\n\t\tfinish(code, reason);\n\t};\n\n\tsocket.on('data', (chunk: Buffer) => {\n\t\tbuffer = Buffer.concat([buffer, chunk]);\n\t\twhile (buffer.length >= 2) {\n\t\t\tconst fin = (buffer[0]! & 0x80) !== 0;\n\t\t\tconst opcode = buffer[0]! & 0x0f;\n\t\t\tconst masked = (buffer[1]! & 0x80) !== 0;\n\t\t\tlet length = buffer[1]! & 0x7f;\n\t\t\tlet offset = 2;\n\t\t\tif (length === 126) {\n\t\t\t\tif (buffer.length < 4) return;\n\t\t\t\tlength = buffer.readUInt16BE(2);\n\t\t\t\toffset = 4;\n\t\t\t} else if (length === 127) {\n\t\t\t\tif (buffer.length < 10) return;\n\t\t\t\tconst big = buffer.readBigUInt64BE(2);\n\t\t\t\tif (big > BigInt(MAX_MESSAGE)) return close(1009, 'Message too big.');\n\t\t\t\tlength = Number(big);\n\t\t\t\toffset = 10;\n\t\t\t}\n\t\t\t// Clients must mask every frame (RFC 6455 §5.1).\n\t\t\tif (!masked) return close(1002, 'Unmasked frame.');\n\t\t\tif (buffer.length < offset + 4 + length) return;\n\t\t\tconst mask = buffer.subarray(offset, offset + 4);\n\t\t\tconst payload = Buffer.from(buffer.subarray(offset + 4, offset + 4 + length));\n\t\t\tfor (let i = 0; i < payload.length; i++) payload[i] = payload[i]! ^ mask[i % 4]!;\n\t\t\tbuffer = buffer.subarray(offset + 4 + length);\n\n\t\t\tif (opcode === 0x8) {\n\t\t\t\tconst code = payload.length >= 2 ? payload.readUInt16BE(0) : 1005;\n\t\t\t\tconst reason = payload.length > 2 ? payload.subarray(2).toString('utf8') : '';\n\t\t\t\tif (open) {\n\t\t\t\t\topen = false;\n\t\t\t\t\tsocket.write(frame(0x8, payload.subarray(0, 2)));\n\t\t\t\t\tsocket.end();\n\t\t\t\t}\n\t\t\t\treturn finish(code, reason);\n\t\t\t}\n\t\t\tif (opcode === 0x9) {\n\t\t\t\tsocket.write(frame(0xa, payload));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (opcode === 0xa) continue;\n\t\t\tif (opcode === 0x2) return close(1003, 'Only text messages are supported.');\n\t\t\tif (opcode === 0x1 || opcode === 0x0) {\n\t\t\t\tfragments.push(payload);\n\t\t\t\tif (fragments.reduce((n, f) => n + f.length, 0) > MAX_MESSAGE) return close(1009, 'Message too big.');\n\t\t\t\tif (fin) {\n\t\t\t\t\tconst text = Buffer.concat(fragments).toString('utf8');\n\t\t\t\t\tfragments = [];\n\t\t\t\t\tmessageListeners.forEach((l) => l(text));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\tsocket.on('close', () => finish(1006, ''));\n\tsocket.on('error', () => finish(1006, ''));\n\n\treturn {\n\t\tget open() {\n\t\t\treturn open;\n\t\t},\n\t\tsend(text) {\n\t\t\tif (open) socket.write(frame(0x1, Buffer.from(text, 'utf8')));\n\t\t},\n\t\tclose,\n\t\tterminate() {\n\t\t\tif (open) close(1001, 'Going away.');\n\t\t\tsocket.destroy();\n\t\t},\n\t\tonMessage(listener) {\n\t\t\tmessageListeners.push(listener);\n\t\t},\n\t\tonClose(listener) {\n\t\t\tcloseListeners.push(listener);\n\t\t}\n\t};\n}\n"],"mappings":";;;;;;;;AAmBA,SAAS,oBAA+C;AACxD,SAAS,uBAAuB;;;AChBhC,SAAS,kBAAkB;AAI3B,IAAM,OAAO;AAEb,IAAM,cAAc,KAAK,OAAO;AAazB,SAAS,gBAAgB,KAAsB,QAAyB;AAC9E,QAAM,MAAM,IAAI,QAAQ,mBAAmB;AAC3C,MAAI,IAAI,QAAQ,SAAS,YAAY,MAAM,eAAe,OAAO,QAAQ,YAAY,IAAI,QAAQ,uBAAuB,MAAM,MAAM;AACnI,kBAAc,QAAQ,KAAK,aAAa;AACxC,WAAO;AAAA,EACR;AACA,QAAM,SAAS,WAAW,MAAM,EAAE,OAAO,MAAM,IAAI,EAAE,OAAO,QAAQ;AACpE,SAAO,MAAM;AAAA;AAAA;AAAA,wBAA0G,MAAM;AAAA;AAAA,CAAU;AACvI,SAAO;AACR;AAGO,SAAS,cAAc,QAAgB,QAAgB,SAAuB;AACpF,SAAO,MAAM,YAAY,MAAM,IAAI,OAAO;AAAA;AAAA;AAAA,kBAAwE,OAAO,WAAW,OAAO,CAAC;AAAA;AAAA,EAAW,OAAO,EAAE;AAChK,SAAO,QAAQ;AAChB;AAEA,SAAS,MAAM,QAAgB,SAAyB;AACvD,QAAM,SAAS,QAAQ;AACvB,QAAM,OAAO,SAAS,MAAM,OAAO,MAAM,CAAC,IAAI,SAAS,QAAQ,OAAO,MAAM,CAAC,IAAI,OAAO,MAAM,EAAE;AAChG,OAAK,CAAC,IAAI,MAAO;AACjB,MAAI,SAAS,IAAK,MAAK,CAAC,IAAI;AAAA,WACnB,SAAS,OAAO;AACxB,SAAK,CAAC,IAAI;AACV,SAAK,cAAc,QAAQ,CAAC;AAAA,EAC7B,OAAO;AACN,SAAK,CAAC,IAAI;AACV,SAAK,iBAAiB,OAAO,MAAM,GAAG,CAAC;AAAA,EACxC;AACA,SAAO,OAAO,OAAO,CAAC,MAAM,OAAO,CAAC;AACrC;AAGO,SAAS,cAAc,QAA8B;AAC3D,MAAI,SAAS,OAAO,MAAM,CAAC;AAC3B,MAAI,YAAsB,CAAC;AAC3B,MAAI,OAAO;AACX,QAAM,mBAAkD,CAAC;AACzD,QAAM,iBAAgE,CAAC;AACvE,MAAI,YAAqD;AAEzD,QAAM,SAAS,CAAC,MAAc,WAAmB;AAChD,QAAI,UAAW;AACf,gBAAY,EAAE,MAAM,OAAO;AAC3B,WAAO;AACP,mBAAe,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;AAAA,EAC9C;AACA,QAAM,QAAQ,CAAC,OAAO,KAAM,SAAS,OAAO;AAC3C,QAAI,CAAC,KAAM;AACX,WAAO;AACP,UAAM,OAAO,OAAO,MAAM,IAAI,OAAO,WAAW,MAAM,CAAC;AACvD,SAAK,cAAc,MAAM,CAAC;AAC1B,SAAK,MAAM,QAAQ,CAAC;AACpB,WAAO,MAAM,MAAM,GAAK,IAAI,CAAC;AAC7B,WAAO,IAAI;AAEX,eAAW,MAAM,OAAO,QAAQ,GAAG,GAAI,EAAE,QAAQ;AACjD,WAAO,MAAM,MAAM;AAAA,EACpB;AAEA,SAAO,GAAG,QAAQ,CAAC,UAAkB;AACpC,aAAS,OAAO,OAAO,CAAC,QAAQ,KAAK,CAAC;AACtC,WAAO,OAAO,UAAU,GAAG;AAC1B,YAAM,OAAO,OAAO,CAAC,IAAK,SAAU;AACpC,YAAM,SAAS,OAAO,CAAC,IAAK;AAC5B,YAAM,UAAU,OAAO,CAAC,IAAK,SAAU;AACvC,UAAI,SAAS,OAAO,CAAC,IAAK;AAC1B,UAAI,SAAS;AACb,UAAI,WAAW,KAAK;AACnB,YAAI,OAAO,SAAS,EAAG;AACvB,iBAAS,OAAO,aAAa,CAAC;AAC9B,iBAAS;AAAA,MACV,WAAW,WAAW,KAAK;AAC1B,YAAI,OAAO,SAAS,GAAI;AACxB,cAAM,MAAM,OAAO,gBAAgB,CAAC;AACpC,YAAI,MAAM,OAAO,WAAW,EAAG,QAAO,MAAM,MAAM,kBAAkB;AACpE,iBAAS,OAAO,GAAG;AACnB,iBAAS;AAAA,MACV;AAEA,UAAI,CAAC,OAAQ,QAAO,MAAM,MAAM,iBAAiB;AACjD,UAAI,OAAO,SAAS,SAAS,IAAI,OAAQ;AACzC,YAAM,OAAO,OAAO,SAAS,QAAQ,SAAS,CAAC;AAC/C,YAAM,UAAU,OAAO,KAAK,OAAO,SAAS,SAAS,GAAG,SAAS,IAAI,MAAM,CAAC;AAC5E,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAAK,SAAQ,CAAC,IAAI,QAAQ,CAAC,IAAK,KAAK,IAAI,CAAC;AAC9E,eAAS,OAAO,SAAS,SAAS,IAAI,MAAM;AAE5C,UAAI,WAAW,GAAK;AACnB,cAAM,OAAO,QAAQ,UAAU,IAAI,QAAQ,aAAa,CAAC,IAAI;AAC7D,cAAM,SAAS,QAAQ,SAAS,IAAI,QAAQ,SAAS,CAAC,EAAE,SAAS,MAAM,IAAI;AAC3E,YAAI,MAAM;AACT,iBAAO;AACP,iBAAO,MAAM,MAAM,GAAK,QAAQ,SAAS,GAAG,CAAC,CAAC,CAAC;AAC/C,iBAAO,IAAI;AAAA,QACZ;AACA,eAAO,OAAO,MAAM,MAAM;AAAA,MAC3B;AACA,UAAI,WAAW,GAAK;AACnB,eAAO,MAAM,MAAM,IAAK,OAAO,CAAC;AAChC;AAAA,MACD;AACA,UAAI,WAAW,GAAK;AACpB,UAAI,WAAW,EAAK,QAAO,MAAM,MAAM,mCAAmC;AAC1E,UAAI,WAAW,KAAO,WAAW,GAAK;AACrC,kBAAU,KAAK,OAAO;AACtB,YAAI,UAAU,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC,IAAI,YAAa,QAAO,MAAM,MAAM,kBAAkB;AACpG,YAAI,KAAK;AACR,gBAAM,OAAO,OAAO,OAAO,SAAS,EAAE,SAAS,MAAM;AACrD,sBAAY,CAAC;AACb,2BAAiB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,QACxC;AAAA,MACD;AAAA,IACD;AAAA,EACD,CAAC;AACD,SAAO,GAAG,SAAS,MAAM,OAAO,MAAM,EAAE,CAAC;AACzC,SAAO,GAAG,SAAS,MAAM,OAAO,MAAM,EAAE,CAAC;AAEzC,SAAO;AAAA,IACN,IAAI,OAAO;AACV,aAAO;AAAA,IACR;AAAA,IACA,KAAK,MAAM;AACV,UAAI,KAAM,QAAO,MAAM,MAAM,GAAK,OAAO,KAAK,MAAM,MAAM,CAAC,CAAC;AAAA,IAC7D;AAAA,IACA;AAAA,IACA,YAAY;AACX,UAAI,KAAM,OAAM,MAAM,aAAa;AACnC,aAAO,QAAQ;AAAA,IAChB;AAAA,IACA,UAAU,UAAU;AACnB,uBAAiB,KAAK,QAAQ;AAAA,IAC/B;AAAA,IACA,QAAQ,UAAU;AACjB,qBAAe,KAAK,QAAQ;AAAA,IAC7B;AAAA,EACD;AACD;;;AD5DO,SAAS,cAAc,QAAyB;AACtD,MAAI;AACH,UAAM,EAAE,SAAS,IAAI,IAAI,IAAI,MAAM;AACnC,WAAO,aAAa,eAAe,aAAa,eAAe,aAAa,WAAW,SAAS,SAAS,YAAY;AAAA,EACtH,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,IAAM,aAAyB;AAAA,EAC9B,MAAM;AAAA,EACN,aAAa;AACd;AAEA,IAAM,sBACL;AAMD,eAAsB,iBAAiB,SAAiD;AACvF,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAAoB,QAAQ,OAAO,MAAM,IAAI,QAAQ,IAAI,KAAK,OAAO;AAAA,CAAI;AACtG,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,eAAe,IAAI,IAAI,QAAQ,WAAW,CAAC,CAAC;AAClD,QAAM,UAAU,oBAAI,IAAoB;AACxC,QAAM,UAAU,oBAAI,IAAqB;AACzC,MAAI,gBAAgB;AACpB,MAAI,WAAW;AACf,QAAM,UAAU,oBAAI,IAAkB;AACtC,MAAI,cAAc;AAClB,MAAI,cAA6B;AACjC,MAAI,SAAS;AACb,MAAI;AAEJ,QAAM,OAAO,CAAC,QAAgB,YAAqC,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC;AAC7G,QAAM,WAAW,IAAI,aAAa;AAClC,QAAM,YAAY,oBAAI,IAAgB;AACtC,QAAM,eAAe,MAAM,UAAU,QAAQ,CAAC,MAAM,EAAE,CAAC;AAGvD,QAAM,kBAAkB,MAAM,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,KAAK,IAAI;AAC/F,WAAS,WAAW,MAAc,KAAsB;AACvD,QAAI,YAAa,OAAM,IAAI,MAAM,WAAW;AAC5C,UAAM,MAAM,CAAC,GAAG,QAAQ,OAAO,CAAC;AAChC,QAAI,CAAC,IAAI,OAAQ,OAAM,IAAI,MAAM,4CAA4C,WAAW,CAAC,8DAA8D;AACvJ,UAAM,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC;AACrE,QAAI,OAAO,QAAQ,YAAY,KAAK;AACnC,YAAM,OAAO,QAAQ,IAAI,GAAG;AAC5B,YAAM,UAAU,OAAO,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,IAAI,YAAY,CAAC,CAAC;AAClG,UAAI,QAAQ,WAAW,EAAG,OAAM,IAAI,MAAM,6BAA6B,GAAG,iBAAiB,gBAAgB,CAAC,GAAG;AAC/G,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,CAAC,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC5C,cAAM,IAAI,MAAM,GAAG,OAAO,EAAE,KAAK,OAAO,IAAI,kBAAkB,IAAI,IAAI,OAAO,SAAS,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC,QAAQ,EAAE,GAAG;AAC3I,aAAO;AAAA,IACR;AACA,QAAI,CAAC,OAAO,OAAQ,OAAM,IAAI,MAAM,qCAAqC,IAAI,iBAAiB,gBAAgB,CAAC,GAAG;AAClH,WAAO,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;AAAA,EAC1D;AAEA,WAAS,KAAK,QAAgB,MAAc,MAA+B,QAA8C;AACxH,UAAM,KAAK,IAAI,EAAE,WAAW;AAC5B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACvC,YAAM,SAAS,CAAC,WAAmB;AAClC,YAAI,CAAC,QAAQ,OAAO,EAAE,EAAG;AACzB,qBAAa,KAAK;AAClB,aAAK,QAAQ,EAAE,GAAG,UAAU,IAAI,OAAO,CAAC;AACxC,eAAO,IAAI,MAAM,MAAM,CAAC;AAAA,MACzB;AACA,YAAM,QAAQ,WAAW,MAAM,OAAO,GAAG,OAAO,EAAE,KAAK,OAAO,IAAI,2BAA2B,KAAK,MAAM,YAAY,GAAI,CAAC,qCAAqC,GAAG,SAAS;AAC1K,cAAQ,IAAI,IAAI,EAAE,QAAQ,SAAS,QAAQ,MAAM,CAAC;AAClD,cAAQ,iBAAiB,SAAS,MAAM,OAAO,YAAY,GAAG,EAAE,MAAM,KAAK,CAAC;AAC5E,WAAK,QAAQ,EAAE,GAAG,QAAQ,IAAI,MAAM,WAAW,KAAK,CAAC;AAAA,IACtD,CAAC;AAAA,EACF;AAEA,QAAM,cAAyB;AAAA,IAC9B,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACC;AAAA,IACD,aAAa,EAAE,MAAM,UAAU,YAAY,CAAC,GAAG,sBAAsB,MAAM;AAAA,IAC3E,aAAa,EAAE,OAAO,0BAA0B,cAAc,MAAM,iBAAiB,OAAO,gBAAgB,MAAM,eAAe,MAAM;AAAA,IACvI,SAAS,MAAM;AACd,UAAI,YAAa,OAAM,IAAI,MAAM,WAAW;AAC5C,aAAO,EAAE,SAAS,MAAM,EAAE;AAAA,IAC3B;AAAA,EACD;AAGA,QAAM,iBAAiB,MAAmB;AACzC,UAAM,OAAO,oBAAI,IAA+B;AAChD,eAAW,UAAU,QAAQ,OAAO,EAAG,YAAW,QAAQ,OAAO,MAAO,KAAI,CAAC,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,SAAS,eAAgB,MAAK,IAAI,KAAK,MAAM,IAAI;AAC1J,WAAO,CAAC,GAAG,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS;AACvC,YAAM,SAAS,KAAK;AACpB,aAAO;AAAA,QACN,GAAG;AAAA,QACH,OAAO,KAAK,SAAS,KAAK,aAAa,SAAS,KAAK;AAAA,QACrD,aAAa,KAAK,eAAe,KAAK;AAAA,QACtC,aAAa,EAAE,GAAG,QAAQ,MAAM,UAAU,YAAY,EAAE,QAAQ,YAAY,GAAI,OAAO,cAAc,CAAC,EAAG,EAAE;AAAA,QAC3G,SAAS,OAAO,MAA2B,YAAsC;AAChF,gBAAM,EAAE,QAAQ,KAAK,GAAG,KAAK,IAAI;AACjC,gBAAM,SAAS,WAAW,KAAK,MAAM,GAAG;AACxC,gBAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,MAAM,MAAM,QAAQ,MAAM;AACjE,iBAAO,WAAW,KAAK,IAAI;AAC3B,iBAAO,YAAY,EAAE;AACrB,iBAAO;AAAA,QACR;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAEA,WAAS,SAAS;AAAA,IACjB,IAAI;AAAA,IACJ,OAAO,MAAM,CAAC,aAAa,GAAG,eAAe,CAAC;AAAA,IAC9C,WAAW,CAAC,WAAW;AACtB,gBAAU,IAAI,MAAM;AACpB,aAAO,MAAM,UAAU,OAAO,MAAM;AAAA,IACrC;AAAA,EACD,CAAC;AAED,QAAM,QAAQ,MACb,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,MAAM,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC,GAAI,OAAO,EAAE,MAAM,QAAQ,aAAa,KAAK,OAAO,KAAK,IAAI,IAAI,EAAE,YAAY,GAAI,EAAE,EAAE;AAGrL,QAAM,OAAmB,aAAa,CAAC,MAAM,QAAQ;AACpD,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,+DAA+D;AAAA,EACxE,CAAC;AACD,OAAK,GAAG,WAAW,CAAC,KAAK,WAAW;AACnC,UAAM,SAAS,IAAI,QAAQ;AAE3B,QAAI,UAAU,CAAC,aAAa,IAAI,MAAM,KAAK,CAAC,cAAc,MAAM,GAAG;AAClE,UAAI,0BAA0B,MAAM,EAAE;AACtC,aAAO,cAAc,QAAQ,KAAK,oBAAoB;AAAA,IACvD;AACA,QAAI,QAAQ,OAAO;AAClB,YAAM,QAAQ,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB,EAAE,aAAa,IAAI,OAAO;AAClF,UAAI,UAAU,QAAQ,OAAO;AAC5B,YAAI,sCAAsC;AAC1C,eAAO,cAAc,QAAQ,KAAK,WAAW;AAAA,MAC9C;AAAA,IACD;AACA,QAAI,CAAC,gBAAgB,KAAK,MAAM,EAAG;AACnC,UAAM,KAAK,cAAc,MAAM;AAC/B,YAAQ,IAAI,EAAE;AACd,QAAI,SAAwB;AAC5B,OAAG,UAAU,CAAC,SAAS;AACtB,UAAI;AACJ,UAAI;AACH,kBAAU,KAAK,MAAM,IAAI;AAAA,MAC1B,QAAQ;AACP;AAAA,MACD;AACA,UAAI,QAAQ,MAAM,SAAS;AAC1B,YAAI,QAAQ,MAAM,sBAAsB;AACvC,cAAI,wCAAwC,OAAO,QAAQ,CAAC,CAAC,EAAE;AAC/D,iBAAO,GAAG,MAAM,MAAM,gCAAgC,oBAAoB,4BAA4B;AAAA,QACvG;AACA,iBAAS,EAAE,IAAI,IAAI,EAAE,aAAa,IAAI,MAAM,OAAO,QAAQ,QAAQ,QAAQ,GAAG,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC,GAAI,QAAQ,IAAI,OAAO,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC,GAAG,UAAU,KAAK,IAAI,GAAG,WAAW,EAAE,SAAS;AACjP,gBAAQ,IAAI,OAAO,IAAI,MAAM;AAC7B,WAAG,KAAK,KAAK,UAAU,EAAE,GAAG,WAAW,GAAG,sBAAsB,IAAI,OAAO,GAAG,CAAmC,CAAC;AAClH,YAAI,UAAU,OAAO,EAAE,eAAe,OAAO,IAAI,KAAK,OAAO,MAAM,MAAM,SAAS;AAClF,qBAAa;AAAA,MACd,WAAW,QAAQ,MAAM,WAAW,QAAQ;AAC3C,eAAO,QAAQ,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC;AAC/D,qBAAa;AAAA,MACd,WAAW,QAAQ,MAAM,UAAU;AAClC,cAAM,UAAU,QAAQ,IAAI,QAAQ,EAAE;AACtC,YAAI,CAAC,WAAW,QAAQ,WAAW,OAAQ;AAC3C,gBAAQ,OAAO,QAAQ,EAAE;AACzB,qBAAa,QAAQ,KAAK;AAC1B,gBAAQ,QAAQ,QAAQ,MAAM;AAAA,MAC/B;AAAA,IACD,CAAC;AACD,OAAG,QAAQ,MAAM;AAChB,cAAQ,OAAO,EAAE;AACjB,UAAI,CAAC,OAAQ;AACb,cAAQ,OAAO,OAAO,EAAE;AACxB,iBAAW,CAAC,IAAI,OAAO,KAAK,SAAS;AACpC,YAAI,QAAQ,WAAW,OAAQ;AAC/B,gBAAQ,OAAO,EAAE;AACjB,qBAAa,QAAQ,KAAK;AAC1B,gBAAQ,OAAO,IAAI,MAAM,GAAG,OAAO,EAAE,KAAK,OAAO,IAAI,kCAAkC,CAAC;AAAA,MACzF;AACA,UAAI,UAAU,OAAO,EAAE,eAAe;AACtC,mBAAa;AAAA,IACd,CAAC;AAAA,EACF,CAAC;AAED,QAAM,aAAa,MAAM;AACxB,UAAM,UAAU,KAAK,QAAQ;AAC7B,WAAO,WAAW,OAAO,YAAY,WAAW,QAAQ,OAAO;AAAA,EAChE;AACA,QAAM,SAAS,MAAM,KAAK,OAAO,MAAM,WAAW;AAClD,QAAM,YAAY,IAAI,QAAc,CAAC,YAAY;AAChD,SAAK,GAAG,aAAa,MAAM;AAC1B,UAAI,YAAa,KAAI,QAAQ,IAAI,cAAc;AAC/C,oBAAc;AACd,UAAI,yCAAyC,WAAW,CAAC,EAAE;AAC3D,cAAQ;AAAA,IACT,CAAC;AACD,SAAK,GAAG,SAAS,CAAC,UAAiC;AAClD,YAAM,QAAQ,MAAM,SAAS;AAC7B,YAAM,QAAQ,SAAS,UAAU,KAAK,CAAC;AACvC,YAAM,UAAU,QACb,QAAQ,IAAI,iCAAiC,QAAQ,+EAA+E,OAAO,+BAC3I,OAAO,KAAK;AACf,UAAI,YAAY,YAAa,KAAI,OAAO;AACxC,oBAAc;AACd,UAAI,MAAO,SAAQ,WAAW,QAAQ,OAAO;AAE7C,cAAQ;AAAA,IACT,CAAC;AAAA,EACF,CAAC;AACD,SAAO;AAGP,MAAI,SAAuC;AAC3C,QAAM,QAAQ,CAAC,YAAmD,QAAQ,MAAM,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,CAAI;AAC9G,QAAM,MAAM,gBAAgB,UAAU;AAAA,IACrC,MAAM,QAAQ;AAAA,IACd,SAAS,QAAQ,WAAW;AAAA,IAC5B,cAAc,CAAC,QAAQ,cAAc,mBAAmB,EAAE,OAAO,OAAO,EAAE,KAAK,MAAM;AAAA,IACrF,MAAM;AAAA,EACP,CAAC;AACD,MAAI,QAAmD;AACvD,MAAI,QAAQ,UAAU,OAAO;AAC5B,UAAM,KAAK,QAAQ,SAAS,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO;AAC3E,aAAS,GAAG;AACZ,YAAQ,gBAAgB,EAAE,OAAO,GAAG,MAAM,CAAC;AAC3C,UAAM,GAAG,QAAQ,CAAC,SAAS;AAC1B,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,WAAK,IAAI,OAAO,IAAI,EAAE,KAAK,CAAC,aAAa;AACxC,YAAI,SAAU,OAAM,QAAQ;AAAA,MAC7B,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AACA,QAAM;AAEN,SAAO;AAAA,IACN,IAAI,OAAO;AACV,aAAO,WAAW;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT,MAAM,QAAQ;AACb,eAAS;AACT,mBAAa,KAAK;AAClB,iBAAW,CAAC,IAAI,OAAO,KAAK,SAAS;AACpC,qBAAa,QAAQ,KAAK;AAC1B,gBAAQ,OAAO,IAAI,MAAM,oBAAoB,CAAC;AAC9C,gBAAQ,OAAO,EAAE;AAAA,MAClB;AACA,iBAAW,UAAU,CAAC,GAAG,OAAO,EAAG,QAAO,UAAU;AACpD,cAAQ,MAAM;AACd,UAAI,MAAM;AACV,aAAO,MAAM;AACb,UAAI,KAAK,WAAW;AACnB,cAAM,OAAO,IAAI,QAAc,CAAC,YAAY,KAAK,MAAM,MAAM,QAAQ,CAAC,CAAC;AACvE,aAAK,oBAAoB;AACzB,cAAM;AAAA,MACP;AAAA,IACD;AAAA,EACD;AACD;","names":[]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/animation/presence.ts
|
|
2
|
+
var PRESENCE_COLORS = ["#7c3aed", "#db2777", "#0891b2", "#ea580c", "#16a34a"];
|
|
3
|
+
var PresenceStore = class {
|
|
4
|
+
state = { actors: [] };
|
|
5
|
+
listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
getState = () => this.state;
|
|
7
|
+
subscribe = (listener) => {
|
|
8
|
+
this.listeners.add(listener);
|
|
9
|
+
return () => {
|
|
10
|
+
this.listeners.delete(listener);
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/** The actor with `id`, if it is on the surface. */
|
|
14
|
+
get(id) {
|
|
15
|
+
return this.state.actors.find((a) => a.id === id);
|
|
16
|
+
}
|
|
17
|
+
/** Adds or changes an actor. */
|
|
18
|
+
set(actor) {
|
|
19
|
+
const current = this.get(actor.id);
|
|
20
|
+
const next = { name: actor.id, color: PRESENCE_COLORS[this.state.actors.length % PRESENCE_COLORS.length], x: 0, y: 0, pressed: false, ...current, ...actor };
|
|
21
|
+
this.state = { actors: current ? this.state.actors.map((a) => a.id === actor.id ? next : a) : [...this.state.actors, next] };
|
|
22
|
+
this.listeners.forEach((l) => l(this.state));
|
|
23
|
+
}
|
|
24
|
+
remove(id) {
|
|
25
|
+
if (!this.get(id)) return;
|
|
26
|
+
this.state = { actors: this.state.actors.filter((a) => a.id !== id) };
|
|
27
|
+
this.listeners.forEach((l) => l(this.state));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var realPresenceClock = {
|
|
31
|
+
now: () => Date.now(),
|
|
32
|
+
wait: (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)))
|
|
33
|
+
};
|
|
34
|
+
function createInstantPresenceClock(start = 0) {
|
|
35
|
+
let t = start;
|
|
36
|
+
return {
|
|
37
|
+
now: () => t,
|
|
38
|
+
wait: async (ms) => {
|
|
39
|
+
t += Math.max(0, ms);
|
|
40
|
+
},
|
|
41
|
+
elapsed: () => t - start
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function tweenPresenceCursor(presence, clock, id, from, to, ms, each) {
|
|
45
|
+
const frames = Math.max(1, Math.round(ms / 16));
|
|
46
|
+
for (let f = 1; f <= frames; f++) {
|
|
47
|
+
const t = f / frames;
|
|
48
|
+
const e = 1 - Math.pow(1 - t, 3);
|
|
49
|
+
presence.set({ id, x: from.x + (to.x - from.x) * e, y: from.y + (to.y - from.y) * e });
|
|
50
|
+
each?.(e);
|
|
51
|
+
await clock.wait(ms / frames);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
var PerformanceQueue = class {
|
|
55
|
+
constructor(presence, options) {
|
|
56
|
+
this.presence = presence;
|
|
57
|
+
this.options = options;
|
|
58
|
+
}
|
|
59
|
+
presence;
|
|
60
|
+
options;
|
|
61
|
+
queues = /* @__PURE__ */ new Map();
|
|
62
|
+
calls = 0;
|
|
63
|
+
run(actor, perform) {
|
|
64
|
+
const prev = this.queues.get(actor.id) ?? Promise.resolve();
|
|
65
|
+
const run = prev.then(async () => {
|
|
66
|
+
this.presence.set({ id: actor.id, ...actor.name ? { name: actor.name } : {}, ...actor.color ? { color: actor.color } : {} });
|
|
67
|
+
this.options.beginGesture?.(`agent:${actor.id}:${++this.calls}`);
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await perform();
|
|
71
|
+
} finally {
|
|
72
|
+
this.options.endGesture?.();
|
|
73
|
+
this.presence.set({ id: actor.id, message: void 0, pressed: false });
|
|
74
|
+
}
|
|
75
|
+
const linger = this.options.linger ?? 1500;
|
|
76
|
+
if (linger > 0) {
|
|
77
|
+
await this.options.clock.wait(linger);
|
|
78
|
+
if (this.queues.get(actor.id) === guarded) this.presence.remove(actor.id);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
});
|
|
82
|
+
const guarded = run.catch(() => void 0);
|
|
83
|
+
this.queues.set(actor.id, guarded);
|
|
84
|
+
return run;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
PRESENCE_COLORS,
|
|
90
|
+
PresenceStore,
|
|
91
|
+
realPresenceClock,
|
|
92
|
+
createInstantPresenceClock,
|
|
93
|
+
tweenPresenceCursor,
|
|
94
|
+
PerformanceQueue
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=chunk-B6DSIEXA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/animation/presence.ts"],"sourcesContent":["// Agent \"performances\" follow Ursula's remote control (src/remote/commands.ts `planCommand`,\n// src/react/remote/director.ts, presence.ts, clock.ts): a pure planner turns a command into\n// cursor steps, a director acts them out per actor and commits each call as one undo step,\n// and the clock is injectable so performances are repeatable in tests. This module holds the\n// parts every director shares (charts, Mermaid diagrams): the cursor store, the clock, the\n// cursor tween and the per-actor queue.\n\nexport interface PresenceActor {\n\tid: string;\n\tname: string;\n\tcolor: string;\n\t/** Cursor position in the surface's own coordinates (scene / canvas). */\n\tx: number;\n\ty: number;\n\t/** The button is down (dragging). */\n\tpressed: boolean;\n\t/** A short note shown next to the cursor (what the agent is doing). */\n\tmessage?: string;\n}\n\nexport interface PresenceState {\n\tactors: readonly PresenceActor[];\n}\n\nexport const PRESENCE_COLORS: readonly string[] = ['#7c3aed', '#db2777', '#0891b2', '#ea580c', '#16a34a'];\n\n/** Cursor positions and notes of agents acting on a surface, for an overlay. */\nexport class PresenceStore {\n\tprivate state: PresenceState = { actors: [] };\n\tprivate readonly listeners = new Set<(state: PresenceState) => void>();\n\n\tgetState = (): PresenceState => this.state;\n\n\tsubscribe = (listener: (state: PresenceState) => void): (() => void) => {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t};\n\n\t/** The actor with `id`, if it is on the surface. */\n\tget(id: string): PresenceActor | undefined {\n\t\treturn this.state.actors.find((a) => a.id === id);\n\t}\n\n\t/** Adds or changes an actor. */\n\tset(actor: Pick<PresenceActor, 'id'> & Partial<PresenceActor>): void {\n\t\tconst current = this.get(actor.id);\n\t\tconst next: PresenceActor = { name: actor.id, color: PRESENCE_COLORS[this.state.actors.length % PRESENCE_COLORS.length]!, x: 0, y: 0, pressed: false, ...current, ...actor };\n\t\tthis.state = { actors: current ? this.state.actors.map((a) => (a.id === actor.id ? next : a)) : [...this.state.actors, next] };\n\t\tthis.listeners.forEach((l) => l(this.state));\n\t}\n\n\tremove(id: string): void {\n\t\tif (!this.get(id)) return;\n\t\tthis.state = { actors: this.state.actors.filter((a) => a.id !== id) };\n\t\tthis.listeners.forEach((l) => l(this.state));\n\t}\n}\n\n/** Time for performances: real timers, or an instant clock for tests and recordings. */\nexport interface PresenceClock {\n\tnow(): number;\n\twait(ms: number): Promise<void>;\n}\n\nexport const realPresenceClock: PresenceClock = {\n\tnow: () => Date.now(),\n\twait: (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)))\n};\n\n/** A clock whose waits finish at once while its time still advances (repeatable performances). */\nexport function createInstantPresenceClock(start = 0): PresenceClock & { elapsed(): number } {\n\tlet t = start;\n\treturn {\n\t\tnow: () => t,\n\t\twait: async (ms) => {\n\t\t\tt += Math.max(0, ms);\n\t\t},\n\t\telapsed: () => t - start\n\t};\n}\n\n/**\n * Glides an actor's cursor from `from` to `to` over `ms` (ease-out cubic, ~60 frames a second),\n * calling `each` with the eased progress after every frame (e.g. to drag a value along).\n */\nexport async function tweenPresenceCursor(\n\tpresence: PresenceStore,\n\tclock: PresenceClock,\n\tid: string,\n\tfrom: { x: number; y: number },\n\tto: { x: number; y: number },\n\tms: number,\n\teach?: (t: number) => void\n): Promise<void> {\n\tconst frames = Math.max(1, Math.round(ms / 16));\n\tfor (let f = 1; f <= frames; f++) {\n\t\tconst t = f / frames;\n\t\tconst e = 1 - Math.pow(1 - t, 3);\n\t\tpresence.set({ id, x: from.x + (to.x - from.x) * e, y: from.y + (to.y - from.y) * e });\n\t\teach?.(e);\n\t\tawait clock.wait(ms / frames);\n\t}\n}\n\nexport interface PresenceActorInfo {\n\tid: string;\n\tname?: string;\n\tcolor?: string;\n}\n\nexport interface PerformanceQueueOptions {\n\tclock: PresenceClock;\n\t/** Hide each actor's cursor this long after its last call (ms, default 1500; 0 keeps it). */\n\tlinger?: number;\n\t/** Called around each call so everything it changes is one undo step. */\n\tbeginGesture?: (key: string) => void;\n\tendGesture?: () => void;\n}\n\n/**\n * Runs performances one after another per actor (different actors run side by side). Each\n * call shows the actor's cursor, wraps the call in one gesture (one undo step), clears the\n * note when it ends, and hides the cursor after `linger` unless another call followed.\n */\nexport class PerformanceQueue {\n\tprivate readonly queues = new Map<string, Promise<unknown>>();\n\tprivate calls = 0;\n\n\tconstructor(\n\t\treadonly presence: PresenceStore,\n\t\tprivate readonly options: PerformanceQueueOptions\n\t) {}\n\n\trun<T>(actor: PresenceActorInfo, perform: () => Promise<T>): Promise<T> {\n\t\tconst prev = this.queues.get(actor.id) ?? Promise.resolve();\n\t\tconst run = prev.then(async () => {\n\t\t\tthis.presence.set({ id: actor.id, ...(actor.name ? { name: actor.name } : {}), ...(actor.color ? { color: actor.color } : {}) });\n\t\t\tthis.options.beginGesture?.(`agent:${actor.id}:${++this.calls}`);\n\t\t\tlet result: T;\n\t\t\ttry {\n\t\t\t\tresult = await perform();\n\t\t\t} finally {\n\t\t\t\tthis.options.endGesture?.();\n\t\t\t\tthis.presence.set({ id: actor.id, message: undefined, pressed: false });\n\t\t\t}\n\t\t\tconst linger = this.options.linger ?? 1500;\n\t\t\tif (linger > 0) {\n\t\t\t\tawait this.options.clock.wait(linger);\n\t\t\t\tif (this.queues.get(actor.id) === guarded) this.presence.remove(actor.id);\n\t\t\t}\n\t\t\treturn result;\n\t\t});\n\t\tconst guarded = run.catch(() => undefined);\n\t\tthis.queues.set(actor.id, guarded);\n\t\treturn run;\n\t}\n}\n"],"mappings":"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","names":[]}
|