@chrrxs/robloxstudio-mcp-inspector 3.0.2 → 3.0.3

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/index.js CHANGED
@@ -1,4 +1,1937 @@
1
1
  #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+
28
+ // ../../node_modules/xmlchars/xml/1.0/ed5.js
29
+ var require_ed5 = __commonJS({
30
+ "../../node_modules/xmlchars/xml/1.0/ed5.js"(exports) {
31
+ "use strict";
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.CHAR = " \n\r -\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}";
34
+ exports.S = " \r\n";
35
+ exports.NAME_START_CHAR = ":A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}";
36
+ exports.NAME_CHAR = "-" + exports.NAME_START_CHAR + ".0-9\xB7\u0300-\u036F\u203F-\u2040";
37
+ exports.CHAR_RE = new RegExp("^[" + exports.CHAR + "]$", "u");
38
+ exports.S_RE = new RegExp("^[" + exports.S + "]+$", "u");
39
+ exports.NAME_START_CHAR_RE = new RegExp("^[" + exports.NAME_START_CHAR + "]$", "u");
40
+ exports.NAME_CHAR_RE = new RegExp("^[" + exports.NAME_CHAR + "]$", "u");
41
+ exports.NAME_RE = new RegExp("^[" + exports.NAME_START_CHAR + "][" + exports.NAME_CHAR + "]*$", "u");
42
+ exports.NMTOKEN_RE = new RegExp("^[" + exports.NAME_CHAR + "]+$", "u");
43
+ var TAB = 9;
44
+ var NL = 10;
45
+ var CR = 13;
46
+ var SPACE = 32;
47
+ exports.S_LIST = [SPACE, NL, CR, TAB];
48
+ function isChar(c) {
49
+ return c >= SPACE && c <= 55295 || c === NL || c === CR || c === TAB || c >= 57344 && c <= 65533 || c >= 65536 && c <= 1114111;
50
+ }
51
+ exports.isChar = isChar;
52
+ function isS(c) {
53
+ return c === SPACE || c === NL || c === CR || c === TAB;
54
+ }
55
+ exports.isS = isS;
56
+ function isNameStartChar(c) {
57
+ return c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 58 || c === 95 || c === 8204 || c === 8205 || c >= 192 && c <= 214 || c >= 216 && c <= 246 || c >= 248 && c <= 767 || c >= 880 && c <= 893 || c >= 895 && c <= 8191 || c >= 8304 && c <= 8591 || c >= 11264 && c <= 12271 || c >= 12289 && c <= 55295 || c >= 63744 && c <= 64975 || c >= 65008 && c <= 65533 || c >= 65536 && c <= 983039;
58
+ }
59
+ exports.isNameStartChar = isNameStartChar;
60
+ function isNameChar(c) {
61
+ return isNameStartChar(c) || c >= 48 && c <= 57 || c === 45 || c === 46 || c === 183 || c >= 768 && c <= 879 || c >= 8255 && c <= 8256;
62
+ }
63
+ exports.isNameChar = isNameChar;
64
+ }
65
+ });
66
+
67
+ // ../../node_modules/xmlchars/xml/1.1/ed2.js
68
+ var require_ed2 = __commonJS({
69
+ "../../node_modules/xmlchars/xml/1.1/ed2.js"(exports) {
70
+ "use strict";
71
+ Object.defineProperty(exports, "__esModule", { value: true });
72
+ exports.CHAR = "-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}";
73
+ exports.RESTRICTED_CHAR = "-\b\v\f-\x7F-\x84\x86-\x9F";
74
+ exports.S = " \r\n";
75
+ exports.NAME_START_CHAR = ":A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}";
76
+ exports.NAME_CHAR = "-" + exports.NAME_START_CHAR + ".0-9\xB7\u0300-\u036F\u203F-\u2040";
77
+ exports.CHAR_RE = new RegExp("^[" + exports.CHAR + "]$", "u");
78
+ exports.RESTRICTED_CHAR_RE = new RegExp("^[" + exports.RESTRICTED_CHAR + "]$", "u");
79
+ exports.S_RE = new RegExp("^[" + exports.S + "]+$", "u");
80
+ exports.NAME_START_CHAR_RE = new RegExp("^[" + exports.NAME_START_CHAR + "]$", "u");
81
+ exports.NAME_CHAR_RE = new RegExp("^[" + exports.NAME_CHAR + "]$", "u");
82
+ exports.NAME_RE = new RegExp("^[" + exports.NAME_START_CHAR + "][" + exports.NAME_CHAR + "]*$", "u");
83
+ exports.NMTOKEN_RE = new RegExp("^[" + exports.NAME_CHAR + "]+$", "u");
84
+ var TAB = 9;
85
+ var NL = 10;
86
+ var CR = 13;
87
+ var SPACE = 32;
88
+ exports.S_LIST = [SPACE, NL, CR, TAB];
89
+ function isChar(c) {
90
+ return c >= 1 && c <= 55295 || c >= 57344 && c <= 65533 || c >= 65536 && c <= 1114111;
91
+ }
92
+ exports.isChar = isChar;
93
+ function isRestrictedChar(c) {
94
+ return c >= 1 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31 || c >= 127 && c <= 132 || c >= 134 && c <= 159;
95
+ }
96
+ exports.isRestrictedChar = isRestrictedChar;
97
+ function isCharAndNotRestricted(c) {
98
+ return c === 9 || c === 10 || c === 13 || c > 31 && c < 127 || c === 133 || c > 159 && c <= 55295 || c >= 57344 && c <= 65533 || c >= 65536 && c <= 1114111;
99
+ }
100
+ exports.isCharAndNotRestricted = isCharAndNotRestricted;
101
+ function isS(c) {
102
+ return c === SPACE || c === NL || c === CR || c === TAB;
103
+ }
104
+ exports.isS = isS;
105
+ function isNameStartChar(c) {
106
+ return c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 58 || c === 95 || c === 8204 || c === 8205 || c >= 192 && c <= 214 || c >= 216 && c <= 246 || c >= 248 && c <= 767 || c >= 880 && c <= 893 || c >= 895 && c <= 8191 || c >= 8304 && c <= 8591 || c >= 11264 && c <= 12271 || c >= 12289 && c <= 55295 || c >= 63744 && c <= 64975 || c >= 65008 && c <= 65533 || c >= 65536 && c <= 983039;
107
+ }
108
+ exports.isNameStartChar = isNameStartChar;
109
+ function isNameChar(c) {
110
+ return isNameStartChar(c) || c >= 48 && c <= 57 || c === 45 || c === 46 || c === 183 || c >= 768 && c <= 879 || c >= 8255 && c <= 8256;
111
+ }
112
+ exports.isNameChar = isNameChar;
113
+ }
114
+ });
115
+
116
+ // ../../node_modules/xmlchars/xmlns/1.0/ed3.js
117
+ var require_ed3 = __commonJS({
118
+ "../../node_modules/xmlchars/xmlns/1.0/ed3.js"(exports) {
119
+ "use strict";
120
+ Object.defineProperty(exports, "__esModule", { value: true });
121
+ exports.NC_NAME_START_CHAR = "A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}";
122
+ exports.NC_NAME_CHAR = "-" + exports.NC_NAME_START_CHAR + ".0-9\xB7\u0300-\u036F\u203F-\u2040";
123
+ exports.NC_NAME_START_CHAR_RE = new RegExp("^[" + exports.NC_NAME_START_CHAR + "]$", "u");
124
+ exports.NC_NAME_CHAR_RE = new RegExp("^[" + exports.NC_NAME_CHAR + "]$", "u");
125
+ exports.NC_NAME_RE = new RegExp("^[" + exports.NC_NAME_START_CHAR + "][" + exports.NC_NAME_CHAR + "]*$", "u");
126
+ function isNCNameStartChar(c) {
127
+ return c >= 65 && c <= 90 || c === 95 || c >= 97 && c <= 122 || c >= 192 && c <= 214 || c >= 216 && c <= 246 || c >= 248 && c <= 767 || c >= 880 && c <= 893 || c >= 895 && c <= 8191 || c >= 8204 && c <= 8205 || c >= 8304 && c <= 8591 || c >= 11264 && c <= 12271 || c >= 12289 && c <= 55295 || c >= 63744 && c <= 64975 || c >= 65008 && c <= 65533 || c >= 65536 && c <= 983039;
128
+ }
129
+ exports.isNCNameStartChar = isNCNameStartChar;
130
+ function isNCNameChar(c) {
131
+ return isNCNameStartChar(c) || (c === 45 || c === 46 || c >= 48 && c <= 57 || c === 183 || c >= 768 && c <= 879 || c >= 8255 && c <= 8256);
132
+ }
133
+ exports.isNCNameChar = isNCNameChar;
134
+ }
135
+ });
136
+
137
+ // ../../node_modules/saxes/saxes.js
138
+ var require_saxes = __commonJS({
139
+ "../../node_modules/saxes/saxes.js"(exports) {
140
+ "use strict";
141
+ Object.defineProperty(exports, "__esModule", { value: true });
142
+ exports.SaxesParser = exports.EVENTS = void 0;
143
+ var ed5 = require_ed5();
144
+ var ed2 = require_ed2();
145
+ var NSed3 = require_ed3();
146
+ var isS = ed5.isS;
147
+ var isChar10 = ed5.isChar;
148
+ var isNameStartChar = ed5.isNameStartChar;
149
+ var isNameChar = ed5.isNameChar;
150
+ var S_LIST = ed5.S_LIST;
151
+ var NAME_RE = ed5.NAME_RE;
152
+ var isChar11 = ed2.isChar;
153
+ var isNCNameStartChar = NSed3.isNCNameStartChar;
154
+ var isNCNameChar = NSed3.isNCNameChar;
155
+ var NC_NAME_RE = NSed3.NC_NAME_RE;
156
+ var XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
157
+ var XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
158
+ var rootNS = {
159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
160
+ __proto__: null,
161
+ xml: XML_NAMESPACE,
162
+ xmlns: XMLNS_NAMESPACE
163
+ };
164
+ var XML_ENTITIES = {
165
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
166
+ __proto__: null,
167
+ amp: "&",
168
+ gt: ">",
169
+ lt: "<",
170
+ quot: '"',
171
+ apos: "'"
172
+ };
173
+ var EOC = -1;
174
+ var NL_LIKE = -2;
175
+ var S_BEGIN = 0;
176
+ var S_BEGIN_WHITESPACE = 1;
177
+ var S_DOCTYPE = 2;
178
+ var S_DOCTYPE_QUOTE = 3;
179
+ var S_DTD = 4;
180
+ var S_DTD_QUOTED = 5;
181
+ var S_DTD_OPEN_WAKA = 6;
182
+ var S_DTD_OPEN_WAKA_BANG = 7;
183
+ var S_DTD_COMMENT = 8;
184
+ var S_DTD_COMMENT_ENDING = 9;
185
+ var S_DTD_COMMENT_ENDED = 10;
186
+ var S_DTD_PI = 11;
187
+ var S_DTD_PI_ENDING = 12;
188
+ var S_TEXT = 13;
189
+ var S_ENTITY = 14;
190
+ var S_OPEN_WAKA = 15;
191
+ var S_OPEN_WAKA_BANG = 16;
192
+ var S_COMMENT = 17;
193
+ var S_COMMENT_ENDING = 18;
194
+ var S_COMMENT_ENDED = 19;
195
+ var S_CDATA = 20;
196
+ var S_CDATA_ENDING = 21;
197
+ var S_CDATA_ENDING_2 = 22;
198
+ var S_PI_FIRST_CHAR = 23;
199
+ var S_PI_REST = 24;
200
+ var S_PI_BODY = 25;
201
+ var S_PI_ENDING = 26;
202
+ var S_XML_DECL_NAME_START = 27;
203
+ var S_XML_DECL_NAME = 28;
204
+ var S_XML_DECL_EQ = 29;
205
+ var S_XML_DECL_VALUE_START = 30;
206
+ var S_XML_DECL_VALUE = 31;
207
+ var S_XML_DECL_SEPARATOR = 32;
208
+ var S_XML_DECL_ENDING = 33;
209
+ var S_OPEN_TAG = 34;
210
+ var S_OPEN_TAG_SLASH = 35;
211
+ var S_ATTRIB = 36;
212
+ var S_ATTRIB_NAME = 37;
213
+ var S_ATTRIB_NAME_SAW_WHITE = 38;
214
+ var S_ATTRIB_VALUE = 39;
215
+ var S_ATTRIB_VALUE_QUOTED = 40;
216
+ var S_ATTRIB_VALUE_CLOSED = 41;
217
+ var S_ATTRIB_VALUE_UNQUOTED = 42;
218
+ var S_CLOSE_TAG = 43;
219
+ var S_CLOSE_TAG_SAW_WHITE = 44;
220
+ var TAB = 9;
221
+ var NL = 10;
222
+ var CR = 13;
223
+ var SPACE = 32;
224
+ var BANG = 33;
225
+ var DQUOTE = 34;
226
+ var AMP = 38;
227
+ var SQUOTE = 39;
228
+ var MINUS = 45;
229
+ var FORWARD_SLASH = 47;
230
+ var SEMICOLON = 59;
231
+ var LESS = 60;
232
+ var EQUAL = 61;
233
+ var GREATER = 62;
234
+ var QUESTION = 63;
235
+ var OPEN_BRACKET = 91;
236
+ var CLOSE_BRACKET = 93;
237
+ var NEL = 133;
238
+ var LS = 8232;
239
+ var isQuote = (c) => c === DQUOTE || c === SQUOTE;
240
+ var QUOTES = [DQUOTE, SQUOTE];
241
+ var DOCTYPE_TERMINATOR = [...QUOTES, OPEN_BRACKET, GREATER];
242
+ var DTD_TERMINATOR = [...QUOTES, LESS, CLOSE_BRACKET];
243
+ var XML_DECL_NAME_TERMINATOR = [EQUAL, QUESTION, ...S_LIST];
244
+ var ATTRIB_VALUE_UNQUOTED_TERMINATOR = [...S_LIST, GREATER, AMP, LESS];
245
+ function nsPairCheck(parser, prefix, uri) {
246
+ switch (prefix) {
247
+ case "xml":
248
+ if (uri !== XML_NAMESPACE) {
249
+ parser.fail(`xml prefix must be bound to ${XML_NAMESPACE}.`);
250
+ }
251
+ break;
252
+ case "xmlns":
253
+ if (uri !== XMLNS_NAMESPACE) {
254
+ parser.fail(`xmlns prefix must be bound to ${XMLNS_NAMESPACE}.`);
255
+ }
256
+ break;
257
+ default:
258
+ }
259
+ switch (uri) {
260
+ case XMLNS_NAMESPACE:
261
+ parser.fail(prefix === "" ? `the default namespace may not be set to ${uri}.` : `may not assign a prefix (even "xmlns") to the URI ${XMLNS_NAMESPACE}.`);
262
+ break;
263
+ case XML_NAMESPACE:
264
+ switch (prefix) {
265
+ case "xml":
266
+ break;
267
+ case "":
268
+ parser.fail(`the default namespace may not be set to ${uri}.`);
269
+ break;
270
+ default:
271
+ parser.fail("may not assign the xml namespace to another prefix.");
272
+ }
273
+ break;
274
+ default:
275
+ }
276
+ }
277
+ function nsMappingCheck(parser, mapping) {
278
+ for (const local of Object.keys(mapping)) {
279
+ nsPairCheck(parser, local, mapping[local]);
280
+ }
281
+ }
282
+ var isNCName = (name) => NC_NAME_RE.test(name);
283
+ var isName = (name) => NAME_RE.test(name);
284
+ var FORBIDDEN_START = 0;
285
+ var FORBIDDEN_BRACKET = 1;
286
+ var FORBIDDEN_BRACKET_BRACKET = 2;
287
+ exports.EVENTS = [
288
+ "xmldecl",
289
+ "text",
290
+ "processinginstruction",
291
+ "doctype",
292
+ "comment",
293
+ "opentagstart",
294
+ "attribute",
295
+ "opentag",
296
+ "closetag",
297
+ "cdata",
298
+ "error",
299
+ "end",
300
+ "ready"
301
+ ];
302
+ var EVENT_NAME_TO_HANDLER_NAME = {
303
+ xmldecl: "xmldeclHandler",
304
+ text: "textHandler",
305
+ processinginstruction: "piHandler",
306
+ doctype: "doctypeHandler",
307
+ comment: "commentHandler",
308
+ opentagstart: "openTagStartHandler",
309
+ attribute: "attributeHandler",
310
+ opentag: "openTagHandler",
311
+ closetag: "closeTagHandler",
312
+ cdata: "cdataHandler",
313
+ error: "errorHandler",
314
+ end: "endHandler",
315
+ ready: "readyHandler"
316
+ };
317
+ var SaxesParser2 = class {
318
+ /**
319
+ * @param opt The parser options.
320
+ */
321
+ constructor(opt) {
322
+ this.opt = opt !== null && opt !== void 0 ? opt : {};
323
+ this.fragmentOpt = !!this.opt.fragment;
324
+ const xmlnsOpt = this.xmlnsOpt = !!this.opt.xmlns;
325
+ this.trackPosition = this.opt.position !== false;
326
+ this.fileName = this.opt.fileName;
327
+ if (xmlnsOpt) {
328
+ this.nameStartCheck = isNCNameStartChar;
329
+ this.nameCheck = isNCNameChar;
330
+ this.isName = isNCName;
331
+ this.processAttribs = this.processAttribsNS;
332
+ this.pushAttrib = this.pushAttribNS;
333
+ this.ns = Object.assign({ __proto__: null }, rootNS);
334
+ const additional = this.opt.additionalNamespaces;
335
+ if (additional != null) {
336
+ nsMappingCheck(this, additional);
337
+ Object.assign(this.ns, additional);
338
+ }
339
+ } else {
340
+ this.nameStartCheck = isNameStartChar;
341
+ this.nameCheck = isNameChar;
342
+ this.isName = isName;
343
+ this.processAttribs = this.processAttribsPlain;
344
+ this.pushAttrib = this.pushAttribPlain;
345
+ }
346
+ this.stateTable = [
347
+ /* eslint-disable @typescript-eslint/unbound-method */
348
+ this.sBegin,
349
+ this.sBeginWhitespace,
350
+ this.sDoctype,
351
+ this.sDoctypeQuote,
352
+ this.sDTD,
353
+ this.sDTDQuoted,
354
+ this.sDTDOpenWaka,
355
+ this.sDTDOpenWakaBang,
356
+ this.sDTDComment,
357
+ this.sDTDCommentEnding,
358
+ this.sDTDCommentEnded,
359
+ this.sDTDPI,
360
+ this.sDTDPIEnding,
361
+ this.sText,
362
+ this.sEntity,
363
+ this.sOpenWaka,
364
+ this.sOpenWakaBang,
365
+ this.sComment,
366
+ this.sCommentEnding,
367
+ this.sCommentEnded,
368
+ this.sCData,
369
+ this.sCDataEnding,
370
+ this.sCDataEnding2,
371
+ this.sPIFirstChar,
372
+ this.sPIRest,
373
+ this.sPIBody,
374
+ this.sPIEnding,
375
+ this.sXMLDeclNameStart,
376
+ this.sXMLDeclName,
377
+ this.sXMLDeclEq,
378
+ this.sXMLDeclValueStart,
379
+ this.sXMLDeclValue,
380
+ this.sXMLDeclSeparator,
381
+ this.sXMLDeclEnding,
382
+ this.sOpenTag,
383
+ this.sOpenTagSlash,
384
+ this.sAttrib,
385
+ this.sAttribName,
386
+ this.sAttribNameSawWhite,
387
+ this.sAttribValue,
388
+ this.sAttribValueQuoted,
389
+ this.sAttribValueClosed,
390
+ this.sAttribValueUnquoted,
391
+ this.sCloseTag,
392
+ this.sCloseTagSawWhite
393
+ /* eslint-enable @typescript-eslint/unbound-method */
394
+ ];
395
+ this._init();
396
+ }
397
+ /**
398
+ * Indicates whether or not the parser is closed. If ``true``, wait for
399
+ * the ``ready`` event to write again.
400
+ */
401
+ get closed() {
402
+ return this._closed;
403
+ }
404
+ _init() {
405
+ var _a;
406
+ this.openWakaBang = "";
407
+ this.text = "";
408
+ this.name = "";
409
+ this.piTarget = "";
410
+ this.entity = "";
411
+ this.q = null;
412
+ this.tags = [];
413
+ this.tag = null;
414
+ this.topNS = null;
415
+ this.chunk = "";
416
+ this.chunkPosition = 0;
417
+ this.i = 0;
418
+ this.prevI = 0;
419
+ this.carriedFromPrevious = void 0;
420
+ this.forbiddenState = FORBIDDEN_START;
421
+ this.attribList = [];
422
+ const { fragmentOpt } = this;
423
+ this.state = fragmentOpt ? S_TEXT : S_BEGIN;
424
+ this.reportedTextBeforeRoot = this.reportedTextAfterRoot = this.closedRoot = this.sawRoot = fragmentOpt;
425
+ this.xmlDeclPossible = !fragmentOpt;
426
+ this.xmlDeclExpects = ["version"];
427
+ this.entityReturnState = void 0;
428
+ let { defaultXMLVersion } = this.opt;
429
+ if (defaultXMLVersion === void 0) {
430
+ if (this.opt.forceXMLVersion === true) {
431
+ throw new Error("forceXMLVersion set but defaultXMLVersion is not set");
432
+ }
433
+ defaultXMLVersion = "1.0";
434
+ }
435
+ this.setXMLVersion(defaultXMLVersion);
436
+ this.positionAtNewLine = 0;
437
+ this.doctype = false;
438
+ this._closed = false;
439
+ this.xmlDecl = {
440
+ version: void 0,
441
+ encoding: void 0,
442
+ standalone: void 0
443
+ };
444
+ this.line = 1;
445
+ this.column = 0;
446
+ this.ENTITIES = Object.create(XML_ENTITIES);
447
+ (_a = this.readyHandler) === null || _a === void 0 ? void 0 : _a.call(this);
448
+ }
449
+ /**
450
+ * The stream position the parser is currently looking at. This field is
451
+ * zero-based.
452
+ *
453
+ * This field is not based on counting Unicode characters but is to be
454
+ * interpreted as a plain index into a JavaScript string.
455
+ */
456
+ get position() {
457
+ return this.chunkPosition + this.i;
458
+ }
459
+ /**
460
+ * The column number of the next character to be read by the parser. *
461
+ * This field is zero-based. (The first column in a line is 0.)
462
+ *
463
+ * This field reports the index at which the next character would be in the
464
+ * line if the line were represented as a JavaScript string. Note that this
465
+ * *can* be different to a count based on the number of *Unicode characters*
466
+ * due to how JavaScript handles astral plane characters.
467
+ *
468
+ * See [[column]] for a number that corresponds to a count of Unicode
469
+ * characters.
470
+ */
471
+ get columnIndex() {
472
+ return this.position - this.positionAtNewLine;
473
+ }
474
+ /**
475
+ * Set an event listener on an event. The parser supports one handler per
476
+ * event type. If you try to set an event handler over an existing handler,
477
+ * the old handler is silently overwritten.
478
+ *
479
+ * @param name The event to listen to.
480
+ *
481
+ * @param handler The handler to set.
482
+ */
483
+ on(name, handler) {
484
+ this[EVENT_NAME_TO_HANDLER_NAME[name]] = handler;
485
+ }
486
+ /**
487
+ * Unset an event handler.
488
+ *
489
+ * @parma name The event to stop listening to.
490
+ */
491
+ off(name) {
492
+ this[EVENT_NAME_TO_HANDLER_NAME[name]] = void 0;
493
+ }
494
+ /**
495
+ * Make an error object. The error object will have a message that contains
496
+ * the ``fileName`` option passed at the creation of the parser. If position
497
+ * tracking was turned on, it will also have line and column number
498
+ * information.
499
+ *
500
+ * @param message The message describing the error to report.
501
+ *
502
+ * @returns An error object with a properly formatted message.
503
+ */
504
+ makeError(message) {
505
+ var _a;
506
+ let msg = (_a = this.fileName) !== null && _a !== void 0 ? _a : "";
507
+ if (this.trackPosition) {
508
+ if (msg.length > 0) {
509
+ msg += ":";
510
+ }
511
+ msg += `${this.line}:${this.column}`;
512
+ }
513
+ if (msg.length > 0) {
514
+ msg += ": ";
515
+ }
516
+ return new Error(msg + message);
517
+ }
518
+ /**
519
+ * Report a parsing error. This method is made public so that client code may
520
+ * check for issues that are outside the scope of this project and can report
521
+ * errors.
522
+ *
523
+ * @param message The error to report.
524
+ *
525
+ * @returns this
526
+ */
527
+ fail(message) {
528
+ const err2 = this.makeError(message);
529
+ const handler = this.errorHandler;
530
+ if (handler === void 0) {
531
+ throw err2;
532
+ } else {
533
+ handler(err2);
534
+ }
535
+ return this;
536
+ }
537
+ /**
538
+ * Write a XML data to the parser.
539
+ *
540
+ * @param chunk The XML data to write.
541
+ *
542
+ * @returns this
543
+ */
544
+ // We do need object for the type here. Yes, it often causes problems
545
+ // but not in this case.
546
+ write(chunk) {
547
+ if (this.closed) {
548
+ return this.fail("cannot write after close; assign an onready handler.");
549
+ }
550
+ let end = false;
551
+ if (chunk === null) {
552
+ end = true;
553
+ chunk = "";
554
+ } else if (typeof chunk === "object") {
555
+ chunk = chunk.toString();
556
+ }
557
+ if (this.carriedFromPrevious !== void 0) {
558
+ chunk = `${this.carriedFromPrevious}${chunk}`;
559
+ this.carriedFromPrevious = void 0;
560
+ }
561
+ let limit = chunk.length;
562
+ const lastCode = chunk.charCodeAt(limit - 1);
563
+ if (!end && // A trailing CR or surrogate must be carried over to the next
564
+ // chunk.
565
+ (lastCode === CR || lastCode >= 55296 && lastCode <= 56319)) {
566
+ this.carriedFromPrevious = chunk[limit - 1];
567
+ limit--;
568
+ chunk = chunk.slice(0, limit);
569
+ }
570
+ const { stateTable } = this;
571
+ this.chunk = chunk;
572
+ this.i = 0;
573
+ while (this.i < limit) {
574
+ stateTable[this.state].call(this);
575
+ }
576
+ this.chunkPosition += limit;
577
+ return end ? this.end() : this;
578
+ }
579
+ /**
580
+ * Close the current stream. Perform final well-formedness checks and reset
581
+ * the parser tstate.
582
+ *
583
+ * @returns this
584
+ */
585
+ close() {
586
+ return this.write(null);
587
+ }
588
+ /**
589
+ * Get a single code point out of the current chunk. This updates the current
590
+ * position if we do position tracking.
591
+ *
592
+ * This is the algorithm to use for XML 1.0.
593
+ *
594
+ * @returns The character read.
595
+ */
596
+ getCode10() {
597
+ const { chunk, i } = this;
598
+ this.prevI = i;
599
+ this.i = i + 1;
600
+ if (i >= chunk.length) {
601
+ return EOC;
602
+ }
603
+ const code = chunk.charCodeAt(i);
604
+ this.column++;
605
+ if (code < 55296) {
606
+ if (code >= SPACE || code === TAB) {
607
+ return code;
608
+ }
609
+ switch (code) {
610
+ case NL:
611
+ this.line++;
612
+ this.column = 0;
613
+ this.positionAtNewLine = this.position;
614
+ return NL;
615
+ case CR:
616
+ if (chunk.charCodeAt(i + 1) === NL) {
617
+ this.i = i + 2;
618
+ }
619
+ this.line++;
620
+ this.column = 0;
621
+ this.positionAtNewLine = this.position;
622
+ return NL_LIKE;
623
+ default:
624
+ this.fail("disallowed character.");
625
+ return code;
626
+ }
627
+ }
628
+ if (code > 56319) {
629
+ if (!(code >= 57344 && code <= 65533)) {
630
+ this.fail("disallowed character.");
631
+ }
632
+ return code;
633
+ }
634
+ const final = 65536 + (code - 55296) * 1024 + (chunk.charCodeAt(i + 1) - 56320);
635
+ this.i = i + 2;
636
+ if (final > 1114111) {
637
+ this.fail("disallowed character.");
638
+ }
639
+ return final;
640
+ }
641
+ /**
642
+ * Get a single code point out of the current chunk. This updates the current
643
+ * position if we do position tracking.
644
+ *
645
+ * This is the algorithm to use for XML 1.1.
646
+ *
647
+ * @returns {number} The character read.
648
+ */
649
+ getCode11() {
650
+ const { chunk, i } = this;
651
+ this.prevI = i;
652
+ this.i = i + 1;
653
+ if (i >= chunk.length) {
654
+ return EOC;
655
+ }
656
+ const code = chunk.charCodeAt(i);
657
+ this.column++;
658
+ if (code < 55296) {
659
+ if (code > 31 && code < 127 || code > 159 && code !== LS || code === TAB) {
660
+ return code;
661
+ }
662
+ switch (code) {
663
+ case NL:
664
+ this.line++;
665
+ this.column = 0;
666
+ this.positionAtNewLine = this.position;
667
+ return NL;
668
+ case CR: {
669
+ const next = chunk.charCodeAt(i + 1);
670
+ if (next === NL || next === NEL) {
671
+ this.i = i + 2;
672
+ }
673
+ }
674
+ /* yes, fall through */
675
+ case NEL:
676
+ // 0x85
677
+ case LS:
678
+ this.line++;
679
+ this.column = 0;
680
+ this.positionAtNewLine = this.position;
681
+ return NL_LIKE;
682
+ default:
683
+ this.fail("disallowed character.");
684
+ return code;
685
+ }
686
+ }
687
+ if (code > 56319) {
688
+ if (!(code >= 57344 && code <= 65533)) {
689
+ this.fail("disallowed character.");
690
+ }
691
+ return code;
692
+ }
693
+ const final = 65536 + (code - 55296) * 1024 + (chunk.charCodeAt(i + 1) - 56320);
694
+ this.i = i + 2;
695
+ if (final > 1114111) {
696
+ this.fail("disallowed character.");
697
+ }
698
+ return final;
699
+ }
700
+ /**
701
+ * Like ``getCode`` but with the return value normalized so that ``NL`` is
702
+ * returned for ``NL_LIKE``.
703
+ */
704
+ getCodeNorm() {
705
+ const c = this.getCode();
706
+ return c === NL_LIKE ? NL : c;
707
+ }
708
+ unget() {
709
+ this.i = this.prevI;
710
+ this.column--;
711
+ }
712
+ /**
713
+ * Capture characters into a buffer until encountering one of a set of
714
+ * characters.
715
+ *
716
+ * @param chars An array of codepoints. Encountering a character in the array
717
+ * ends the capture. (``chars`` may safely contain ``NL``.)
718
+ *
719
+ * @return The character code that made the capture end, or ``EOC`` if we hit
720
+ * the end of the chunk. The return value cannot be NL_LIKE: NL is returned
721
+ * instead.
722
+ */
723
+ captureTo(chars) {
724
+ let { i: start } = this;
725
+ const { chunk } = this;
726
+ while (true) {
727
+ const c = this.getCode();
728
+ const isNLLike = c === NL_LIKE;
729
+ const final = isNLLike ? NL : c;
730
+ if (final === EOC || chars.includes(final)) {
731
+ this.text += chunk.slice(start, this.prevI);
732
+ return final;
733
+ }
734
+ if (isNLLike) {
735
+ this.text += `${chunk.slice(start, this.prevI)}
736
+ `;
737
+ start = this.i;
738
+ }
739
+ }
740
+ }
741
+ /**
742
+ * Capture characters into a buffer until encountering a character.
743
+ *
744
+ * @param char The codepoint that ends the capture. **NOTE ``char`` MAY NOT
745
+ * CONTAIN ``NL``.** Passing ``NL`` will result in buggy behavior.
746
+ *
747
+ * @return ``true`` if we ran into the character. Otherwise, we ran into the
748
+ * end of the current chunk.
749
+ */
750
+ captureToChar(char) {
751
+ let { i: start } = this;
752
+ const { chunk } = this;
753
+ while (true) {
754
+ let c = this.getCode();
755
+ switch (c) {
756
+ case NL_LIKE:
757
+ this.text += `${chunk.slice(start, this.prevI)}
758
+ `;
759
+ start = this.i;
760
+ c = NL;
761
+ break;
762
+ case EOC:
763
+ this.text += chunk.slice(start);
764
+ return false;
765
+ default:
766
+ }
767
+ if (c === char) {
768
+ this.text += chunk.slice(start, this.prevI);
769
+ return true;
770
+ }
771
+ }
772
+ }
773
+ /**
774
+ * Capture characters that satisfy ``isNameChar`` into the ``name`` field of
775
+ * this parser.
776
+ *
777
+ * @return The character code that made the test fail, or ``EOC`` if we hit
778
+ * the end of the chunk. The return value cannot be NL_LIKE: NL is returned
779
+ * instead.
780
+ */
781
+ captureNameChars() {
782
+ const { chunk, i: start } = this;
783
+ while (true) {
784
+ const c = this.getCode();
785
+ if (c === EOC) {
786
+ this.name += chunk.slice(start);
787
+ return EOC;
788
+ }
789
+ if (!isNameChar(c)) {
790
+ this.name += chunk.slice(start, this.prevI);
791
+ return c === NL_LIKE ? NL : c;
792
+ }
793
+ }
794
+ }
795
+ /**
796
+ * Skip white spaces.
797
+ *
798
+ * @return The character that ended the skip, or ``EOC`` if we hit
799
+ * the end of the chunk. The return value cannot be NL_LIKE: NL is returned
800
+ * instead.
801
+ */
802
+ skipSpaces() {
803
+ while (true) {
804
+ const c = this.getCodeNorm();
805
+ if (c === EOC || !isS(c)) {
806
+ return c;
807
+ }
808
+ }
809
+ }
810
+ setXMLVersion(version) {
811
+ this.currentXMLVersion = version;
812
+ if (version === "1.0") {
813
+ this.isChar = isChar10;
814
+ this.getCode = this.getCode10;
815
+ } else {
816
+ this.isChar = isChar11;
817
+ this.getCode = this.getCode11;
818
+ }
819
+ }
820
+ // STATE ENGINE METHODS
821
+ // This needs to be a state separate from S_BEGIN_WHITESPACE because we want
822
+ // to be sure never to come back to this state later.
823
+ sBegin() {
824
+ if (this.chunk.charCodeAt(0) === 65279) {
825
+ this.i++;
826
+ this.column++;
827
+ }
828
+ this.state = S_BEGIN_WHITESPACE;
829
+ }
830
+ sBeginWhitespace() {
831
+ const iBefore = this.i;
832
+ const c = this.skipSpaces();
833
+ if (this.prevI !== iBefore) {
834
+ this.xmlDeclPossible = false;
835
+ }
836
+ switch (c) {
837
+ case LESS:
838
+ this.state = S_OPEN_WAKA;
839
+ if (this.text.length !== 0) {
840
+ throw new Error("no-empty text at start");
841
+ }
842
+ break;
843
+ case EOC:
844
+ break;
845
+ default:
846
+ this.unget();
847
+ this.state = S_TEXT;
848
+ this.xmlDeclPossible = false;
849
+ }
850
+ }
851
+ sDoctype() {
852
+ var _a;
853
+ const c = this.captureTo(DOCTYPE_TERMINATOR);
854
+ switch (c) {
855
+ case GREATER: {
856
+ (_a = this.doctypeHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text);
857
+ this.text = "";
858
+ this.state = S_TEXT;
859
+ this.doctype = true;
860
+ break;
861
+ }
862
+ case EOC:
863
+ break;
864
+ default:
865
+ this.text += String.fromCodePoint(c);
866
+ if (c === OPEN_BRACKET) {
867
+ this.state = S_DTD;
868
+ } else if (isQuote(c)) {
869
+ this.state = S_DOCTYPE_QUOTE;
870
+ this.q = c;
871
+ }
872
+ }
873
+ }
874
+ sDoctypeQuote() {
875
+ const q = this.q;
876
+ if (this.captureToChar(q)) {
877
+ this.text += String.fromCodePoint(q);
878
+ this.q = null;
879
+ this.state = S_DOCTYPE;
880
+ }
881
+ }
882
+ sDTD() {
883
+ const c = this.captureTo(DTD_TERMINATOR);
884
+ if (c === EOC) {
885
+ return;
886
+ }
887
+ this.text += String.fromCodePoint(c);
888
+ if (c === CLOSE_BRACKET) {
889
+ this.state = S_DOCTYPE;
890
+ } else if (c === LESS) {
891
+ this.state = S_DTD_OPEN_WAKA;
892
+ } else if (isQuote(c)) {
893
+ this.state = S_DTD_QUOTED;
894
+ this.q = c;
895
+ }
896
+ }
897
+ sDTDQuoted() {
898
+ const q = this.q;
899
+ if (this.captureToChar(q)) {
900
+ this.text += String.fromCodePoint(q);
901
+ this.state = S_DTD;
902
+ this.q = null;
903
+ }
904
+ }
905
+ sDTDOpenWaka() {
906
+ const c = this.getCodeNorm();
907
+ this.text += String.fromCodePoint(c);
908
+ switch (c) {
909
+ case BANG:
910
+ this.state = S_DTD_OPEN_WAKA_BANG;
911
+ this.openWakaBang = "";
912
+ break;
913
+ case QUESTION:
914
+ this.state = S_DTD_PI;
915
+ break;
916
+ default:
917
+ this.state = S_DTD;
918
+ }
919
+ }
920
+ sDTDOpenWakaBang() {
921
+ const char = String.fromCodePoint(this.getCodeNorm());
922
+ const owb = this.openWakaBang += char;
923
+ this.text += char;
924
+ if (owb !== "-") {
925
+ this.state = owb === "--" ? S_DTD_COMMENT : S_DTD;
926
+ this.openWakaBang = "";
927
+ }
928
+ }
929
+ sDTDComment() {
930
+ if (this.captureToChar(MINUS)) {
931
+ this.text += "-";
932
+ this.state = S_DTD_COMMENT_ENDING;
933
+ }
934
+ }
935
+ sDTDCommentEnding() {
936
+ const c = this.getCodeNorm();
937
+ this.text += String.fromCodePoint(c);
938
+ this.state = c === MINUS ? S_DTD_COMMENT_ENDED : S_DTD_COMMENT;
939
+ }
940
+ sDTDCommentEnded() {
941
+ const c = this.getCodeNorm();
942
+ this.text += String.fromCodePoint(c);
943
+ if (c === GREATER) {
944
+ this.state = S_DTD;
945
+ } else {
946
+ this.fail("malformed comment.");
947
+ this.state = S_DTD_COMMENT;
948
+ }
949
+ }
950
+ sDTDPI() {
951
+ if (this.captureToChar(QUESTION)) {
952
+ this.text += "?";
953
+ this.state = S_DTD_PI_ENDING;
954
+ }
955
+ }
956
+ sDTDPIEnding() {
957
+ const c = this.getCodeNorm();
958
+ this.text += String.fromCodePoint(c);
959
+ if (c === GREATER) {
960
+ this.state = S_DTD;
961
+ }
962
+ }
963
+ sText() {
964
+ if (this.tags.length !== 0) {
965
+ this.handleTextInRoot();
966
+ } else {
967
+ this.handleTextOutsideRoot();
968
+ }
969
+ }
970
+ sEntity() {
971
+ let { i: start } = this;
972
+ const { chunk } = this;
973
+ loop:
974
+ while (true) {
975
+ switch (this.getCode()) {
976
+ case NL_LIKE:
977
+ this.entity += `${chunk.slice(start, this.prevI)}
978
+ `;
979
+ start = this.i;
980
+ break;
981
+ case SEMICOLON: {
982
+ const { entityReturnState } = this;
983
+ const entity = this.entity + chunk.slice(start, this.prevI);
984
+ this.state = entityReturnState;
985
+ let parsed;
986
+ if (entity === "") {
987
+ this.fail("empty entity name.");
988
+ parsed = "&;";
989
+ } else {
990
+ parsed = this.parseEntity(entity);
991
+ this.entity = "";
992
+ }
993
+ if (entityReturnState !== S_TEXT || this.textHandler !== void 0) {
994
+ this.text += parsed;
995
+ }
996
+ break loop;
997
+ }
998
+ case EOC:
999
+ this.entity += chunk.slice(start);
1000
+ break loop;
1001
+ default:
1002
+ }
1003
+ }
1004
+ }
1005
+ sOpenWaka() {
1006
+ const c = this.getCode();
1007
+ if (isNameStartChar(c)) {
1008
+ this.state = S_OPEN_TAG;
1009
+ this.unget();
1010
+ this.xmlDeclPossible = false;
1011
+ } else {
1012
+ switch (c) {
1013
+ case FORWARD_SLASH:
1014
+ this.state = S_CLOSE_TAG;
1015
+ this.xmlDeclPossible = false;
1016
+ break;
1017
+ case BANG:
1018
+ this.state = S_OPEN_WAKA_BANG;
1019
+ this.openWakaBang = "";
1020
+ this.xmlDeclPossible = false;
1021
+ break;
1022
+ case QUESTION:
1023
+ this.state = S_PI_FIRST_CHAR;
1024
+ break;
1025
+ default:
1026
+ this.fail("disallowed character in tag name");
1027
+ this.state = S_TEXT;
1028
+ this.xmlDeclPossible = false;
1029
+ }
1030
+ }
1031
+ }
1032
+ sOpenWakaBang() {
1033
+ this.openWakaBang += String.fromCodePoint(this.getCodeNorm());
1034
+ switch (this.openWakaBang) {
1035
+ case "[CDATA[":
1036
+ if (!this.sawRoot && !this.reportedTextBeforeRoot) {
1037
+ this.fail("text data outside of root node.");
1038
+ this.reportedTextBeforeRoot = true;
1039
+ }
1040
+ if (this.closedRoot && !this.reportedTextAfterRoot) {
1041
+ this.fail("text data outside of root node.");
1042
+ this.reportedTextAfterRoot = true;
1043
+ }
1044
+ this.state = S_CDATA;
1045
+ this.openWakaBang = "";
1046
+ break;
1047
+ case "--":
1048
+ this.state = S_COMMENT;
1049
+ this.openWakaBang = "";
1050
+ break;
1051
+ case "DOCTYPE":
1052
+ this.state = S_DOCTYPE;
1053
+ if (this.doctype || this.sawRoot) {
1054
+ this.fail("inappropriately located doctype declaration.");
1055
+ }
1056
+ this.openWakaBang = "";
1057
+ break;
1058
+ default:
1059
+ if (this.openWakaBang.length >= 7) {
1060
+ this.fail("incorrect syntax.");
1061
+ }
1062
+ }
1063
+ }
1064
+ sComment() {
1065
+ if (this.captureToChar(MINUS)) {
1066
+ this.state = S_COMMENT_ENDING;
1067
+ }
1068
+ }
1069
+ sCommentEnding() {
1070
+ var _a;
1071
+ const c = this.getCodeNorm();
1072
+ if (c === MINUS) {
1073
+ this.state = S_COMMENT_ENDED;
1074
+ (_a = this.commentHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text);
1075
+ this.text = "";
1076
+ } else {
1077
+ this.text += `-${String.fromCodePoint(c)}`;
1078
+ this.state = S_COMMENT;
1079
+ }
1080
+ }
1081
+ sCommentEnded() {
1082
+ const c = this.getCodeNorm();
1083
+ if (c !== GREATER) {
1084
+ this.fail("malformed comment.");
1085
+ this.text += `--${String.fromCodePoint(c)}`;
1086
+ this.state = S_COMMENT;
1087
+ } else {
1088
+ this.state = S_TEXT;
1089
+ }
1090
+ }
1091
+ sCData() {
1092
+ if (this.captureToChar(CLOSE_BRACKET)) {
1093
+ this.state = S_CDATA_ENDING;
1094
+ }
1095
+ }
1096
+ sCDataEnding() {
1097
+ const c = this.getCodeNorm();
1098
+ if (c === CLOSE_BRACKET) {
1099
+ this.state = S_CDATA_ENDING_2;
1100
+ } else {
1101
+ this.text += `]${String.fromCodePoint(c)}`;
1102
+ this.state = S_CDATA;
1103
+ }
1104
+ }
1105
+ sCDataEnding2() {
1106
+ var _a;
1107
+ const c = this.getCodeNorm();
1108
+ switch (c) {
1109
+ case GREATER: {
1110
+ (_a = this.cdataHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.text);
1111
+ this.text = "";
1112
+ this.state = S_TEXT;
1113
+ break;
1114
+ }
1115
+ case CLOSE_BRACKET:
1116
+ this.text += "]";
1117
+ break;
1118
+ default:
1119
+ this.text += `]]${String.fromCodePoint(c)}`;
1120
+ this.state = S_CDATA;
1121
+ }
1122
+ }
1123
+ // We need this separate state to check the first character fo the pi target
1124
+ // with this.nameStartCheck which allows less characters than this.nameCheck.
1125
+ sPIFirstChar() {
1126
+ const c = this.getCodeNorm();
1127
+ if (this.nameStartCheck(c)) {
1128
+ this.piTarget += String.fromCodePoint(c);
1129
+ this.state = S_PI_REST;
1130
+ } else if (c === QUESTION || isS(c)) {
1131
+ this.fail("processing instruction without a target.");
1132
+ this.state = c === QUESTION ? S_PI_ENDING : S_PI_BODY;
1133
+ } else {
1134
+ this.fail("disallowed character in processing instruction name.");
1135
+ this.piTarget += String.fromCodePoint(c);
1136
+ this.state = S_PI_REST;
1137
+ }
1138
+ }
1139
+ sPIRest() {
1140
+ const { chunk, i: start } = this;
1141
+ while (true) {
1142
+ const c = this.getCodeNorm();
1143
+ if (c === EOC) {
1144
+ this.piTarget += chunk.slice(start);
1145
+ return;
1146
+ }
1147
+ if (!this.nameCheck(c)) {
1148
+ this.piTarget += chunk.slice(start, this.prevI);
1149
+ const isQuestion = c === QUESTION;
1150
+ if (isQuestion || isS(c)) {
1151
+ if (this.piTarget === "xml") {
1152
+ if (!this.xmlDeclPossible) {
1153
+ this.fail("an XML declaration must be at the start of the document.");
1154
+ }
1155
+ this.state = isQuestion ? S_XML_DECL_ENDING : S_XML_DECL_NAME_START;
1156
+ } else {
1157
+ this.state = isQuestion ? S_PI_ENDING : S_PI_BODY;
1158
+ }
1159
+ } else {
1160
+ this.fail("disallowed character in processing instruction name.");
1161
+ this.piTarget += String.fromCodePoint(c);
1162
+ }
1163
+ break;
1164
+ }
1165
+ }
1166
+ }
1167
+ sPIBody() {
1168
+ if (this.text.length === 0) {
1169
+ const c = this.getCodeNorm();
1170
+ if (c === QUESTION) {
1171
+ this.state = S_PI_ENDING;
1172
+ } else if (!isS(c)) {
1173
+ this.text = String.fromCodePoint(c);
1174
+ }
1175
+ } else if (this.captureToChar(QUESTION)) {
1176
+ this.state = S_PI_ENDING;
1177
+ }
1178
+ }
1179
+ sPIEnding() {
1180
+ var _a;
1181
+ const c = this.getCodeNorm();
1182
+ if (c === GREATER) {
1183
+ const { piTarget } = this;
1184
+ if (piTarget.toLowerCase() === "xml") {
1185
+ this.fail("the XML declaration must appear at the start of the document.");
1186
+ }
1187
+ (_a = this.piHandler) === null || _a === void 0 ? void 0 : _a.call(this, {
1188
+ target: piTarget,
1189
+ body: this.text
1190
+ });
1191
+ this.piTarget = this.text = "";
1192
+ this.state = S_TEXT;
1193
+ } else if (c === QUESTION) {
1194
+ this.text += "?";
1195
+ } else {
1196
+ this.text += `?${String.fromCodePoint(c)}`;
1197
+ this.state = S_PI_BODY;
1198
+ }
1199
+ this.xmlDeclPossible = false;
1200
+ }
1201
+ sXMLDeclNameStart() {
1202
+ const c = this.skipSpaces();
1203
+ if (c === QUESTION) {
1204
+ this.state = S_XML_DECL_ENDING;
1205
+ return;
1206
+ }
1207
+ if (c !== EOC) {
1208
+ this.state = S_XML_DECL_NAME;
1209
+ this.name = String.fromCodePoint(c);
1210
+ }
1211
+ }
1212
+ sXMLDeclName() {
1213
+ const c = this.captureTo(XML_DECL_NAME_TERMINATOR);
1214
+ if (c === QUESTION) {
1215
+ this.state = S_XML_DECL_ENDING;
1216
+ this.name += this.text;
1217
+ this.text = "";
1218
+ this.fail("XML declaration is incomplete.");
1219
+ return;
1220
+ }
1221
+ if (!(isS(c) || c === EQUAL)) {
1222
+ return;
1223
+ }
1224
+ this.name += this.text;
1225
+ this.text = "";
1226
+ if (!this.xmlDeclExpects.includes(this.name)) {
1227
+ switch (this.name.length) {
1228
+ case 0:
1229
+ this.fail("did not expect any more name/value pairs.");
1230
+ break;
1231
+ case 1:
1232
+ this.fail(`expected the name ${this.xmlDeclExpects[0]}.`);
1233
+ break;
1234
+ default:
1235
+ this.fail(`expected one of ${this.xmlDeclExpects.join(", ")}`);
1236
+ }
1237
+ }
1238
+ this.state = c === EQUAL ? S_XML_DECL_VALUE_START : S_XML_DECL_EQ;
1239
+ }
1240
+ sXMLDeclEq() {
1241
+ const c = this.getCodeNorm();
1242
+ if (c === QUESTION) {
1243
+ this.state = S_XML_DECL_ENDING;
1244
+ this.fail("XML declaration is incomplete.");
1245
+ return;
1246
+ }
1247
+ if (isS(c)) {
1248
+ return;
1249
+ }
1250
+ if (c !== EQUAL) {
1251
+ this.fail("value required.");
1252
+ }
1253
+ this.state = S_XML_DECL_VALUE_START;
1254
+ }
1255
+ sXMLDeclValueStart() {
1256
+ const c = this.getCodeNorm();
1257
+ if (c === QUESTION) {
1258
+ this.state = S_XML_DECL_ENDING;
1259
+ this.fail("XML declaration is incomplete.");
1260
+ return;
1261
+ }
1262
+ if (isS(c)) {
1263
+ return;
1264
+ }
1265
+ if (!isQuote(c)) {
1266
+ this.fail("value must be quoted.");
1267
+ this.q = SPACE;
1268
+ } else {
1269
+ this.q = c;
1270
+ }
1271
+ this.state = S_XML_DECL_VALUE;
1272
+ }
1273
+ sXMLDeclValue() {
1274
+ const c = this.captureTo([this.q, QUESTION]);
1275
+ if (c === QUESTION) {
1276
+ this.state = S_XML_DECL_ENDING;
1277
+ this.text = "";
1278
+ this.fail("XML declaration is incomplete.");
1279
+ return;
1280
+ }
1281
+ if (c === EOC) {
1282
+ return;
1283
+ }
1284
+ const value = this.text;
1285
+ this.text = "";
1286
+ switch (this.name) {
1287
+ case "version": {
1288
+ this.xmlDeclExpects = ["encoding", "standalone"];
1289
+ const version = value;
1290
+ this.xmlDecl.version = version;
1291
+ if (!/^1\.[0-9]+$/.test(version)) {
1292
+ this.fail("version number must match /^1\\.[0-9]+$/.");
1293
+ } else if (!this.opt.forceXMLVersion) {
1294
+ this.setXMLVersion(version);
1295
+ }
1296
+ break;
1297
+ }
1298
+ case "encoding":
1299
+ if (!/^[A-Za-z][A-Za-z0-9._-]*$/.test(value)) {
1300
+ this.fail("encoding value must match /^[A-Za-z0-9][A-Za-z0-9._-]*$/.");
1301
+ }
1302
+ this.xmlDeclExpects = ["standalone"];
1303
+ this.xmlDecl.encoding = value;
1304
+ break;
1305
+ case "standalone":
1306
+ if (value !== "yes" && value !== "no") {
1307
+ this.fail('standalone value must match "yes" or "no".');
1308
+ }
1309
+ this.xmlDeclExpects = [];
1310
+ this.xmlDecl.standalone = value;
1311
+ break;
1312
+ default:
1313
+ }
1314
+ this.name = "";
1315
+ this.state = S_XML_DECL_SEPARATOR;
1316
+ }
1317
+ sXMLDeclSeparator() {
1318
+ const c = this.getCodeNorm();
1319
+ if (c === QUESTION) {
1320
+ this.state = S_XML_DECL_ENDING;
1321
+ return;
1322
+ }
1323
+ if (!isS(c)) {
1324
+ this.fail("whitespace required.");
1325
+ this.unget();
1326
+ }
1327
+ this.state = S_XML_DECL_NAME_START;
1328
+ }
1329
+ sXMLDeclEnding() {
1330
+ var _a;
1331
+ const c = this.getCodeNorm();
1332
+ if (c === GREATER) {
1333
+ if (this.piTarget !== "xml") {
1334
+ this.fail("processing instructions are not allowed before root.");
1335
+ } else if (this.name !== "version" && this.xmlDeclExpects.includes("version")) {
1336
+ this.fail("XML declaration must contain a version.");
1337
+ }
1338
+ (_a = this.xmldeclHandler) === null || _a === void 0 ? void 0 : _a.call(this, this.xmlDecl);
1339
+ this.name = "";
1340
+ this.piTarget = this.text = "";
1341
+ this.state = S_TEXT;
1342
+ } else {
1343
+ this.fail("The character ? is disallowed anywhere in XML declarations.");
1344
+ }
1345
+ this.xmlDeclPossible = false;
1346
+ }
1347
+ sOpenTag() {
1348
+ var _a;
1349
+ const c = this.captureNameChars();
1350
+ if (c === EOC) {
1351
+ return;
1352
+ }
1353
+ const tag = this.tag = {
1354
+ name: this.name,
1355
+ attributes: /* @__PURE__ */ Object.create(null)
1356
+ };
1357
+ this.name = "";
1358
+ if (this.xmlnsOpt) {
1359
+ this.topNS = tag.ns = /* @__PURE__ */ Object.create(null);
1360
+ }
1361
+ (_a = this.openTagStartHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag);
1362
+ this.sawRoot = true;
1363
+ if (!this.fragmentOpt && this.closedRoot) {
1364
+ this.fail("documents may contain only one root.");
1365
+ }
1366
+ switch (c) {
1367
+ case GREATER:
1368
+ this.openTag();
1369
+ break;
1370
+ case FORWARD_SLASH:
1371
+ this.state = S_OPEN_TAG_SLASH;
1372
+ break;
1373
+ default:
1374
+ if (!isS(c)) {
1375
+ this.fail("disallowed character in tag name.");
1376
+ }
1377
+ this.state = S_ATTRIB;
1378
+ }
1379
+ }
1380
+ sOpenTagSlash() {
1381
+ if (this.getCode() === GREATER) {
1382
+ this.openSelfClosingTag();
1383
+ } else {
1384
+ this.fail("forward-slash in opening tag not followed by >.");
1385
+ this.state = S_ATTRIB;
1386
+ }
1387
+ }
1388
+ sAttrib() {
1389
+ const c = this.skipSpaces();
1390
+ if (c === EOC) {
1391
+ return;
1392
+ }
1393
+ if (isNameStartChar(c)) {
1394
+ this.unget();
1395
+ this.state = S_ATTRIB_NAME;
1396
+ } else if (c === GREATER) {
1397
+ this.openTag();
1398
+ } else if (c === FORWARD_SLASH) {
1399
+ this.state = S_OPEN_TAG_SLASH;
1400
+ } else {
1401
+ this.fail("disallowed character in attribute name.");
1402
+ }
1403
+ }
1404
+ sAttribName() {
1405
+ const c = this.captureNameChars();
1406
+ if (c === EQUAL) {
1407
+ this.state = S_ATTRIB_VALUE;
1408
+ } else if (isS(c)) {
1409
+ this.state = S_ATTRIB_NAME_SAW_WHITE;
1410
+ } else if (c === GREATER) {
1411
+ this.fail("attribute without value.");
1412
+ this.pushAttrib(this.name, this.name);
1413
+ this.name = this.text = "";
1414
+ this.openTag();
1415
+ } else if (c !== EOC) {
1416
+ this.fail("disallowed character in attribute name.");
1417
+ }
1418
+ }
1419
+ sAttribNameSawWhite() {
1420
+ const c = this.skipSpaces();
1421
+ switch (c) {
1422
+ case EOC:
1423
+ return;
1424
+ case EQUAL:
1425
+ this.state = S_ATTRIB_VALUE;
1426
+ break;
1427
+ default:
1428
+ this.fail("attribute without value.");
1429
+ this.text = "";
1430
+ this.name = "";
1431
+ if (c === GREATER) {
1432
+ this.openTag();
1433
+ } else if (isNameStartChar(c)) {
1434
+ this.unget();
1435
+ this.state = S_ATTRIB_NAME;
1436
+ } else {
1437
+ this.fail("disallowed character in attribute name.");
1438
+ this.state = S_ATTRIB;
1439
+ }
1440
+ }
1441
+ }
1442
+ sAttribValue() {
1443
+ const c = this.getCodeNorm();
1444
+ if (isQuote(c)) {
1445
+ this.q = c;
1446
+ this.state = S_ATTRIB_VALUE_QUOTED;
1447
+ } else if (!isS(c)) {
1448
+ this.fail("unquoted attribute value.");
1449
+ this.state = S_ATTRIB_VALUE_UNQUOTED;
1450
+ this.unget();
1451
+ }
1452
+ }
1453
+ sAttribValueQuoted() {
1454
+ const { q, chunk } = this;
1455
+ let { i: start } = this;
1456
+ while (true) {
1457
+ switch (this.getCode()) {
1458
+ case q:
1459
+ this.pushAttrib(this.name, this.text + chunk.slice(start, this.prevI));
1460
+ this.name = this.text = "";
1461
+ this.q = null;
1462
+ this.state = S_ATTRIB_VALUE_CLOSED;
1463
+ return;
1464
+ case AMP:
1465
+ this.text += chunk.slice(start, this.prevI);
1466
+ this.state = S_ENTITY;
1467
+ this.entityReturnState = S_ATTRIB_VALUE_QUOTED;
1468
+ return;
1469
+ case NL:
1470
+ case NL_LIKE:
1471
+ case TAB:
1472
+ this.text += `${chunk.slice(start, this.prevI)} `;
1473
+ start = this.i;
1474
+ break;
1475
+ case LESS:
1476
+ this.text += chunk.slice(start, this.prevI);
1477
+ this.fail("disallowed character.");
1478
+ return;
1479
+ case EOC:
1480
+ this.text += chunk.slice(start);
1481
+ return;
1482
+ default:
1483
+ }
1484
+ }
1485
+ }
1486
+ sAttribValueClosed() {
1487
+ const c = this.getCodeNorm();
1488
+ if (isS(c)) {
1489
+ this.state = S_ATTRIB;
1490
+ } else if (c === GREATER) {
1491
+ this.openTag();
1492
+ } else if (c === FORWARD_SLASH) {
1493
+ this.state = S_OPEN_TAG_SLASH;
1494
+ } else if (isNameStartChar(c)) {
1495
+ this.fail("no whitespace between attributes.");
1496
+ this.unget();
1497
+ this.state = S_ATTRIB_NAME;
1498
+ } else {
1499
+ this.fail("disallowed character in attribute name.");
1500
+ }
1501
+ }
1502
+ sAttribValueUnquoted() {
1503
+ const c = this.captureTo(ATTRIB_VALUE_UNQUOTED_TERMINATOR);
1504
+ switch (c) {
1505
+ case AMP:
1506
+ this.state = S_ENTITY;
1507
+ this.entityReturnState = S_ATTRIB_VALUE_UNQUOTED;
1508
+ break;
1509
+ case LESS:
1510
+ this.fail("disallowed character.");
1511
+ break;
1512
+ case EOC:
1513
+ break;
1514
+ default:
1515
+ if (this.text.includes("]]>")) {
1516
+ this.fail('the string "]]>" is disallowed in char data.');
1517
+ }
1518
+ this.pushAttrib(this.name, this.text);
1519
+ this.name = this.text = "";
1520
+ if (c === GREATER) {
1521
+ this.openTag();
1522
+ } else {
1523
+ this.state = S_ATTRIB;
1524
+ }
1525
+ }
1526
+ }
1527
+ sCloseTag() {
1528
+ const c = this.captureNameChars();
1529
+ if (c === GREATER) {
1530
+ this.closeTag();
1531
+ } else if (isS(c)) {
1532
+ this.state = S_CLOSE_TAG_SAW_WHITE;
1533
+ } else if (c !== EOC) {
1534
+ this.fail("disallowed character in closing tag.");
1535
+ }
1536
+ }
1537
+ sCloseTagSawWhite() {
1538
+ switch (this.skipSpaces()) {
1539
+ case GREATER:
1540
+ this.closeTag();
1541
+ break;
1542
+ case EOC:
1543
+ break;
1544
+ default:
1545
+ this.fail("disallowed character in closing tag.");
1546
+ }
1547
+ }
1548
+ // END OF STATE ENGINE METHODS
1549
+ handleTextInRoot() {
1550
+ let { i: start, forbiddenState } = this;
1551
+ const { chunk, textHandler: handler } = this;
1552
+ scanLoop:
1553
+ while (true) {
1554
+ switch (this.getCode()) {
1555
+ case LESS: {
1556
+ this.state = S_OPEN_WAKA;
1557
+ if (handler !== void 0) {
1558
+ const { text } = this;
1559
+ const slice = chunk.slice(start, this.prevI);
1560
+ if (text.length !== 0) {
1561
+ handler(text + slice);
1562
+ this.text = "";
1563
+ } else if (slice.length !== 0) {
1564
+ handler(slice);
1565
+ }
1566
+ }
1567
+ forbiddenState = FORBIDDEN_START;
1568
+ break scanLoop;
1569
+ }
1570
+ case AMP:
1571
+ this.state = S_ENTITY;
1572
+ this.entityReturnState = S_TEXT;
1573
+ if (handler !== void 0) {
1574
+ this.text += chunk.slice(start, this.prevI);
1575
+ }
1576
+ forbiddenState = FORBIDDEN_START;
1577
+ break scanLoop;
1578
+ case CLOSE_BRACKET:
1579
+ switch (forbiddenState) {
1580
+ case FORBIDDEN_START:
1581
+ forbiddenState = FORBIDDEN_BRACKET;
1582
+ break;
1583
+ case FORBIDDEN_BRACKET:
1584
+ forbiddenState = FORBIDDEN_BRACKET_BRACKET;
1585
+ break;
1586
+ case FORBIDDEN_BRACKET_BRACKET:
1587
+ break;
1588
+ default:
1589
+ throw new Error("impossible state");
1590
+ }
1591
+ break;
1592
+ case GREATER:
1593
+ if (forbiddenState === FORBIDDEN_BRACKET_BRACKET) {
1594
+ this.fail('the string "]]>" is disallowed in char data.');
1595
+ }
1596
+ forbiddenState = FORBIDDEN_START;
1597
+ break;
1598
+ case NL_LIKE:
1599
+ if (handler !== void 0) {
1600
+ this.text += `${chunk.slice(start, this.prevI)}
1601
+ `;
1602
+ }
1603
+ start = this.i;
1604
+ forbiddenState = FORBIDDEN_START;
1605
+ break;
1606
+ case EOC:
1607
+ if (handler !== void 0) {
1608
+ this.text += chunk.slice(start);
1609
+ }
1610
+ break scanLoop;
1611
+ default:
1612
+ forbiddenState = FORBIDDEN_START;
1613
+ }
1614
+ }
1615
+ this.forbiddenState = forbiddenState;
1616
+ }
1617
+ handleTextOutsideRoot() {
1618
+ let { i: start } = this;
1619
+ const { chunk, textHandler: handler } = this;
1620
+ let nonSpace = false;
1621
+ outRootLoop:
1622
+ while (true) {
1623
+ const code = this.getCode();
1624
+ switch (code) {
1625
+ case LESS: {
1626
+ this.state = S_OPEN_WAKA;
1627
+ if (handler !== void 0) {
1628
+ const { text } = this;
1629
+ const slice = chunk.slice(start, this.prevI);
1630
+ if (text.length !== 0) {
1631
+ handler(text + slice);
1632
+ this.text = "";
1633
+ } else if (slice.length !== 0) {
1634
+ handler(slice);
1635
+ }
1636
+ }
1637
+ break outRootLoop;
1638
+ }
1639
+ case AMP:
1640
+ this.state = S_ENTITY;
1641
+ this.entityReturnState = S_TEXT;
1642
+ if (handler !== void 0) {
1643
+ this.text += chunk.slice(start, this.prevI);
1644
+ }
1645
+ nonSpace = true;
1646
+ break outRootLoop;
1647
+ case NL_LIKE:
1648
+ if (handler !== void 0) {
1649
+ this.text += `${chunk.slice(start, this.prevI)}
1650
+ `;
1651
+ }
1652
+ start = this.i;
1653
+ break;
1654
+ case EOC:
1655
+ if (handler !== void 0) {
1656
+ this.text += chunk.slice(start);
1657
+ }
1658
+ break outRootLoop;
1659
+ default:
1660
+ if (!isS(code)) {
1661
+ nonSpace = true;
1662
+ }
1663
+ }
1664
+ }
1665
+ if (!nonSpace) {
1666
+ return;
1667
+ }
1668
+ if (!this.sawRoot && !this.reportedTextBeforeRoot) {
1669
+ this.fail("text data outside of root node.");
1670
+ this.reportedTextBeforeRoot = true;
1671
+ }
1672
+ if (this.closedRoot && !this.reportedTextAfterRoot) {
1673
+ this.fail("text data outside of root node.");
1674
+ this.reportedTextAfterRoot = true;
1675
+ }
1676
+ }
1677
+ pushAttribNS(name, value) {
1678
+ var _a;
1679
+ const { prefix, local } = this.qname(name);
1680
+ const attr = { name, prefix, local, value };
1681
+ this.attribList.push(attr);
1682
+ (_a = this.attributeHandler) === null || _a === void 0 ? void 0 : _a.call(this, attr);
1683
+ if (prefix === "xmlns") {
1684
+ const trimmed = value.trim();
1685
+ if (this.currentXMLVersion === "1.0" && trimmed === "") {
1686
+ this.fail("invalid attempt to undefine prefix in XML 1.0");
1687
+ }
1688
+ this.topNS[local] = trimmed;
1689
+ nsPairCheck(this, local, trimmed);
1690
+ } else if (name === "xmlns") {
1691
+ const trimmed = value.trim();
1692
+ this.topNS[""] = trimmed;
1693
+ nsPairCheck(this, "", trimmed);
1694
+ }
1695
+ }
1696
+ pushAttribPlain(name, value) {
1697
+ var _a;
1698
+ const attr = { name, value };
1699
+ this.attribList.push(attr);
1700
+ (_a = this.attributeHandler) === null || _a === void 0 ? void 0 : _a.call(this, attr);
1701
+ }
1702
+ /**
1703
+ * End parsing. This performs final well-formedness checks and resets the
1704
+ * parser to a clean state.
1705
+ *
1706
+ * @returns this
1707
+ */
1708
+ end() {
1709
+ var _a, _b;
1710
+ if (!this.sawRoot) {
1711
+ this.fail("document must contain a root element.");
1712
+ }
1713
+ const { tags } = this;
1714
+ while (tags.length > 0) {
1715
+ const tag = tags.pop();
1716
+ this.fail(`unclosed tag: ${tag.name}`);
1717
+ }
1718
+ if (this.state !== S_BEGIN && this.state !== S_TEXT) {
1719
+ this.fail("unexpected end.");
1720
+ }
1721
+ const { text } = this;
1722
+ if (text.length !== 0) {
1723
+ (_a = this.textHandler) === null || _a === void 0 ? void 0 : _a.call(this, text);
1724
+ this.text = "";
1725
+ }
1726
+ this._closed = true;
1727
+ (_b = this.endHandler) === null || _b === void 0 ? void 0 : _b.call(this);
1728
+ this._init();
1729
+ return this;
1730
+ }
1731
+ /**
1732
+ * Resolve a namespace prefix.
1733
+ *
1734
+ * @param prefix The prefix to resolve.
1735
+ *
1736
+ * @returns The namespace URI or ``undefined`` if the prefix is not defined.
1737
+ */
1738
+ resolve(prefix) {
1739
+ var _a, _b;
1740
+ let uri = this.topNS[prefix];
1741
+ if (uri !== void 0) {
1742
+ return uri;
1743
+ }
1744
+ const { tags } = this;
1745
+ for (let index = tags.length - 1; index >= 0; index--) {
1746
+ uri = tags[index].ns[prefix];
1747
+ if (uri !== void 0) {
1748
+ return uri;
1749
+ }
1750
+ }
1751
+ uri = this.ns[prefix];
1752
+ if (uri !== void 0) {
1753
+ return uri;
1754
+ }
1755
+ return (_b = (_a = this.opt).resolvePrefix) === null || _b === void 0 ? void 0 : _b.call(_a, prefix);
1756
+ }
1757
+ /**
1758
+ * Parse a qname into its prefix and local name parts.
1759
+ *
1760
+ * @param name The name to parse
1761
+ *
1762
+ * @returns
1763
+ */
1764
+ qname(name) {
1765
+ const colon = name.indexOf(":");
1766
+ if (colon === -1) {
1767
+ return { prefix: "", local: name };
1768
+ }
1769
+ const local = name.slice(colon + 1);
1770
+ const prefix = name.slice(0, colon);
1771
+ if (prefix === "" || local === "" || local.includes(":")) {
1772
+ this.fail(`malformed name: ${name}.`);
1773
+ }
1774
+ return { prefix, local };
1775
+ }
1776
+ processAttribsNS() {
1777
+ var _a;
1778
+ const { attribList } = this;
1779
+ const tag = this.tag;
1780
+ {
1781
+ const { prefix, local } = this.qname(tag.name);
1782
+ tag.prefix = prefix;
1783
+ tag.local = local;
1784
+ const uri = tag.uri = (_a = this.resolve(prefix)) !== null && _a !== void 0 ? _a : "";
1785
+ if (prefix !== "") {
1786
+ if (prefix === "xmlns") {
1787
+ this.fail('tags may not have "xmlns" as prefix.');
1788
+ }
1789
+ if (uri === "") {
1790
+ this.fail(`unbound namespace prefix: ${JSON.stringify(prefix)}.`);
1791
+ tag.uri = prefix;
1792
+ }
1793
+ }
1794
+ }
1795
+ if (attribList.length === 0) {
1796
+ return;
1797
+ }
1798
+ const { attributes } = tag;
1799
+ const seen = /* @__PURE__ */ new Set();
1800
+ for (const attr of attribList) {
1801
+ const { name, prefix, local } = attr;
1802
+ let uri;
1803
+ let eqname;
1804
+ if (prefix === "") {
1805
+ uri = name === "xmlns" ? XMLNS_NAMESPACE : "";
1806
+ eqname = name;
1807
+ } else {
1808
+ uri = this.resolve(prefix);
1809
+ if (uri === void 0) {
1810
+ this.fail(`unbound namespace prefix: ${JSON.stringify(prefix)}.`);
1811
+ uri = prefix;
1812
+ }
1813
+ eqname = `{${uri}}${local}`;
1814
+ }
1815
+ if (seen.has(eqname)) {
1816
+ this.fail(`duplicate attribute: ${eqname}.`);
1817
+ }
1818
+ seen.add(eqname);
1819
+ attr.uri = uri;
1820
+ attributes[name] = attr;
1821
+ }
1822
+ this.attribList = [];
1823
+ }
1824
+ processAttribsPlain() {
1825
+ const { attribList } = this;
1826
+ const attributes = this.tag.attributes;
1827
+ for (const { name, value } of attribList) {
1828
+ if (attributes[name] !== void 0) {
1829
+ this.fail(`duplicate attribute: ${name}.`);
1830
+ }
1831
+ attributes[name] = value;
1832
+ }
1833
+ this.attribList = [];
1834
+ }
1835
+ /**
1836
+ * Handle a complete open tag. This parser code calls this once it has seen
1837
+ * the whole tag. This method checks for well-formeness and then emits
1838
+ * ``onopentag``.
1839
+ */
1840
+ openTag() {
1841
+ var _a;
1842
+ this.processAttribs();
1843
+ const { tags } = this;
1844
+ const tag = this.tag;
1845
+ tag.isSelfClosing = false;
1846
+ (_a = this.openTagHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag);
1847
+ tags.push(tag);
1848
+ this.state = S_TEXT;
1849
+ this.name = "";
1850
+ }
1851
+ /**
1852
+ * Handle a complete self-closing tag. This parser code calls this once it has
1853
+ * seen the whole tag. This method checks for well-formeness and then emits
1854
+ * ``onopentag`` and ``onclosetag``.
1855
+ */
1856
+ openSelfClosingTag() {
1857
+ var _a, _b, _c;
1858
+ this.processAttribs();
1859
+ const { tags } = this;
1860
+ const tag = this.tag;
1861
+ tag.isSelfClosing = true;
1862
+ (_a = this.openTagHandler) === null || _a === void 0 ? void 0 : _a.call(this, tag);
1863
+ (_b = this.closeTagHandler) === null || _b === void 0 ? void 0 : _b.call(this, tag);
1864
+ const top = this.tag = (_c = tags[tags.length - 1]) !== null && _c !== void 0 ? _c : null;
1865
+ if (top === null) {
1866
+ this.closedRoot = true;
1867
+ }
1868
+ this.state = S_TEXT;
1869
+ this.name = "";
1870
+ }
1871
+ /**
1872
+ * Handle a complete close tag. This parser code calls this once it has seen
1873
+ * the whole tag. This method checks for well-formeness and then emits
1874
+ * ``onclosetag``.
1875
+ */
1876
+ closeTag() {
1877
+ const { tags, name } = this;
1878
+ this.state = S_TEXT;
1879
+ this.name = "";
1880
+ if (name === "") {
1881
+ this.fail("weird empty close tag.");
1882
+ this.text += "</>";
1883
+ return;
1884
+ }
1885
+ const handler = this.closeTagHandler;
1886
+ let l = tags.length;
1887
+ while (l-- > 0) {
1888
+ const tag = this.tag = tags.pop();
1889
+ this.topNS = tag.ns;
1890
+ handler === null || handler === void 0 ? void 0 : handler(tag);
1891
+ if (tag.name === name) {
1892
+ break;
1893
+ }
1894
+ this.fail("unexpected close tag.");
1895
+ }
1896
+ if (l === 0) {
1897
+ this.closedRoot = true;
1898
+ } else if (l < 0) {
1899
+ this.fail(`unmatched closing tag: ${name}.`);
1900
+ this.text += `</${name}>`;
1901
+ }
1902
+ }
1903
+ /**
1904
+ * Resolves an entity. Makes any necessary well-formedness checks.
1905
+ *
1906
+ * @param entity The entity to resolve.
1907
+ *
1908
+ * @returns The parsed entity.
1909
+ */
1910
+ parseEntity(entity) {
1911
+ if (entity[0] !== "#") {
1912
+ const defined = this.ENTITIES[entity];
1913
+ if (defined !== void 0) {
1914
+ return defined;
1915
+ }
1916
+ this.fail(this.isName(entity) ? "undefined entity." : "disallowed character in entity name.");
1917
+ return `&${entity};`;
1918
+ }
1919
+ let num = NaN;
1920
+ if (entity[1] === "x" && /^#x[0-9a-f]+$/i.test(entity)) {
1921
+ num = parseInt(entity.slice(2), 16);
1922
+ } else if (/^#[0-9]+$/.test(entity)) {
1923
+ num = parseInt(entity.slice(1), 10);
1924
+ }
1925
+ if (!this.isChar(num)) {
1926
+ this.fail("malformed character entity.");
1927
+ return `&${entity};`;
1928
+ }
1929
+ return String.fromCodePoint(num);
1930
+ }
1931
+ };
1932
+ exports.SaxesParser = SaxesParser2;
1933
+ }
1934
+ });
2
1935
 
3
1936
  // ../core/dist/server.js
4
1937
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
@@ -29,7 +1962,6 @@ function toPublic(inst) {
29
1962
  pluginVersion: inst.pluginVersion,
30
1963
  pluginVariant: inst.pluginVariant,
31
1964
  serverVersion: inst.serverVersion,
32
- versionMismatch: inst.versionMismatch,
33
1965
  lastActivity: inst.lastActivity,
34
1966
  connectedAt: inst.connectedAt
35
1967
  };
@@ -37,8 +1969,8 @@ function toPublic(inst) {
37
1969
  var STALE_INSTANCE_MS = 3e4;
38
1970
  var DUPLICATE_TAKEOVER_MS = 3e3;
39
1971
  var INSTANCE_ALIAS_TTL_MS = 5 * 60 * 1e3;
40
- var DEFAULT_POLL_WAIT_TIMEOUT_MS = 15e3;
41
- var MAX_ACTIVE_POLLS = 128;
1972
+ var ACCEPTED_REQUEST_TOMBSTONE_TTL_MS = 6e4;
1973
+ var MAX_ACCEPTED_REQUEST_TOMBSTONES = 4096;
42
1974
  function publishedInstanceId(placeId) {
43
1975
  if (placeId === void 0 || !Number.isFinite(placeId) || placeId <= 0)
44
1976
  return void 0;
@@ -46,11 +1978,14 @@ function publishedInstanceId(placeId) {
46
1978
  }
47
1979
  var BridgeService = class {
48
1980
  pendingRequests = /* @__PURE__ */ new Map();
1981
+ acceptedRequestIds = /* @__PURE__ */ new Map();
49
1982
  // Keyed by pluginSessionId (the per-plugin GUID).
50
1983
  instances = /* @__PURE__ */ new Map();
51
1984
  instanceAliases = /* @__PURE__ */ new Map();
52
1985
  instanceRegisteredListeners = /* @__PURE__ */ new Set();
53
- pendingPolls = /* @__PURE__ */ new Map();
1986
+ requestAvailableListeners = /* @__PURE__ */ new Set();
1987
+ sessionClosedListeners = /* @__PURE__ */ new Set();
1988
+ deliveryOwnersByPhysicalSession = /* @__PURE__ */ new Map();
54
1989
  requestTimeout = 3e4;
55
1990
  onInstanceRegistered(listener) {
56
1991
  this.instanceRegisteredListeners.add(listener);
@@ -70,6 +2005,47 @@ var BridgeService = class {
70
2005
  }
71
2006
  }
72
2007
  }
2008
+ onRequestAvailable(listener) {
2009
+ this.requestAvailableListeners.add(listener);
2010
+ return () => this.requestAvailableListeners.delete(listener);
2011
+ }
2012
+ onSessionClosed(listener) {
2013
+ this.sessionClosedListeners.add(listener);
2014
+ return () => this.sessionClosedListeners.delete(listener);
2015
+ }
2016
+ setDeliveryActive(physicalSessionId, owner, active) {
2017
+ let owners = this.deliveryOwnersByPhysicalSession.get(physicalSessionId);
2018
+ if (active) {
2019
+ if (!owners) {
2020
+ owners = /* @__PURE__ */ new Set();
2021
+ this.deliveryOwnersByPhysicalSession.set(physicalSessionId, owners);
2022
+ }
2023
+ owners.add(owner);
2024
+ return;
2025
+ }
2026
+ if (!owners)
2027
+ return;
2028
+ owners.delete(owner);
2029
+ if (owners.size === 0)
2030
+ this.deliveryOwnersByPhysicalSession.delete(physicalSessionId);
2031
+ }
2032
+ notifyRequestAvailable(physicalSessionId) {
2033
+ for (const listener of this.requestAvailableListeners) {
2034
+ try {
2035
+ listener(physicalSessionId);
2036
+ } catch {
2037
+ }
2038
+ }
2039
+ }
2040
+ physicalSessionsForTarget(targetInstanceId, targetRole) {
2041
+ const physicalSessionIds = /* @__PURE__ */ new Set();
2042
+ for (const instance of this.instances.values()) {
2043
+ if (instance.instanceId === targetInstanceId && instance.role === targetRole) {
2044
+ physicalSessionIds.add(instance.physicalSessionId);
2045
+ }
2046
+ }
2047
+ return Array.from(physicalSessionIds);
2048
+ }
73
2049
  canonicalInstanceId(instanceId, placeId) {
74
2050
  return publishedInstanceId(placeId) ?? instanceId;
75
2051
  }
@@ -140,7 +2116,6 @@ var BridgeService = class {
140
2116
  const pluginVersion = input.pluginVersion ?? "";
141
2117
  const pluginVariant = input.pluginVariant ?? "unknown";
142
2118
  const serverVersion = input.serverVersion ?? "";
143
- const versionMismatch = pluginVersion !== "" && serverVersion !== "" && pluginVersion !== serverVersion;
144
2119
  this.rememberInstanceAlias(rawInstanceId, instanceId);
145
2120
  if (prior && prior.instanceId !== instanceId) {
146
2121
  this.rememberInstanceAlias(prior.instanceId, instanceId);
@@ -166,7 +2141,8 @@ var BridgeService = class {
166
2141
  }
167
2142
  const existing = Array.from(this.instances.values()).find((i) => i.instanceId === instanceId && i.role === assignedRole && i.pluginSessionId !== pluginSessionId);
168
2143
  if (existing) {
169
- if (!this.pendingPolls.has(existing.pluginSessionId) && Date.now() - existing.lastActivity > DUPLICATE_TAKEOVER_MS) {
2144
+ const existingDeliveryActive = (this.deliveryOwnersByPhysicalSession.get(existing.physicalSessionId)?.size ?? 0) > 0;
2145
+ if (!existingDeliveryActive && Date.now() - existing.lastActivity > DUPLICATE_TAKEOVER_MS) {
170
2146
  this.unregisterInstance(existing.pluginSessionId);
171
2147
  } else {
172
2148
  return {
@@ -181,6 +2157,7 @@ var BridgeService = class {
181
2157
  }
182
2158
  const registered = {
183
2159
  pluginSessionId,
2160
+ physicalSessionId: input.physicalSessionId,
184
2161
  instanceId,
185
2162
  role: assignedRole,
186
2163
  placeId: input.placeId ?? 0,
@@ -190,24 +2167,35 @@ var BridgeService = class {
190
2167
  pluginVersion,
191
2168
  pluginVariant,
192
2169
  serverVersion,
193
- versionMismatch,
194
2170
  lastActivity: Date.now(),
195
2171
  connectedAt: prior?.connectedAt ?? Date.now()
196
2172
  };
197
2173
  this.instances.set(pluginSessionId, registered);
198
2174
  this.notifyInstanceRegistered(toPublic(registered));
199
- this.dispatchPendingPolls(instanceId, assignedRole);
2175
+ this.notifyRequestAvailable(registered.physicalSessionId);
200
2176
  return { ok: true, assignedRole, instanceId };
201
2177
  }
202
2178
  unregisterInstance(pluginSessionId) {
203
2179
  const removed = this.instances.get(pluginSessionId);
204
- this.instances.delete(pluginSessionId);
205
- const pendingPoll = this.pendingPolls.get(pluginSessionId);
206
- if (pendingPoll) {
207
- this.settlePendingPoll(pendingPoll, { kind: "session_closed" });
208
- }
209
2180
  if (!removed)
210
2181
  return;
2182
+ const logicalChildren = removed.physicalSessionId === pluginSessionId ? Array.from(this.instances.values()).filter((instance) => instance.pluginSessionId !== pluginSessionId && instance.physicalSessionId === pluginSessionId) : [];
2183
+ this.instances.delete(pluginSessionId);
2184
+ const session = {
2185
+ logicalSessionId: pluginSessionId,
2186
+ physicalSessionId: removed.physicalSessionId
2187
+ };
2188
+ for (const listener of this.sessionClosedListeners) {
2189
+ try {
2190
+ listener(session);
2191
+ } catch {
2192
+ }
2193
+ }
2194
+ if (removed.physicalSessionId === pluginSessionId) {
2195
+ this.deliveryOwnersByPhysicalSession.delete(pluginSessionId);
2196
+ }
2197
+ for (const child of logicalChildren)
2198
+ this.unregisterInstance(child.pluginSessionId);
211
2199
  for (const [id, req] of this.pendingRequests.entries()) {
212
2200
  const stillHasHandler = Array.from(this.instances.values()).some((i) => i.instanceId === req.targetInstanceId && i.role === req.targetRole);
213
2201
  if (!stillHasHandler) {
@@ -241,9 +2229,14 @@ var BridgeService = class {
241
2229
  return this.pendingRequests.size;
242
2230
  }
243
2231
  updateInstanceActivity(pluginSessionId) {
244
- const inst = this.instances.get(pluginSessionId);
245
- if (inst) {
246
- inst.lastActivity = Date.now();
2232
+ const instance = this.instances.get(pluginSessionId);
2233
+ if (!instance)
2234
+ return;
2235
+ const now = Date.now();
2236
+ for (const candidate of this.instances.values()) {
2237
+ if (candidate.physicalSessionId === instance.physicalSessionId) {
2238
+ candidate.lastActivity = now;
2239
+ }
247
2240
  }
248
2241
  }
249
2242
  updateInstanceMetadata(pluginSessionId, metadata) {
@@ -266,14 +2259,15 @@ var BridgeService = class {
266
2259
  this.rememberInstanceAlias(priorInstanceId, canonicalInstanceId);
267
2260
  this.migratePendingRequests(priorInstanceId, canonicalInstanceId);
268
2261
  inst.instanceId = canonicalInstanceId;
269
- this.dispatchPendingPolls(canonicalInstanceId, inst.role);
2262
+ this.notifyRequestAvailable(inst.physicalSessionId);
270
2263
  }
271
2264
  }
272
2265
  }
273
2266
  cleanupStaleInstances() {
274
2267
  const now = Date.now();
275
2268
  for (const [id, inst] of this.instances.entries()) {
276
- if (!this.pendingPolls.has(id) && now - inst.lastActivity > STALE_INSTANCE_MS) {
2269
+ const deliveryActive = (this.deliveryOwnersByPhysicalSession.get(inst.physicalSessionId)?.size ?? 0) > 0;
2270
+ if (!deliveryActive && now - inst.lastActivity > STALE_INSTANCE_MS) {
277
2271
  this.unregisterInstance(id);
278
2272
  }
279
2273
  }
@@ -389,9 +2383,9 @@ var BridgeService = class {
389
2383
  };
390
2384
  }
391
2385
  if (distinctInstanceIds.size > 1) {
392
- const errorCode = role ? "ambiguous_target" : "multiple_instances_connected";
2386
+ const errorCode2 = role ? "ambiguous_target" : "multiple_instances_connected";
393
2387
  const msg = role ? `target=${role} is ambiguous because multiple Studio places are connected. Pass instance_id to choose a place.` : "Multiple Studio places are connected. Pass instance_id to disambiguate.";
394
- return { ok: false, error: { code: errorCode, message: msg, data: errorData } };
2388
+ return { ok: false, error: { code: errorCode2, message: msg, data: errorData } };
395
2389
  }
396
2390
  const onlyInstanceId = distinctInstanceIds.values().next().value;
397
2391
  return this.resolveTarget({ instance_id: onlyInstanceId, target });
@@ -413,138 +2407,93 @@ var BridgeService = class {
413
2407
  targetInstanceId,
414
2408
  targetRole,
415
2409
  timestamp: Date.now(),
416
- inFlight: false,
417
2410
  resolve: resolve5,
418
2411
  reject,
419
2412
  timeoutId,
420
2413
  timeoutMs: effectiveTimeoutMs
421
2414
  };
422
2415
  this.pendingRequests.set(requestId, request);
423
- this.dispatchPendingPolls(targetInstanceId, targetRole);
2416
+ for (const physicalSessionId of this.physicalSessionsForTarget(targetInstanceId, targetRole)) {
2417
+ this.notifyRequestAvailable(physicalSessionId);
2418
+ }
424
2419
  });
425
2420
  }
426
- getPendingRequest(callerInstanceId, callerRole) {
427
- let oldestRequest = null;
2421
+ claimNextRequestForPhysical(physicalSessionId, claimOwner) {
2422
+ let oldestRequest;
2423
+ let logicalSessionId = "";
428
2424
  for (const request of this.pendingRequests.values()) {
429
- if (request.targetInstanceId !== callerInstanceId)
430
- continue;
431
- if (request.targetRole !== callerRole)
2425
+ if (request.claimOwner !== void 0)
432
2426
  continue;
433
- if (request.inFlight)
2427
+ let matchingSessionId;
2428
+ for (const candidate of this.instances.values()) {
2429
+ if (candidate.physicalSessionId === physicalSessionId && candidate.instanceId === request.targetInstanceId && candidate.role === request.targetRole) {
2430
+ matchingSessionId = candidate.pluginSessionId;
2431
+ break;
2432
+ }
2433
+ }
2434
+ if (!matchingSessionId)
434
2435
  continue;
435
2436
  if (!oldestRequest || request.timestamp < oldestRequest.timestamp) {
436
2437
  oldestRequest = request;
2438
+ logicalSessionId = matchingSessionId;
437
2439
  }
438
2440
  }
439
- if (oldestRequest) {
440
- oldestRequest.inFlight = true;
441
- return {
442
- requestId: oldestRequest.id,
443
- request: {
444
- endpoint: oldestRequest.endpoint,
445
- data: oldestRequest.data
446
- }
447
- };
448
- }
449
- return null;
450
- }
451
- waitForPendingRequest(pluginSessionId, options = {}) {
452
- const instance = this.instances.get(pluginSessionId);
453
- if (!instance) {
454
- return Promise.resolve({ kind: "unknown_session" });
455
- }
456
- if (options.signal?.aborted) {
457
- return Promise.resolve({ kind: "aborted" });
458
- }
459
- this.updateInstanceActivity(pluginSessionId);
460
- const existingPoll = this.pendingPolls.get(pluginSessionId);
461
- if (existingPoll) {
462
- this.settlePendingPoll(existingPoll, { kind: "superseded" });
463
- }
464
- const pendingRequest = this.getPendingRequest(instance.instanceId, instance.role);
465
- if (pendingRequest) {
466
- return Promise.resolve({
467
- kind: "request",
468
- requestId: pendingRequest.requestId,
469
- request: pendingRequest.request
470
- });
471
- }
472
- if (this.pendingPolls.size >= MAX_ACTIVE_POLLS) {
473
- return Promise.resolve({ kind: "capacity" });
474
- }
475
- const timeoutMs = Math.min(DEFAULT_POLL_WAIT_TIMEOUT_MS, Math.max(1, options.timeoutMs ?? DEFAULT_POLL_WAIT_TIMEOUT_MS));
476
- let resolvePoll;
477
- const promise = new Promise((resolve5) => {
478
- resolvePoll = resolve5;
479
- });
480
- let waiter;
481
- const timeoutId = setTimeout(() => {
482
- this.settlePendingPoll(waiter, { kind: "timeout" }, true);
483
- }, timeoutMs);
484
- timeoutId.unref();
485
- waiter = {
486
- pluginSessionId,
487
- timeoutId,
488
- signal: options.signal,
489
- resolve: resolvePoll
2441
+ if (!oldestRequest)
2442
+ return null;
2443
+ oldestRequest.claimOwner = claimOwner;
2444
+ return {
2445
+ requestId: oldestRequest.id,
2446
+ logicalSessionId,
2447
+ target: oldestRequest.targetRole,
2448
+ endpoint: oldestRequest.endpoint,
2449
+ data: oldestRequest.data
490
2450
  };
491
- if (options.signal) {
492
- waiter.onAbort = () => {
493
- this.settlePendingPoll(waiter, { kind: "aborted" });
494
- };
495
- options.signal.addEventListener("abort", waiter.onAbort, { once: true });
496
- }
497
- this.pendingPolls.set(pluginSessionId, waiter);
498
- return promise;
499
2451
  }
500
- dispatchPendingPolls(targetInstanceId, targetRole) {
501
- for (const waiter of Array.from(this.pendingPolls.values())) {
502
- const instance = this.instances.get(waiter.pluginSessionId);
503
- if (!instance) {
504
- this.settlePendingPoll(waiter, { kind: "session_closed" });
2452
+ releaseDeliveryClaims(claimOwner) {
2453
+ const physicalSessionIds = /* @__PURE__ */ new Set();
2454
+ for (const request of this.pendingRequests.values()) {
2455
+ if (request.claimOwner !== claimOwner)
505
2456
  continue;
2457
+ request.claimOwner = void 0;
2458
+ for (const physicalSessionId of this.physicalSessionsForTarget(request.targetInstanceId, request.targetRole)) {
2459
+ physicalSessionIds.add(physicalSessionId);
506
2460
  }
507
- if (targetInstanceId !== void 0 && instance.instanceId !== targetInstanceId)
508
- continue;
509
- if (targetRole !== void 0 && instance.role !== targetRole)
510
- continue;
511
- const pendingRequest = this.getPendingRequest(instance.instanceId, instance.role);
512
- if (!pendingRequest)
513
- continue;
514
- this.settlePendingPoll(waiter, {
515
- kind: "request",
516
- requestId: pendingRequest.requestId,
517
- request: pendingRequest.request
518
- }, true);
519
- }
520
- }
521
- settlePendingPoll(waiter, result, refreshActivity = false) {
522
- if (this.pendingPolls.get(waiter.pluginSessionId) !== waiter)
523
- return;
524
- this.pendingPolls.delete(waiter.pluginSessionId);
525
- clearTimeout(waiter.timeoutId);
526
- if (waiter.signal && waiter.onAbort) {
527
- waiter.signal.removeEventListener("abort", waiter.onAbort);
528
2461
  }
529
- if (refreshActivity) {
530
- this.updateInstanceActivity(waiter.pluginSessionId);
2462
+ for (const physicalSessionId of physicalSessionIds) {
2463
+ this.notifyRequestAvailable(physicalSessionId);
531
2464
  }
532
- waiter.resolve(result);
533
2465
  }
534
2466
  resolveRequest(requestId, response) {
535
- const request = this.pendingRequests.get(requestId);
536
- if (request) {
537
- clearTimeout(request.timeoutId);
538
- this.pendingRequests.delete(requestId);
539
- request.resolve(response);
540
- }
2467
+ return this.settleRequest(requestId, (request) => request.resolve(response));
541
2468
  }
542
2469
  rejectRequest(requestId, error) {
2470
+ return this.settleRequest(requestId, (request) => request.reject(error));
2471
+ }
2472
+ settleRequest(requestId, settle) {
2473
+ const now = Date.now();
2474
+ this.pruneAcceptedRequestIds(now);
543
2475
  const request = this.pendingRequests.get(requestId);
544
- if (request) {
545
- clearTimeout(request.timeoutId);
546
- this.pendingRequests.delete(requestId);
547
- request.reject(error);
2476
+ if (!request) {
2477
+ return this.acceptedRequestIds.has(requestId) ? "already_settled" : "unknown";
2478
+ }
2479
+ clearTimeout(request.timeoutId);
2480
+ this.pendingRequests.delete(requestId);
2481
+ this.acceptedRequestIds.set(requestId, now);
2482
+ this.pruneAcceptedRequestIds(now);
2483
+ settle(request);
2484
+ return "accepted";
2485
+ }
2486
+ pruneAcceptedRequestIds(now) {
2487
+ for (const [requestId, acceptedAt] of this.acceptedRequestIds) {
2488
+ if (now - acceptedAt < ACCEPTED_REQUEST_TOMBSTONE_TTL_MS)
2489
+ break;
2490
+ this.acceptedRequestIds.delete(requestId);
2491
+ }
2492
+ while (this.acceptedRequestIds.size > MAX_ACCEPTED_REQUEST_TOMBSTONES) {
2493
+ const oldestRequestId = this.acceptedRequestIds.keys().next().value;
2494
+ if (oldestRequestId === void 0)
2495
+ break;
2496
+ this.acceptedRequestIds.delete(oldestRequestId);
548
2497
  }
549
2498
  }
550
2499
  cleanupOldRequests() {
@@ -563,9 +2512,6 @@ var BridgeService = class {
563
2512
  request.reject(new Error("Connection closed"));
564
2513
  }
565
2514
  this.pendingRequests.clear();
566
- for (const waiter of Array.from(this.pendingPolls.values())) {
567
- this.settlePendingPoll(waiter, { kind: "bridge_closed" });
568
- }
569
2515
  }
570
2516
  };
571
2517
 
@@ -3377,8 +5323,7 @@ var INTERNAL_RESULT_KEYS = /* @__PURE__ */ new Set([
3377
5323
  "pluginVariant",
3378
5324
  "pluginVersion",
3379
5325
  "requestId",
3380
- "serverVersion",
3381
- "versionMismatch"
5326
+ "serverVersion"
3382
5327
  ]);
3383
5328
  var TEXT_RESULT_TOOLS = /* @__PURE__ */ new Set(["get_roblox_docs"]);
3384
5329
  var SIDE_EFFECTING_READ_TOOLS = /* @__PURE__ */ new Set([
@@ -3656,6 +5601,182 @@ function tokensMatch(provided, expected) {
3656
5601
  return timingSafeEqual(a, b);
3657
5602
  }
3658
5603
 
5604
+ // ../core/dist/studio-transport.js
5605
+ var HEARTBEAT_INTERVAL_MS = 1e4;
5606
+ var MAX_ACTIVE_EVENT_STREAMS = 64;
5607
+ var SseStudioTransport = class {
5608
+ queue;
5609
+ streams = /* @__PURE__ */ new Map();
5610
+ unsubscribeRequestAvailable;
5611
+ unsubscribeSessionClosed;
5612
+ nextGeneration = 0;
5613
+ constructor(queue) {
5614
+ this.queue = queue;
5615
+ this.unsubscribeRequestAvailable = queue.onRequestAvailable((physicalSessionId) => {
5616
+ const stream = this.streams.get(physicalSessionId);
5617
+ if (stream)
5618
+ this.pump(stream);
5619
+ });
5620
+ this.unsubscribeSessionClosed = queue.onSessionClosed((route) => {
5621
+ if (route.logicalSessionId === route.physicalSessionId) {
5622
+ this.closePhysical(route.physicalSessionId);
5623
+ }
5624
+ });
5625
+ }
5626
+ get activeStreamCount() {
5627
+ return this.streams.size;
5628
+ }
5629
+ canOpen(physicalSessionId) {
5630
+ return this.streams.has(physicalSessionId) || this.streams.size < MAX_ACTIVE_EVENT_STREAMS;
5631
+ }
5632
+ open(physicalSessionId, sink, status) {
5633
+ if (!this.canOpen(physicalSessionId))
5634
+ return void 0;
5635
+ this.nextGeneration += 1;
5636
+ const claimOwner = `sse:${physicalSessionId}:${this.nextGeneration}`;
5637
+ const stream = {
5638
+ physicalSessionId,
5639
+ claimOwner,
5640
+ sink,
5641
+ status,
5642
+ closed: false,
5643
+ blocked: false,
5644
+ statusPending: true,
5645
+ onClose: () => this.closeStream(stream),
5646
+ onDrain: () => {
5647
+ if (stream.closed)
5648
+ return;
5649
+ stream.blocked = false;
5650
+ this.pump(stream);
5651
+ }
5652
+ };
5653
+ this.queue.setDeliveryActive(physicalSessionId, claimOwner, true);
5654
+ this.queue.updateInstanceActivity(physicalSessionId);
5655
+ const replaced = this.streams.get(physicalSessionId);
5656
+ if (replaced)
5657
+ this.closeStream(replaced, true);
5658
+ this.streams.set(physicalSessionId, stream);
5659
+ sink.on("close", stream.onClose);
5660
+ sink.on("error", stream.onClose);
5661
+ sink.on("drain", stream.onDrain);
5662
+ stream.heartbeatTimer = setInterval(() => {
5663
+ if (!stream.closed && !stream.blocked) {
5664
+ this.queue.updateInstanceActivity(physicalSessionId);
5665
+ stream.statusPending = true;
5666
+ this.pump(stream);
5667
+ if (!stream.blocked) {
5668
+ this.write(stream, { kind: "heartbeat", timestamp: Date.now() });
5669
+ }
5670
+ }
5671
+ }, HEARTBEAT_INTERVAL_MS);
5672
+ stream.heartbeatTimer.unref();
5673
+ this.pump(stream);
5674
+ return {
5675
+ physicalSessionId,
5676
+ close: () => this.closeStream(stream, true)
5677
+ };
5678
+ }
5679
+ refreshStatus(physicalSessionId) {
5680
+ if (physicalSessionId !== void 0) {
5681
+ const stream = this.streams.get(physicalSessionId);
5682
+ if (stream) {
5683
+ stream.lastStatusJson = void 0;
5684
+ stream.statusPending = true;
5685
+ this.pump(stream);
5686
+ }
5687
+ return;
5688
+ }
5689
+ for (const stream of this.streams.values()) {
5690
+ stream.lastStatusJson = void 0;
5691
+ stream.statusPending = true;
5692
+ this.pump(stream);
5693
+ }
5694
+ }
5695
+ closePhysical(physicalSessionId) {
5696
+ const stream = this.streams.get(physicalSessionId);
5697
+ if (stream)
5698
+ this.closeStream(stream, true);
5699
+ }
5700
+ close() {
5701
+ for (const stream of Array.from(this.streams.values())) {
5702
+ this.closeStream(stream, true);
5703
+ }
5704
+ this.unsubscribeRequestAvailable();
5705
+ this.unsubscribeSessionClosed();
5706
+ }
5707
+ pump(stream) {
5708
+ if (stream.closed || stream.blocked || this.streams.get(stream.physicalSessionId) !== stream)
5709
+ return;
5710
+ if (stream.statusPending) {
5711
+ stream.statusPending = false;
5712
+ let status;
5713
+ try {
5714
+ status = stream.status();
5715
+ } catch {
5716
+ this.closeStream(stream);
5717
+ return;
5718
+ }
5719
+ const statusJson = JSON.stringify(status);
5720
+ if (statusJson !== stream.lastStatusJson) {
5721
+ stream.lastStatusJson = statusJson;
5722
+ if (!this.write(stream, status))
5723
+ return;
5724
+ }
5725
+ }
5726
+ while (!stream.closed && !stream.blocked) {
5727
+ const request = this.queue.claimNextRequestForPhysical(stream.physicalSessionId, stream.claimOwner);
5728
+ if (!request)
5729
+ return;
5730
+ const event = {
5731
+ kind: "request",
5732
+ requestId: request.requestId,
5733
+ logicalSessionId: request.logicalSessionId,
5734
+ target: request.target,
5735
+ endpoint: request.endpoint,
5736
+ data: request.data === void 0 ? null : request.data
5737
+ };
5738
+ if (!this.write(stream, event))
5739
+ return;
5740
+ }
5741
+ }
5742
+ write(stream, event) {
5743
+ if (stream.closed)
5744
+ return false;
5745
+ try {
5746
+ const writable = stream.sink.write(`data: ${JSON.stringify(event)}
5747
+
5748
+ `);
5749
+ if (!writable)
5750
+ stream.blocked = true;
5751
+ return writable;
5752
+ } catch {
5753
+ this.closeStream(stream);
5754
+ return false;
5755
+ }
5756
+ }
5757
+ closeStream(stream, endSink = false) {
5758
+ if (stream.closed)
5759
+ return;
5760
+ stream.closed = true;
5761
+ clearInterval(stream.heartbeatTimer);
5762
+ stream.sink.removeListener("close", stream.onClose);
5763
+ stream.sink.removeListener("error", stream.onClose);
5764
+ stream.sink.removeListener("drain", stream.onDrain);
5765
+ if (this.streams.get(stream.physicalSessionId) === stream) {
5766
+ this.streams.delete(stream.physicalSessionId);
5767
+ }
5768
+ this.queue.updateInstanceActivity(stream.physicalSessionId);
5769
+ this.queue.setDeliveryActive(stream.physicalSessionId, stream.claimOwner, false);
5770
+ this.queue.releaseDeliveryClaims(stream.claimOwner);
5771
+ if (endSink) {
5772
+ try {
5773
+ stream.sink.end();
5774
+ } catch {
5775
+ }
5776
+ }
5777
+ }
5778
+ };
5779
+
3659
5780
  // ../core/dist/http-server.js
3660
5781
  function parseLineRange(lineRange) {
3661
5782
  const validLine = (line) => line === void 0 || line >= 1;
@@ -3812,26 +5933,26 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
3812
5933
  let lastMCPActivity = 0;
3813
5934
  let mcpServerStartTime = 0;
3814
5935
  const proxyInstances = /* @__PURE__ */ new Set();
3815
- const warnedVersionMismatches = /* @__PURE__ */ new Set();
3816
- let activePollEpoch = new AbortController();
3817
- activePollEpoch.abort();
5936
+ const rejectedVersionSessions = /* @__PURE__ */ new Set();
5937
+ const eventTransport = new SseStudioTransport(bridge);
5938
+ const eventStreamHandles = /* @__PURE__ */ new Set();
3818
5939
  const setMCPServerActive = (active) => {
3819
5940
  mcpServerActive = active;
3820
5941
  if (active) {
3821
- if (activePollEpoch.signal.aborted) {
3822
- activePollEpoch = new AbortController();
3823
- }
3824
5942
  mcpServerStartTime = Date.now();
3825
5943
  lastMCPActivity = Date.now();
3826
5944
  } else {
3827
- activePollEpoch.abort();
3828
5945
  mcpServerStartTime = 0;
3829
5946
  lastMCPActivity = 0;
3830
5947
  }
5948
+ eventTransport.refreshStatus();
3831
5949
  };
3832
5950
  const trackMCPActivity = () => {
3833
5951
  if (mcpServerActive) {
5952
+ const wasConnected = Date.now() - lastMCPActivity < 3e4;
3834
5953
  lastMCPActivity = Date.now();
5954
+ if (!wasConnected)
5955
+ eventTransport.refreshStatus();
3835
5956
  }
3836
5957
  };
3837
5958
  const isMCPServerActive = () => {
@@ -3839,6 +5960,18 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
3839
5960
  return false;
3840
5961
  return Date.now() - lastMCPActivity < 3e4;
3841
5962
  };
5963
+ const eventStatus = (physicalSessionId) => {
5964
+ const instance = bridge.getInstanceBySessionId(physicalSessionId);
5965
+ const knownInstance = instance?.physicalSessionId === physicalSessionId;
5966
+ return {
5967
+ kind: "status",
5968
+ knownInstance,
5969
+ mcpConnected: isMCPServerActive(),
5970
+ serverVersion: serverConfig?.version,
5971
+ pluginVersion: instance?.pluginVersion,
5972
+ pluginVariant: instance?.pluginVariant
5973
+ };
5974
+ };
3842
5975
  const isPluginConnected = () => {
3843
5976
  return bridge.getInstances().length > 0;
3844
5977
  };
@@ -3908,17 +6041,18 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
3908
6041
  pluginConnected: instances.length > 0,
3909
6042
  instanceCount: instances.length,
3910
6043
  instances: publicInstances,
3911
- versionMismatch: publicInstances.some((inst) => inst.versionMismatch),
3912
6044
  mcpServerActive: isMCPServerActive(),
3913
6045
  uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0,
3914
6046
  pendingRequests: bridge.getPendingRequestCount(),
3915
6047
  proxyInstanceCount: proxyInstances.size,
6048
+ activeEventStreams: eventTransport.activeStreamCount,
3916
6049
  streamableHttp: !!serverConfig
3917
6050
  });
3918
6051
  });
3919
6052
  app.post("/ready", (req, res) => {
3920
- const { pluginSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning, pluginVersion, pluginVariant } = req.body;
6053
+ const { pluginSessionId, physicalSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning, pluginVersion, pluginVariant } = req.body;
3921
6054
  const requestContext = {
6055
+ physicalSessionId: typeof physicalSessionId === "string" ? physicalSessionId : void 0,
3922
6056
  instanceId: typeof instanceId === "string" ? instanceId : void 0,
3923
6057
  role: typeof role === "string" ? role : void 0,
3924
6058
  placeId: typeof placeId === "number" ? placeId : void 0,
@@ -3928,12 +6062,15 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
3928
6062
  pluginVersion: typeof pluginVersion === "string" ? pluginVersion : void 0,
3929
6063
  pluginVariant: typeof pluginVariant === "string" ? pluginVariant : void 0
3930
6064
  };
3931
- if (!pluginSessionId || !instanceId || !role) {
3932
- const missingFields = [
3933
- !pluginSessionId ? "pluginSessionId" : void 0,
3934
- !instanceId ? "instanceId" : void 0,
3935
- !role ? "role" : void 0
3936
- ].filter((field) => !!field);
6065
+ const missingFields = [
6066
+ typeof pluginSessionId !== "string" || pluginSessionId === "" ? "pluginSessionId" : void 0,
6067
+ typeof physicalSessionId !== "string" || physicalSessionId === "" ? "physicalSessionId" : void 0,
6068
+ typeof instanceId !== "string" || instanceId === "" ? "instanceId" : void 0,
6069
+ typeof role !== "string" || role === "" ? "role" : void 0,
6070
+ typeof pluginVersion !== "string" || pluginVersion === "" ? "pluginVersion" : void 0,
6071
+ typeof pluginVariant !== "string" || pluginVariant === "" ? "pluginVariant" : void 0
6072
+ ].filter((field) => !!field);
6073
+ if (missingFields.length > 0) {
3937
6074
  res.status(400).json({
3938
6075
  success: false,
3939
6076
  error: "missing_ready_fields",
@@ -3943,19 +6080,71 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
3943
6080
  });
3944
6081
  return;
3945
6082
  }
6083
+ const serverVersion = serverConfig?.version;
6084
+ if (!serverVersion) {
6085
+ res.status(503).json({
6086
+ success: false,
6087
+ error: "server_version_unavailable",
6088
+ message: "The MCP server cannot accept Studio connections without a configured version.",
6089
+ request: requestContext
6090
+ });
6091
+ return;
6092
+ }
6093
+ if (pluginVersion !== serverVersion) {
6094
+ if (!rejectedVersionSessions.has(pluginSessionId)) {
6095
+ if (rejectedVersionSessions.size >= 256)
6096
+ rejectedVersionSessions.clear();
6097
+ rejectedVersionSessions.add(pluginSessionId);
6098
+ console.error(`[plugin-version-rejected] Studio plugin v${pluginVersion} (${pluginVariant}) does not match MCP server v${serverVersion} for ${instanceId}/${role}`);
6099
+ }
6100
+ res.status(426).json({
6101
+ success: false,
6102
+ error: "plugin_version_mismatch",
6103
+ message: `Studio plugin v${pluginVersion} does not match MCP server v${serverVersion}.`,
6104
+ pluginVersion,
6105
+ serverVersion,
6106
+ request: requestContext
6107
+ });
6108
+ return;
6109
+ }
6110
+ const isClientRole = role === "client" || /^client-[1-9]\d*$/.test(role);
6111
+ const isLogicalSession = physicalSessionId !== pluginSessionId;
6112
+ if (isLogicalSession && !isClientRole || !isLogicalSession && isClientRole || !isClientRole && role !== "edit" && role !== "server") {
6113
+ res.status(400).json({
6114
+ success: false,
6115
+ error: "invalid_session_topology",
6116
+ message: "Physical sessions must use the edit or server role; client roles must use a distinct physical server session.",
6117
+ request: requestContext
6118
+ });
6119
+ return;
6120
+ }
6121
+ if (isLogicalSession) {
6122
+ const physicalOwner = bridge.getInstanceBySessionId(physicalSessionId);
6123
+ const requestedInstanceId = typeof placeId === "number" && Number.isFinite(placeId) && placeId > 0 ? `place:${Math.trunc(placeId)}` : bridge.resolveInstanceId(instanceId);
6124
+ if (!physicalOwner || physicalOwner.pluginSessionId !== physicalSessionId || physicalOwner.physicalSessionId !== physicalSessionId || physicalOwner.role !== "server" || physicalOwner.instanceId !== requestedInstanceId) {
6125
+ res.status(409).json({
6126
+ success: false,
6127
+ error: "physical_session_unavailable",
6128
+ message: "A logical client requires a registered physical server session for the same Studio instance.",
6129
+ request: requestContext
6130
+ });
6131
+ return;
6132
+ }
6133
+ }
3946
6134
  let result;
3947
6135
  try {
3948
6136
  result = bridge.registerInstance({
3949
6137
  pluginSessionId,
6138
+ physicalSessionId,
3950
6139
  instanceId,
3951
6140
  role,
3952
6141
  placeId: typeof placeId === "number" ? placeId : 0,
3953
6142
  placeName: typeof placeName === "string" ? placeName : "",
3954
6143
  dataModelName: typeof dataModelName === "string" ? dataModelName : "",
3955
6144
  isRunning: !!isRunning,
3956
- pluginVersion: typeof pluginVersion === "string" ? pluginVersion : "",
3957
- pluginVariant: typeof pluginVariant === "string" ? pluginVariant : "unknown",
3958
- serverVersion: serverConfig?.version ?? ""
6145
+ pluginVersion,
6146
+ pluginVariant,
6147
+ serverVersion
3959
6148
  });
3960
6149
  } catch (err2) {
3961
6150
  res.status(500).json({
@@ -3976,17 +6165,12 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
3976
6165
  });
3977
6166
  return;
3978
6167
  }
3979
- const registered = bridge.getInstanceBySessionId(pluginSessionId);
3980
- if (registered?.versionMismatch && !warnedVersionMismatches.has(pluginSessionId)) {
3981
- warnedVersionMismatches.add(pluginSessionId);
3982
- console.error(`[version-mismatch] Studio plugin v${registered.pluginVersion} (${registered.pluginVariant}) does not match MCP server v${registered.serverVersion} for ${registered.instanceId}/${registered.role}`);
3983
- }
6168
+ eventTransport.refreshStatus(physicalSessionId);
3984
6169
  res.json({
3985
6170
  success: true,
3986
6171
  assignedRole: result.assignedRole,
3987
6172
  instanceId: result.instanceId,
3988
- serverVersion: serverConfig?.version,
3989
- versionMismatch: registered?.versionMismatch ?? false
6173
+ serverVersion
3990
6174
  });
3991
6175
  });
3992
6176
  app.post("/disconnect", (req, res) => {
@@ -4013,7 +6197,6 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
4013
6197
  instanceCount: instances.length,
4014
6198
  instances: publicInstances,
4015
6199
  serverVersion: serverConfig?.version,
4016
- versionMismatch: publicInstances.some((inst) => inst.versionMismatch),
4017
6200
  mcpServerActive: isMCPServerActive(),
4018
6201
  lastMCPActivity,
4019
6202
  uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0
@@ -4023,179 +6206,71 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
4023
6206
  const instances = bridge.getInstances();
4024
6207
  res.json({
4025
6208
  instances,
4026
- serverVersion: serverConfig?.version,
4027
- versionMismatch: instances.some((inst) => inst.versionMismatch)
6209
+ serverVersion: serverConfig?.version
4028
6210
  });
4029
6211
  });
4030
- app.get("/poll", async (req, res, next) => {
6212
+ app.get("/events", (req, res) => {
4031
6213
  const pluginSessionId = typeof req.query.pluginSessionId === "string" ? req.query.pluginSessionId : void 0;
4032
- const longPollRequested = req.query.pollMode === "long";
4033
- if (pluginSessionId) {
4034
- bridge.updateInstanceActivity(pluginSessionId);
4035
- }
4036
- const pollMetadata = () => {
4037
- const instance = pluginSessionId ? bridge.getInstanceBySessionId(pluginSessionId) : void 0;
4038
- return {
4039
- knownInstance: instance !== void 0,
4040
- pluginVersion: instance?.pluginVersion,
4041
- pluginVariant: instance?.pluginVariant,
4042
- versionMismatch: instance?.versionMismatch ?? false
4043
- };
4044
- };
4045
- let metadata = pollMetadata();
4046
- if (!isMCPServerActive()) {
4047
- res.status(503).json({
4048
- error: "MCP server not connected",
4049
- pluginConnected: true,
4050
- mcpConnected: false,
4051
- knownInstance: metadata.knownInstance,
4052
- serverVersion: serverConfig?.version,
4053
- pluginVersion: metadata.pluginVersion,
4054
- pluginVariant: metadata.pluginVariant,
4055
- versionMismatch: metadata.versionMismatch,
4056
- request: null
6214
+ if (!pluginSessionId) {
6215
+ res.status(400).json({
6216
+ error: "missing_plugin_session_id",
6217
+ message: "pluginSessionId is required"
4057
6218
  });
4058
6219
  return;
4059
6220
  }
4060
- if (!pluginSessionId || !metadata.knownInstance) {
4061
- res.json({
4062
- request: null,
4063
- mcpConnected: true,
4064
- pluginConnected: true,
4065
- knownInstance: false,
4066
- serverVersion: serverConfig?.version,
4067
- pluginVersion: metadata.pluginVersion,
4068
- pluginVariant: metadata.pluginVariant,
4069
- versionMismatch: metadata.versionMismatch,
4070
- proxyInstanceCount: proxyInstances.size,
4071
- pollMode: longPollRequested ? "long" : void 0
6221
+ const instance = bridge.getInstanceBySessionId(pluginSessionId);
6222
+ if (!instance) {
6223
+ res.status(404).json({
6224
+ error: "unknown_session",
6225
+ knownInstance: false
4072
6226
  });
4073
6227
  return;
4074
6228
  }
4075
- if (!longPollRequested) {
4076
- const instance = bridge.getInstanceBySessionId(pluginSessionId);
4077
- const pendingRequest = instance ? bridge.getPendingRequest(instance.instanceId, instance.role) : null;
4078
- metadata = pollMetadata();
4079
- res.json({
4080
- request: pendingRequest?.request ?? null,
4081
- requestId: pendingRequest?.requestId,
4082
- mcpConnected: true,
4083
- pluginConnected: true,
4084
- knownInstance: metadata.knownInstance,
4085
- serverVersion: serverConfig?.version,
4086
- pluginVersion: metadata.pluginVersion,
4087
- pluginVariant: metadata.pluginVariant,
4088
- versionMismatch: metadata.versionMismatch,
4089
- proxyInstanceCount: proxyInstances.size
6229
+ if (instance.physicalSessionId !== pluginSessionId) {
6230
+ res.status(409).json({
6231
+ error: "logical_session_has_no_event_stream",
6232
+ physicalSessionId: instance.physicalSessionId
4090
6233
  });
4091
6234
  return;
4092
6235
  }
4093
- const pollEpoch = activePollEpoch;
4094
- const pollController = new AbortController();
4095
- let socketClosed = false;
4096
- let mcpBecameInactive = false;
4097
- const detachListeners = () => {
4098
- req.removeListener("aborted", onSocketClosed);
4099
- res.removeListener("close", onSocketClosed);
4100
- res.removeListener("finish", onResponseFinished);
4101
- pollEpoch.signal.removeEventListener("abort", onMCPInactive);
4102
- };
4103
- const onSocketClosed = () => {
4104
- socketClosed = true;
4105
- pollController.abort();
4106
- detachListeners();
4107
- };
4108
- const onResponseFinished = () => {
4109
- detachListeners();
4110
- };
4111
- const onMCPInactive = () => {
4112
- mcpBecameInactive = true;
4113
- pollController.abort();
4114
- };
4115
- req.once("aborted", onSocketClosed);
4116
- res.once("close", onSocketClosed);
4117
- res.once("finish", onResponseFinished);
4118
- pollEpoch.signal.addEventListener("abort", onMCPInactive, { once: true });
4119
- if (req.aborted || res.destroyed) {
4120
- onSocketClosed();
4121
- } else if (pollEpoch.signal.aborted) {
4122
- onMCPInactive();
4123
- }
4124
- try {
4125
- const result = await bridge.waitForPendingRequest(pluginSessionId, {
4126
- signal: pollController.signal
4127
- });
4128
- if (socketClosed || res.destroyed) {
4129
- return;
4130
- }
4131
- metadata = pollMetadata();
4132
- if (result.kind === "request" && metadata.knownInstance) {
4133
- res.json({
4134
- request: result.request,
4135
- requestId: result.requestId,
4136
- mcpConnected: true,
4137
- pluginConnected: true,
4138
- knownInstance: true,
4139
- serverVersion: serverConfig?.version,
4140
- pluginVersion: metadata.pluginVersion,
4141
- pluginVariant: metadata.pluginVariant,
4142
- versionMismatch: metadata.versionMismatch,
4143
- proxyInstanceCount: proxyInstances.size,
4144
- pollMode: "long"
4145
- });
4146
- return;
4147
- }
4148
- if (mcpBecameInactive || pollEpoch.signal.aborted || !isMCPServerActive()) {
4149
- res.status(503).json({
4150
- error: "MCP server not connected",
4151
- pluginConnected: true,
4152
- mcpConnected: false,
4153
- knownInstance: metadata.knownInstance,
4154
- serverVersion: serverConfig?.version,
4155
- pluginVersion: metadata.pluginVersion,
4156
- pluginVariant: metadata.pluginVariant,
4157
- versionMismatch: metadata.versionMismatch,
4158
- request: null
4159
- });
4160
- return;
4161
- }
4162
- if (result.kind === "capacity") {
4163
- res.setHeader("Retry-After", "1");
4164
- res.status(503).json({
4165
- error: "Poll capacity reached",
4166
- code: "poll_capacity_reached",
4167
- pluginConnected: true,
4168
- mcpConnected: true,
4169
- knownInstance: metadata.knownInstance,
4170
- request: null
4171
- });
4172
- return;
4173
- }
4174
- res.json({
4175
- request: null,
4176
- mcpConnected: true,
4177
- pluginConnected: true,
4178
- knownInstance: metadata.knownInstance,
4179
- serverVersion: serverConfig?.version,
4180
- pluginVersion: metadata.pluginVersion,
4181
- pluginVariant: metadata.pluginVariant,
4182
- versionMismatch: metadata.versionMismatch,
4183
- proxyInstanceCount: proxyInstances.size,
4184
- pollMode: "long"
6236
+ if (!eventTransport.canOpen(pluginSessionId)) {
6237
+ res.setHeader("Retry-After", "1");
6238
+ res.status(503).json({
6239
+ error: "event_stream_capacity_reached",
6240
+ capacity: MAX_ACTIVE_EVENT_STREAMS
4185
6241
  });
4186
- } catch (error) {
4187
- detachListeners();
4188
- next(error);
6242
+ return;
6243
+ }
6244
+ bridge.updateInstanceActivity(pluginSessionId);
6245
+ res.status(200);
6246
+ res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
6247
+ res.setHeader("Cache-Control", "no-cache, no-transform");
6248
+ res.setHeader("Connection", "keep-alive");
6249
+ res.setHeader("X-Accel-Buffering", "no");
6250
+ res.flushHeaders();
6251
+ const handle = eventTransport.open(pluginSessionId, res, () => eventStatus(pluginSessionId));
6252
+ if (!handle) {
6253
+ res.end();
6254
+ return;
4189
6255
  }
6256
+ eventStreamHandles.add(handle);
6257
+ res.once("close", () => eventStreamHandles.delete(handle));
4190
6258
  });
4191
6259
  app.post("/response", (req, res) => {
4192
6260
  const { requestId, response, error } = req.body;
4193
- if (error) {
4194
- bridge.rejectRequest(requestId, error);
4195
- } else {
4196
- bridge.resolveRequest(requestId, response);
6261
+ if (typeof requestId !== "string" || requestId.length === 0) {
6262
+ res.status(400).json({
6263
+ success: false,
6264
+ error: "invalid_request_id"
6265
+ });
6266
+ return;
4197
6267
  }
4198
- res.json({ success: true });
6268
+ const disposition = error !== void 0 ? bridge.rejectRequest(requestId, error) : bridge.resolveRequest(requestId, response);
6269
+ if (disposition === "unknown") {
6270
+ res.status(404).json({ success: false, disposition });
6271
+ return;
6272
+ }
6273
+ res.json({ success: true, disposition });
4199
6274
  });
4200
6275
  app.post("/proxy", async (req, res) => {
4201
6276
  const { endpoint, data, targetInstanceId, targetRole, proxyInstanceId } = req.body;
@@ -4257,6 +6332,13 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
4257
6332
  app.isMCPServerActive = isMCPServerActive;
4258
6333
  app.trackMCPActivity = trackMCPActivity;
4259
6334
  app.closeMcpHandler = () => mcpHandler?.close();
6335
+ app.cleanup = async () => {
6336
+ for (const handle of eventStreamHandles)
6337
+ handle.close();
6338
+ eventStreamHandles.clear();
6339
+ eventTransport.close();
6340
+ await mcpHandler?.close();
6341
+ };
4260
6342
  return app;
4261
6343
  }
4262
6344
  function listenWithRetry(app, host, startPort, maxAttempts = 5) {
@@ -7106,6 +9188,7 @@ function rgbaToPng(rgba, width, height) {
7106
9188
  import * as fs3 from "fs";
7107
9189
  import * as path5 from "path";
7108
9190
  var MAX_INLINE_IMAGE_BYTES = 6e6;
9191
+ var MAX_MATRIX_IMAGE_BYTES = 8e6;
7109
9192
  var DEFAULT_ASSET_AUDIO_PREVIEWS = 3;
7110
9193
  var MAX_ASSET_AUDIO_PREVIEWS = 5;
7111
9194
  var MAX_INLINE_AUDIO_PREVIEW_BYTES = 3e6;
@@ -8047,7 +10130,7 @@ var RobloxStudioTools = class {
8047
10130
  }
8048
10131
  _resolveDeviceSimulatorSingleTarget(target, instance_id, toolName) {
8049
10132
  const selectedTarget = target ?? "edit";
8050
- if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients" || selectedTarget === "edit-proxy") {
10133
+ if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients") {
8051
10134
  throw new Error(`${toolName} target must be "edit" or "client-N" (got: ${selectedTarget})`);
8052
10135
  }
8053
10136
  if (selectedTarget !== "edit" && !/^client-\d+$/.test(selectedTarget)) {
@@ -8085,7 +10168,7 @@ var RobloxStudioTools = class {
8085
10168
  }
8086
10169
  _resolveSimulationTargets(target, instance_id, toolName) {
8087
10170
  const selectedTarget = target ?? "edit-and-clients";
8088
- if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "edit-proxy") {
10171
+ if (selectedTarget === "server" || selectedTarget === "all") {
8089
10172
  throw new Error(`${toolName} target must be "edit", "client-N", "all-clients", or "edit-and-clients" (got: ${selectedTarget})`);
8090
10173
  }
8091
10174
  const instanceId = this._resolveInstanceIdOnly(instance_id);
@@ -8886,6 +10969,7 @@ var RobloxStudioTools = class {
8886
10969
  const entrySummaries = summary.entries;
8887
10970
  const content = [];
8888
10971
  const failures = [];
10972
+ let imageBudget = MAX_MATRIX_IMAGE_BYTES;
8889
10973
  try {
8890
10974
  for (let i = 0; i < matrixEntries.length; i++) {
8891
10975
  const entry = matrixEntries[i];
@@ -8903,8 +10987,10 @@ var RobloxStudioTools = class {
8903
10987
  entrySummary.applied = applied;
8904
10988
  if (settleMs > 0)
8905
10989
  await sleep(settleMs);
8906
- const capture = await this._captureViewportImage(resolved.instanceId, resolved.role, format, quality);
10990
+ const entryBudget = Math.max(1, Math.floor(imageBudget / (matrixEntries.length - i)));
10991
+ const capture = await this._captureViewportImage(resolved.instanceId, resolved.role, format, quality, entryBudget);
8907
10992
  if (capture.success) {
10993
+ imageBudget -= Math.ceil(capture.data.length * 3 / 4);
8908
10994
  entrySummary.screenshot = {
8909
10995
  width: capture.width,
8910
10996
  height: capture.height,
@@ -9001,7 +11087,7 @@ var RobloxStudioTools = class {
9001
11087
  content: [{ type: "text", text: JSON.stringify(response) }]
9002
11088
  };
9003
11089
  }
9004
- const targets = resolved.targets.filter((t) => t.targetRole !== "edit-proxy");
11090
+ const targets = resolved.targets;
9005
11091
  const originPeerReliable = targets.length > 0 ? await this._isMultiplayerTestRunning(targets[0].targetInstanceId) : false;
9006
11092
  const responses = await Promise.allSettled(targets.map(async (t) => {
9007
11093
  const r = await this.client.request("/api/get-runtime-logs", data, t.targetInstanceId, t.targetRole);
@@ -10646,7 +12732,7 @@ var RobloxStudioTools = class {
10646
12732
  const response = await this.client.request("/api/get-memory-breakdown", data, resolved.targetInstanceId, resolved.targetRole);
10647
12733
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
10648
12734
  }
10649
- const targets = resolved.targets.filter((t) => t.targetRole !== "edit-proxy");
12735
+ const targets = resolved.targets;
10650
12736
  const responses = await Promise.allSettled(targets.map(async (t) => ({
10651
12737
  peer: t.targetRole,
10652
12738
  result: await this.client.request("/api/get-memory-breakdown", data, t.targetInstanceId, t.targetRole)
@@ -10679,7 +12765,7 @@ var RobloxStudioTools = class {
10679
12765
  const response = await this.client.request("/api/get-scene-analysis", data, resolved.targetInstanceId, resolved.targetRole);
10680
12766
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
10681
12767
  }
10682
- const targets = resolved.targets.filter((t) => t.targetRole !== "edit-proxy");
12768
+ const targets = resolved.targets;
10683
12769
  const responses = await Promise.allSettled(targets.map(async (t) => ({
10684
12770
  peer: t.targetRole,
10685
12771
  result: await this.client.request("/api/get-scene-analysis", data, t.targetInstanceId, t.targetRole)
@@ -10803,7 +12889,7 @@ var RobloxStudioTools = class {
10803
12889
  }, tgt, instance_id);
10804
12890
  return { content: [{ type: "text", text: JSON.stringify(response) }] };
10805
12891
  }
10806
- async _captureViewportImage(instanceId, targetRole, format, quality) {
12892
+ async _captureViewportImage(instanceId, targetRole, format, quality, maxBytes = MAX_INLINE_IMAGE_BYTES) {
10807
12893
  let response;
10808
12894
  if (targetRole.startsWith("client-")) {
10809
12895
  const begin = await this._callSingle("/api/capture-begin", {}, targetRole, instanceId);
@@ -10836,21 +12922,29 @@ var RobloxStudioTools = class {
10836
12922
  const { mimeType } = encoded;
10837
12923
  let usedQ = q;
10838
12924
  let note = "";
10839
- if (buffer.length > MAX_INLINE_IMAGE_BYTES) {
12925
+ if (buffer.length > maxBytes) {
10840
12926
  if (fmt === "png") {
10841
12927
  const mb = (buffer.length / 1048576).toFixed(1);
10842
12928
  return {
10843
12929
  success: false,
10844
- error: `PNG screenshot is ${mb}MB, over the ~${(MAX_INLINE_IMAGE_BYTES / 1048576).toFixed(0)}MB inline image limit. Use the default jpeg format (optionally with a "quality" value) or make the Studio window smaller for a lossless capture.`
12930
+ error: `PNG screenshot is ${mb}MB, over the ~${(maxBytes / 1048576).toFixed(1)}MB inline image limit. Use the default jpeg format (optionally with a "quality" value) or make the Studio window smaller for a lossless capture.`
10845
12931
  };
10846
12932
  }
10847
- while (buffer.length > MAX_INLINE_IMAGE_BYTES && usedQ > 25) {
12933
+ while (buffer.length > maxBytes && usedQ > 25) {
10848
12934
  usedQ = Math.max(25, usedQ - 20);
10849
12935
  buffer = encodeImageFromRgbaResponse(response, "jpeg", usedQ).buffer;
10850
12936
  }
12937
+ if (buffer.length > maxBytes) {
12938
+ return {
12939
+ success: false,
12940
+ error: `JPEG screenshot is still ${(buffer.length / 1048576).toFixed(1)}MB at q${usedQ}, over the ${(maxBytes / 1048576).toFixed(1)}MB inline budget. Make the Studio window smaller or capture fewer images per call.`
12941
+ };
12942
+ }
10851
12943
  note = ` \u2014 auto-reduced to q${usedQ} to fit the inline size limit; enlarge the Studio window or capture a smaller region for finer detail`;
10852
12944
  }
10853
- const message = `Screenshot ${w}x${h}px (${fmt}${fmt === "jpeg" ? ` q${usedQ}` : ""})${note}. For simulate_mouse_input, x/y are pixel coordinates in this exact image with (0,0) at the top-left; it is not downscaled, so use coordinates as you read them off the image.`;
12945
+ const nativeW = response.nativeWidth ?? w;
12946
+ const nativeH = response.nativeHeight ?? h;
12947
+ const message = nativeW !== w || nativeH !== h ? `Screenshot ${w}x${h}px (${fmt}${fmt === "jpeg" ? ` q${usedQ}` : ""})${note}, downscaled from the ${nativeW}x${nativeH} viewport to fit transport limits. For simulate_mouse_input, multiply x read off this image by ${(nativeW / w).toFixed(4)} and y by ${(nativeH / h).toFixed(4)} to get viewport pixel coordinates ((0,0) at the top-left).` : `Screenshot ${w}x${h}px (${fmt}${fmt === "jpeg" ? ` q${usedQ}` : ""})${note}. For simulate_mouse_input, x/y are pixel coordinates in this exact image with (0,0) at the top-left; it is not downscaled, so use coordinates as you read them off the image.`;
10854
12948
  return {
10855
12949
  success: true,
10856
12950
  width: w,
@@ -10878,7 +12972,8 @@ var RobloxStudioTools = class {
10878
12972
  height: capture.height,
10879
12973
  format: capture.format,
10880
12974
  mimeType: capture.mimeType,
10881
- ...capture.quality === void 0 ? {} : { quality: capture.quality }
12975
+ ...capture.quality === void 0 ? {} : { quality: capture.quality },
12976
+ message: capture.message
10882
12977
  })
10883
12978
  },
10884
12979
  {
@@ -11050,6 +13145,8 @@ var RobloxStudioMCPServer = class {
11050
13145
  console.error(`HTTP server listening on ${host}:${boundPort} for Studio plugin (primary mode)`);
11051
13146
  console.error(`Streamable HTTP MCP endpoint: http://localhost:${boundPort}/mcp`);
11052
13147
  } catch (error) {
13148
+ await primaryApp?.cleanup().catch(() => {
13149
+ });
11053
13150
  if (process.env.ROBLOX_STUDIO_REQUIRE_PRIMARY === "1") {
11054
13151
  console.error(`Port ${basePort} is unavailable and ROBLOX_STUDIO_REQUIRE_PRIMARY=1; refusing proxy mode`);
11055
13152
  throw error;
@@ -11083,23 +13180,11 @@ var RobloxStudioMCPServer = class {
11083
13180
  if (promotionInterval)
11084
13181
  clearInterval(promotionInterval);
11085
13182
  } catch {
13183
+ await candidateApp.cleanup().catch(() => {
13184
+ });
11086
13185
  }
11087
13186
  }, promotionIntervalMs);
11088
13187
  }
11089
- const LEGACY_PORT = 3002;
11090
- let legacyHandle;
11091
- let legacyApp;
11092
- if (boundPort !== LEGACY_PORT && bridgeMode === "primary") {
11093
- legacyApp = createHttpServer(this.tools, this.bridge, this.allowedToolNames, this.config, security);
11094
- try {
11095
- const result = await listenWithRetry(legacyApp, host, LEGACY_PORT, 1);
11096
- legacyHandle = result.server;
11097
- console.error(`Legacy HTTP server also listening on ${host}:${LEGACY_PORT} for old plugins`);
11098
- legacyApp.setMCPServerActive(true);
11099
- } catch {
11100
- console.error(`Legacy port ${LEGACY_PORT} in use, skipping backward-compat listener`);
11101
- }
11102
- }
11103
13188
  const stdioHandle = serveStdio((context) => createToolServer({
11104
13189
  config: this.config,
11105
13190
  getTools: () => this.tools,
@@ -11121,8 +13206,6 @@ var RobloxStudioMCPServer = class {
11121
13206
  const activityInterval = setInterval(() => {
11122
13207
  if (primaryApp)
11123
13208
  primaryApp.trackMCPActivity();
11124
- if (legacyApp)
11125
- legacyApp.trackMCPActivity();
11126
13209
  if (bridgeMode === "primary" && primaryApp) {
11127
13210
  const pluginConnected = primaryApp.isPluginConnected();
11128
13211
  const mcpActive = primaryApp.isMCPServerActive();
@@ -11147,22 +13230,16 @@ var RobloxStudioMCPServer = class {
11147
13230
  if (promotionInterval)
11148
13231
  clearInterval(promotionInterval);
11149
13232
  primaryApp?.setMCPServerActive(false);
11150
- legacyApp?.setMCPServerActive(false);
11151
13233
  this.bridge.clearAllPendingRequests();
11152
13234
  if (this.bridge instanceof ProxyBridgeService) {
11153
13235
  this.bridge.stop();
11154
13236
  }
11155
13237
  await stdioHandle.close().catch(() => {
11156
13238
  });
11157
- await Promise.all([
11158
- primaryApp?.closeMcpHandler(),
11159
- legacyApp?.closeMcpHandler()
11160
- ]).catch(() => {
13239
+ await primaryApp?.cleanup().catch(() => {
11161
13240
  });
11162
13241
  if (httpHandle)
11163
13242
  httpHandle.close();
11164
- if (legacyHandle)
11165
- legacyHandle.close();
11166
13243
  process.exit(0);
11167
13244
  };
11168
13245
  process.on("SIGTERM", shutdown);
@@ -12909,12 +14986,15 @@ var TOOL_DEFINITIONS = [
12909
14986
  var getReadOnlyTools = () => TOOL_DEFINITIONS.filter((t) => t.category === "read");
12910
14987
 
12911
14988
  // ../core/dist/install-plugin-helpers.js
12912
- import { existsSync as existsSync6, unlinkSync } from "fs";
14989
+ var import_saxes = __toESM(require_saxes(), 1);
14990
+ import { existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, statSync as statSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
12913
14991
  import { execSync } from "child_process";
12914
- import { join as join5 } from "path";
14992
+ import { randomUUID as randomUUID5 } from "crypto";
14993
+ import { basename as basename3, join as join5 } from "path";
12915
14994
  import { homedir as homedir4 } from "os";
14995
+ import { isUtf8 } from "buffer";
12916
14996
  var DEFAULT_PLUGIN_PORT = 58741;
12917
- var SERVER_URL_SETTING_KEY_PATTERN = /MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1|MCP_LAST_SUCCESSFUL_SERVER_URL_|MCP_SERVER_URL_/g;
14997
+ var SERVER_URL_SETTING_KEY_PATTERN = /MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1|MCP_LAST_SUCCESSFUL_SERVER_URL_/g;
12918
14998
  function configurePluginAssetForPort(source, rawPort = process.env.ROBLOX_STUDIO_PORT) {
12919
14999
  if (rawPort === void 0 || rawPort === "")
12920
15000
  return source;
@@ -12941,19 +15021,19 @@ function configurePluginAssetForPort(source, rawPort = process.env.ROBLOX_STUDIO
12941
15021
  function isWSL() {
12942
15022
  return getStudioPlatformCapabilities().isWsl;
12943
15023
  }
12944
- function getWindowsUserPluginsDir() {
15024
+ function getWindowsUserLocalAppData() {
12945
15025
  try {
12946
- const localAppData = execSync('cmd.exe /c "echo %LOCALAPPDATA%"', {
15026
+ const windowsPath = execSync('cmd.exe /c "echo %LOCALAPPDATA%"', {
12947
15027
  stdio: ["ignore", "pipe", "ignore"]
12948
15028
  }).toString().trim();
12949
- if (!localAppData || localAppData.includes("%"))
15029
+ if (!windowsPath || windowsPath.includes("%"))
12950
15030
  return null;
12951
- const linuxPath = execSync(`wslpath -u '${localAppData.replace(/'/g, "'\\''")}'`, {
15031
+ const linuxPath = execSync(`wslpath -u '${windowsPath.replace(/'/g, "'\\''")}'`, {
12952
15032
  stdio: ["ignore", "pipe", "ignore"]
12953
15033
  }).toString().trim();
12954
15034
  if (!linuxPath)
12955
15035
  return null;
12956
- return join5(linuxPath, "Roblox", "Plugins");
15036
+ return { linuxPath, windowsPath };
12957
15037
  } catch {
12958
15038
  return null;
12959
15039
  }
@@ -12965,13 +15045,78 @@ function getPluginsFolder() {
12965
15045
  return join5(process.env.LOCALAPPDATA || join5(homedir4(), "AppData", "Local"), "Roblox", "Plugins");
12966
15046
  }
12967
15047
  if (isWSL()) {
12968
- const win = getWindowsUserPluginsDir();
12969
- if (win)
12970
- return win;
15048
+ const localAppData = getWindowsUserLocalAppData();
15049
+ if (localAppData)
15050
+ return join5(localAppData.linuxPath, "Roblox", "Plugins");
12971
15051
  console.warn("[install-plugin] WSL detected but could not resolve Windows %LOCALAPPDATA%. Falling back to ~/Documents/Roblox/Plugins/ - you will likely need to copy the rbxmx to /mnt/c/Users/<you>/AppData/Local/Roblox/Plugins/ manually. Set MCP_PLUGINS_DIR to skip detection.");
12972
15052
  }
12973
15053
  return join5(homedir4(), "Documents", "Roblox", "Plugins");
12974
15054
  }
15055
+ var STALE_TEST_PLUGINS_DIRECTORY = "RsmcpIsolatedPlugins";
15056
+ function pluginDirectorySettingPattern() {
15057
+ return /(<QDir\b[^>]*\bname="PluginsDir"[^>]*>)([\s\S]*?)(<\/QDir>)/gu;
15058
+ }
15059
+ function escapeXmlText(value) {
15060
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
15061
+ }
15062
+ function repairStaleStudioPluginDirectorySetting({ settingsPath, studioPluginsDirectory }) {
15063
+ if (!existsSync6(settingsPath))
15064
+ return false;
15065
+ if (studioPluginsDirectory === "") {
15066
+ throw new Error("studioPluginsDirectory must be a non-empty path.");
15067
+ }
15068
+ const contents = readFileSync6(settingsPath, "utf8");
15069
+ const matches = [...contents.matchAll(pluginDirectorySettingPattern())];
15070
+ if (matches.length !== 1) {
15071
+ throw new Error(`Expected exactly one Studio.PluginsDir QDir in ${settingsPath}; found ${matches.length}.`);
15072
+ }
15073
+ if (matches[0][2] !== STALE_TEST_PLUGINS_DIRECTORY)
15074
+ return false;
15075
+ const studioPath = escapeXmlText(studioPluginsDirectory);
15076
+ const updated = contents.replace(pluginDirectorySettingPattern(), (_match, open2, _value, close) => `${open2}${studioPath}${close}`);
15077
+ const temporaryPath = `${settingsPath}.rsmcp-install-${process.pid}-${randomUUID5()}.tmp`;
15078
+ try {
15079
+ writeFileSync3(temporaryPath, updated, {
15080
+ encoding: "utf8",
15081
+ mode: statSync3(settingsPath).mode
15082
+ });
15083
+ renameSync(temporaryPath, settingsPath);
15084
+ } finally {
15085
+ rmSync2(temporaryPath, { force: true });
15086
+ }
15087
+ return true;
15088
+ }
15089
+ function repairDefaultStudioPluginDirectorySetting(pluginsFolder, log) {
15090
+ if (process.env.MCP_PLUGINS_DIR)
15091
+ return;
15092
+ let settingsPath;
15093
+ let defaultPluginsFolder;
15094
+ let studioPluginsDirectory;
15095
+ if (process.platform === "win32") {
15096
+ const localAppData = process.env.LOCALAPPDATA || join5(homedir4(), "AppData", "Local");
15097
+ defaultPluginsFolder = join5(localAppData, "Roblox", "Plugins");
15098
+ settingsPath = join5(localAppData, "Roblox", "GlobalSettings_13.xml");
15099
+ studioPluginsDirectory = defaultPluginsFolder.replaceAll("\\", "/");
15100
+ } else if (isWSL()) {
15101
+ const localAppData = getWindowsUserLocalAppData();
15102
+ if (!localAppData)
15103
+ return;
15104
+ defaultPluginsFolder = join5(localAppData.linuxPath, "Roblox", "Plugins");
15105
+ settingsPath = join5(localAppData.linuxPath, "Roblox", "GlobalSettings_13.xml");
15106
+ studioPluginsDirectory = [
15107
+ localAppData.windowsPath.replace(/[\\/]+$/u, ""),
15108
+ "Roblox",
15109
+ "Plugins"
15110
+ ].join("/").replaceAll("\\", "/");
15111
+ } else {
15112
+ return;
15113
+ }
15114
+ if (pluginsFolder !== defaultPluginsFolder)
15115
+ return;
15116
+ if (repairStaleStudioPluginDirectorySetting({ settingsPath, studioPluginsDirectory })) {
15117
+ log(`Restored Studio PluginsDir to ${studioPluginsDirectory}.`);
15118
+ }
15119
+ }
12975
15120
  function handleVariantConflict({ pluginsFolder, otherAssetName, replace, log = console.log, warn = console.warn }) {
12976
15121
  const otherDest = join5(pluginsFolder, otherAssetName);
12977
15122
  if (!existsSync6(otherDest))
@@ -12991,12 +15136,190 @@ Only one MCP plugin variant should be present. If both variants are in the Studi
12991
15136
  Delete ${otherAssetName} manually or use the default CLI installer behavior to replace it.
12992
15137
  `);
12993
15138
  }
15139
+ var PLUGIN_INSTALL_LOCK_NAME = ".robloxstudio-mcp-plugin-install.lock";
15140
+ function errorCode(error) {
15141
+ return error.code;
15142
+ }
15143
+ function acquirePluginInstallLock(pluginsFolder, warn) {
15144
+ const lockPath = join5(pluginsFolder, PLUGIN_INSTALL_LOCK_NAME);
15145
+ const ownerPath = join5(lockPath, "owner.json");
15146
+ const owner = {
15147
+ pid: process.pid,
15148
+ token: randomUUID5(),
15149
+ createdAt: Date.now()
15150
+ };
15151
+ try {
15152
+ mkdirSync4(lockPath);
15153
+ } catch (error) {
15154
+ if (errorCode(error) !== "EEXIST")
15155
+ throw error;
15156
+ throw new Error(`Another Studio plugin installation is already in progress: ${lockPath}. If no installer is running, remove that lock directory and retry.`);
15157
+ }
15158
+ try {
15159
+ writeFileSync3(ownerPath, `${JSON.stringify(owner)}
15160
+ `, {
15161
+ encoding: "utf8",
15162
+ flag: "wx"
15163
+ });
15164
+ } catch (error) {
15165
+ try {
15166
+ rmSync2(lockPath, { recursive: true, force: true });
15167
+ } catch (cleanupError) {
15168
+ warn(`[install-plugin] Could not clean failed install lock ${lockPath}: ${cleanupError}`);
15169
+ }
15170
+ throw error;
15171
+ }
15172
+ return () => {
15173
+ try {
15174
+ const currentOwner = JSON.parse(readFileSync6(ownerPath, "utf8"));
15175
+ if (currentOwner.pid === owner.pid && currentOwner.token === owner.token) {
15176
+ rmSync2(lockPath, { recursive: true, force: true });
15177
+ }
15178
+ } catch (error) {
15179
+ if (errorCode(error) !== "ENOENT") {
15180
+ warn(`[install-plugin] Could not release install lock ${lockPath}: ${error}`);
15181
+ }
15182
+ }
15183
+ };
15184
+ }
15185
+ function assertAssetFileName(name, field) {
15186
+ if (name === "" || basename3(name) !== name) {
15187
+ throw new Error(`${field} must be a file name, received ${JSON.stringify(name)}`);
15188
+ }
15189
+ }
15190
+ function embeddedPluginValue(source, name, assetName) {
15191
+ const pattern = new RegExp(`\\blocal\\s+${name}\\s*=\\s*"([^"]+)"\\s*;?`, "g");
15192
+ const matches = [...source.matchAll(pattern)];
15193
+ if (matches.length !== 1) {
15194
+ throw new Error(`${assetName} must contain exactly one embedded ${name}; found ${matches.length}.`);
15195
+ }
15196
+ return matches[0][1];
15197
+ }
15198
+ var PLUGIN_SOURCE_CLASSES = {
15199
+ Script: true,
15200
+ ModuleScript: true,
15201
+ LocalScript: true
15202
+ };
15203
+ function parsePluginScriptSources(source, assetName) {
15204
+ if (!isUtf8(source)) {
15205
+ throw new Error(`${assetName} is not valid UTF-8 Roblox XML.`);
15206
+ }
15207
+ const text = source.toString("utf8");
15208
+ const elementStack = [];
15209
+ const scriptSources = [];
15210
+ let rootName;
15211
+ let currentSource;
15212
+ const parser = new import_saxes.SaxesParser({ fragment: false, xmlns: false });
15213
+ parser.on("doctype", () => {
15214
+ throw new Error("DOCTYPE declarations are not allowed in plugin artifacts.");
15215
+ });
15216
+ parser.on("opentag", (tag) => {
15217
+ if (elementStack.length === 0)
15218
+ rootName = tag.name;
15219
+ if (currentSource === void 0 && tag.name === "string" && tag.attributes.name === "Source") {
15220
+ const nearestItem = [...elementStack].reverse().find((element) => element.name === "Item");
15221
+ const sourceClass = nearestItem?.attributes.class;
15222
+ if (sourceClass !== void 0 && PLUGIN_SOURCE_CLASSES[sourceClass] === true) {
15223
+ currentSource = { depth: elementStack.length, text: "" };
15224
+ }
15225
+ }
15226
+ elementStack.push({ name: tag.name, attributes: tag.attributes });
15227
+ });
15228
+ parser.on("text", (value) => {
15229
+ if (currentSource !== void 0)
15230
+ currentSource.text += value;
15231
+ });
15232
+ parser.on("cdata", (value) => {
15233
+ if (currentSource !== void 0)
15234
+ currentSource.text += value;
15235
+ });
15236
+ parser.on("closetag", (tag) => {
15237
+ if (currentSource !== void 0 && tag.name === "string" && elementStack.length === currentSource.depth + 1) {
15238
+ scriptSources.push(currentSource.text);
15239
+ currentSource = void 0;
15240
+ }
15241
+ elementStack.pop();
15242
+ });
15243
+ try {
15244
+ parser.write(text).close();
15245
+ } catch (error) {
15246
+ const detail = error instanceof Error ? error.message : String(error);
15247
+ throw new Error(`${assetName} is not well-formed Roblox XML: ${detail}`, {
15248
+ cause: error
15249
+ });
15250
+ }
15251
+ if (rootName !== "roblox" || scriptSources.length === 0) {
15252
+ throw new Error(`${assetName} is not a Roblox XML model containing a Script source.`);
15253
+ }
15254
+ return scriptSources;
15255
+ }
15256
+ function assertPluginAssetIdentity(source, assetName, expectedVersion, expectedVariant) {
15257
+ const pluginSource = parsePluginScriptSources(source, assetName).join("\n");
15258
+ const actualVersion = embeddedPluginValue(pluginSource, "CURRENT_VERSION", assetName);
15259
+ if (actualVersion !== expectedVersion) {
15260
+ throw new Error(`${assetName} embeds version ${actualVersion}; expected ${expectedVersion}.`);
15261
+ }
15262
+ const actualVariant = embeddedPluginValue(pluginSource, "PLUGIN_VARIANT", assetName);
15263
+ if (actualVariant !== expectedVariant) {
15264
+ throw new Error(`${assetName} embeds variant ${actualVariant}; expected ${expectedVariant}.`);
15265
+ }
15266
+ }
15267
+ function installedFileMatches(source, destination) {
15268
+ if (!existsSync6(destination))
15269
+ return false;
15270
+ if (!lstatSync(destination).isFile())
15271
+ return false;
15272
+ const installed = readFileSync6(destination);
15273
+ return installed.length === source.length && installed.equals(source);
15274
+ }
15275
+ function installPluginAsset({ pluginsFolder, assetName, otherAssetName, source, expectedVersion, expectedVariant, rawPort, replaceVariant = true, log = console.log, warn = console.warn }) {
15276
+ assertAssetFileName(assetName, "assetName");
15277
+ assertAssetFileName(otherAssetName, "otherAssetName");
15278
+ if (assetName === otherAssetName) {
15279
+ throw new Error("assetName and otherAssetName must identify different files.");
15280
+ }
15281
+ const configured = configurePluginAssetForPort(source, rawPort);
15282
+ assertPluginAssetIdentity(configured, assetName, expectedVersion, expectedVariant);
15283
+ mkdirSync4(pluginsFolder, { recursive: true });
15284
+ const destination = join5(pluginsFolder, assetName);
15285
+ const releaseLock = acquirePluginInstallLock(pluginsFolder, warn);
15286
+ try {
15287
+ let installed = false;
15288
+ if (!installedFileMatches(configured, destination)) {
15289
+ const staged = join5(pluginsFolder, `.${assetName}.${process.pid}.${randomUUID5()}.tmp`);
15290
+ try {
15291
+ writeFileSync3(staged, configured, { flag: "wx" });
15292
+ renameSync(staged, destination);
15293
+ installed = true;
15294
+ } finally {
15295
+ try {
15296
+ unlinkSync(staged);
15297
+ } catch (error) {
15298
+ if (errorCode(error) !== "ENOENT") {
15299
+ warn(`[install-plugin] Could not remove staging file ${staged}: ${error}`);
15300
+ }
15301
+ }
15302
+ }
15303
+ }
15304
+ handleVariantConflict({
15305
+ pluginsFolder,
15306
+ otherAssetName,
15307
+ replace: replaceVariant,
15308
+ log,
15309
+ warn
15310
+ });
15311
+ repairDefaultStudioPluginDirectorySetting(pluginsFolder, log);
15312
+ return { destination, installed };
15313
+ } finally {
15314
+ releaseLock();
15315
+ }
15316
+ }
12994
15317
 
12995
15318
  // src/index.ts
12996
15319
  import { createRequire } from "module";
12997
15320
 
12998
15321
  // src/install-plugin.ts
12999
- import { createWriteStream, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
15322
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
13000
15323
  import { dirname as dirname5, join as join6 } from "path";
13001
15324
  import { fileURLToPath } from "url";
13002
15325
  import { get } from "https";
@@ -13005,6 +15328,7 @@ var ASSET_NAME = "MCPInspectorPlugin.rbxmx";
13005
15328
  var OTHER_VARIANT = "MCPPlugin.rbxmx";
13006
15329
  var TIMEOUT_MS = 3e4;
13007
15330
  var MAX_REDIRECTS = 5;
15331
+ var MAX_ASSET_BYTES = 64 * 1024 * 1024;
13008
15332
  function httpsGet(url) {
13009
15333
  return new Promise((resolve5, reject) => {
13010
15334
  const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } }, resolve5);
@@ -13014,36 +15338,33 @@ function httpsGet(url) {
13014
15338
  });
13015
15339
  });
13016
15340
  }
13017
- async function download(url, dest, redirects = 0) {
15341
+ async function download(url, redirects = 0) {
13018
15342
  const res = await httpsGet(url);
13019
15343
  if (res.statusCode === 301 || res.statusCode === 302) {
13020
15344
  if (redirects >= MAX_REDIRECTS) throw new Error(`Too many redirects (max ${MAX_REDIRECTS})`);
13021
15345
  const location = res.headers.location;
13022
15346
  if (!location) throw new Error("Redirect with no location header");
13023
- return download(location, dest, redirects + 1);
15347
+ for await (const _chunk of res) {
15348
+ }
15349
+ return download(location, redirects + 1);
13024
15350
  }
13025
15351
  if (res.statusCode !== 200) {
13026
15352
  throw new Error(`Download failed: HTTP ${res.statusCode}`);
13027
15353
  }
13028
- return new Promise((resolve5, reject) => {
13029
- const file = createWriteStream(dest);
13030
- const cleanup = (err2) => {
13031
- file.close(() => {
13032
- try {
13033
- unlinkSync2(dest);
13034
- } catch {
13035
- }
13036
- reject(err2);
13037
- });
13038
- };
13039
- res.pipe(file);
13040
- file.on("finish", () => {
13041
- file.close();
13042
- resolve5();
13043
- });
13044
- file.on("error", cleanup);
13045
- res.on("error", cleanup);
13046
- });
15354
+ const chunks = [];
15355
+ let totalBytes = 0;
15356
+ for await (const chunk of res) {
15357
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
15358
+ totalBytes += bytes.length;
15359
+ if (totalBytes > MAX_ASSET_BYTES) {
15360
+ res.destroy();
15361
+ throw new Error(
15362
+ `${ASSET_NAME} download exceeds the ${MAX_ASSET_BYTES}-byte limit.`
15363
+ );
15364
+ }
15365
+ chunks.push(bytes);
15366
+ }
15367
+ return Buffer.concat(chunks, totalBytes);
13047
15368
  }
13048
15369
  async function fetchJson(url) {
13049
15370
  const res = await httpsGet(url);
@@ -13056,24 +15377,6 @@ async function fetchJson(url) {
13056
15377
  }
13057
15378
  return JSON.parse(Buffer.concat(chunks).toString());
13058
15379
  }
13059
- function prepareInstall({
13060
- replaceVariant,
13061
- log,
13062
- warn
13063
- }) {
13064
- const pluginsFolder = getPluginsFolder();
13065
- if (!existsSync7(pluginsFolder)) {
13066
- mkdirSync4(pluginsFolder, { recursive: true });
13067
- }
13068
- handleVariantConflict({
13069
- pluginsFolder,
13070
- otherAssetName: OTHER_VARIANT,
13071
- replace: replaceVariant,
13072
- log,
13073
- warn
13074
- });
13075
- return pluginsFolder;
13076
- }
13077
15380
  function bundledAssetPath() {
13078
15381
  const currentDir = dirname5(fileURLToPath(import.meta.url));
13079
15382
  const candidates = [
@@ -13088,47 +15391,36 @@ function resolvePluginAssetPath(sourcePath) {
13088
15391
  }
13089
15392
  function packageVersion() {
13090
15393
  const currentDir = dirname5(fileURLToPath(import.meta.url));
13091
- const pkg = JSON.parse(readFileSync6(join6(currentDir, "..", "package.json"), "utf8"));
15394
+ const pkg = JSON.parse(readFileSync7(join6(currentDir, "..", "package.json"), "utf8"));
13092
15395
  if (!pkg.version) {
13093
15396
  throw new Error("Package version not found");
13094
15397
  }
13095
15398
  return pkg.version;
13096
15399
  }
13097
- function bundledPluginVersion(source) {
13098
- const match = readFileSync6(source, "utf8").match(/local CURRENT_VERSION = "([^"]+)"/);
13099
- return match ? match[1] : null;
13100
- }
13101
- function assertBundledPluginVersion(source) {
13102
- const expected = packageVersion();
13103
- const actual = bundledPluginVersion(source);
13104
- if (actual !== expected) {
13105
- throw new Error(
13106
- `Bundled ${ASSET_NAME} version ${actual ?? "unknown"} does not match package version ${expected}. Run npm run build:plugin:inspector before starting with --auto-install-plugin.`
13107
- );
13108
- }
13109
- }
13110
- function filesMatch(expected, actualPath) {
13111
- if (!existsSync7(actualPath)) return false;
13112
- const actual = readFileSync6(actualPath);
13113
- return expected.length === actual.length && expected.equals(actual);
13114
- }
13115
15400
  async function installBundledPlugin(options = {}) {
13116
15401
  const log = options.log ?? console.log;
13117
15402
  const warn = options.warn ?? console.warn;
13118
15403
  const replaceVariant = options.replaceVariant ?? true;
13119
- const source = resolvePluginAssetPath(options.sourcePath);
13120
- if (!source) {
15404
+ const sourcePath = resolvePluginAssetPath(options.sourcePath);
15405
+ if (!sourcePath) {
13121
15406
  throw new Error(
13122
15407
  `Bundled ${ASSET_NAME} not found. Run npm run build:plugin:inspector in this worktree first.`
13123
15408
  );
13124
15409
  }
13125
- assertBundledPluginVersion(source);
13126
- const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
13127
- const dest = join6(pluginsFolder, ASSET_NAME);
13128
- const configured = configurePluginAssetForPort(readFileSync6(source));
13129
- if (filesMatch(configured, dest)) return;
13130
- writeFileSync3(dest, configured);
13131
- log(`Installed ${ASSET_NAME} to ${dest}`);
15410
+ const result = await installPluginAsset({
15411
+ pluginsFolder: getPluginsFolder(),
15412
+ assetName: ASSET_NAME,
15413
+ otherAssetName: OTHER_VARIANT,
15414
+ source: readFileSync7(sourcePath),
15415
+ expectedVersion: packageVersion(),
15416
+ expectedVariant: "inspector",
15417
+ replaceVariant,
15418
+ log,
15419
+ warn
15420
+ });
15421
+ if (result.installed) {
15422
+ log(`Installed ${ASSET_NAME} to ${result.destination}`);
15423
+ }
13132
15424
  }
13133
15425
  async function installPlugin(options = {}) {
13134
15426
  const replaceVariant = options.replaceVariant ?? true;
@@ -13138,17 +15430,23 @@ async function installPlugin(options = {}) {
13138
15430
  if (options.sourcePath !== void 0 && !bundled) {
13139
15431
  throw new Error(`Plugin asset not found at explicit path ${options.sourcePath}.`);
13140
15432
  }
13141
- const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
13142
15433
  if (bundled) {
13143
- assertBundledPluginVersion(bundled);
13144
- const dest2 = join6(pluginsFolder, ASSET_NAME);
13145
- const configured2 = configurePluginAssetForPort(readFileSync6(bundled));
13146
- if (filesMatch(configured2, dest2)) {
15434
+ const result2 = await installPluginAsset({
15435
+ pluginsFolder: getPluginsFolder(),
15436
+ assetName: ASSET_NAME,
15437
+ otherAssetName: OTHER_VARIANT,
15438
+ source: readFileSync7(bundled),
15439
+ expectedVersion: packageVersion(),
15440
+ expectedVariant: "inspector",
15441
+ replaceVariant,
15442
+ log,
15443
+ warn
15444
+ });
15445
+ if (result2.installed) {
15446
+ log(`Installed bundled ${ASSET_NAME} to ${result2.destination}`);
15447
+ } else {
13147
15448
  log(`${ASSET_NAME} already installed.`);
13148
- return;
13149
15449
  }
13150
- writeFileSync3(dest2, configured2);
13151
- log(`Installed bundled ${ASSET_NAME} to ${dest2}`);
13152
15450
  return;
13153
15451
  }
13154
15452
  log("Fetching latest release...");
@@ -13157,13 +15455,24 @@ async function installPlugin(options = {}) {
13157
15455
  if (!asset) {
13158
15456
  throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
13159
15457
  }
13160
- const dest = join6(pluginsFolder, ASSET_NAME);
13161
15458
  log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
13162
- await download(asset.browser_download_url, dest);
13163
- const downloaded = readFileSync6(dest);
13164
- const configured = configurePluginAssetForPort(downloaded);
13165
- if (configured !== downloaded) writeFileSync3(dest, configured);
13166
- log(`Installed to ${dest}`);
15459
+ const downloaded = await download(asset.browser_download_url);
15460
+ const result = await installPluginAsset({
15461
+ pluginsFolder: getPluginsFolder(),
15462
+ assetName: ASSET_NAME,
15463
+ otherAssetName: OTHER_VARIANT,
15464
+ source: downloaded,
15465
+ expectedVersion: release.tag_name.replace(/^v/, ""),
15466
+ expectedVariant: "inspector",
15467
+ replaceVariant,
15468
+ log,
15469
+ warn
15470
+ });
15471
+ if (result.installed) {
15472
+ log(`Installed to ${result.destination}`);
15473
+ } else {
15474
+ log(`${ASSET_NAME} already installed.`);
15475
+ }
13167
15476
  }
13168
15477
 
13169
15478
  // src/index.ts
@@ -13201,3 +15510,32 @@ if (installBundledOnly || installWithFallback) {
13201
15510
  process.exit(1);
13202
15511
  });
13203
15512
  }
15513
+ /*! Bundled license information:
15514
+
15515
+ xmlchars/xml/1.0/ed5.js:
15516
+ (**
15517
+ * Character classes and associated utilities for the 5th edition of XML 1.0.
15518
+ *
15519
+ * @author Louis-Dominique Dubeau
15520
+ * @license MIT
15521
+ * @copyright Louis-Dominique Dubeau
15522
+ *)
15523
+
15524
+ xmlchars/xml/1.1/ed2.js:
15525
+ (**
15526
+ * Character classes and associated utilities for the 2nd edition of XML 1.1.
15527
+ *
15528
+ * @author Louis-Dominique Dubeau
15529
+ * @license MIT
15530
+ * @copyright Louis-Dominique Dubeau
15531
+ *)
15532
+
15533
+ xmlchars/xmlns/1.0/ed3.js:
15534
+ (**
15535
+ * Character class utilities for XML NS 1.0 edition 3.
15536
+ *
15537
+ * @author Louis-Dominique Dubeau
15538
+ * @license MIT
15539
+ * @copyright Louis-Dominique Dubeau
15540
+ *)
15541
+ */