@chrrxs/robloxstudio-mcp-inspector 3.0.2 → 3.0.4
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 +2979 -518
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +1694 -1026
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,10 @@ 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
|
|
41
|
-
var
|
|
1972
|
+
var ACCEPTED_REQUEST_TOMBSTONE_TTL_MS = 6e4;
|
|
1973
|
+
var MAX_ACCEPTED_REQUEST_TOMBSTONES = 4096;
|
|
1974
|
+
var CANCELLATION_TOMBSTONE_TTL_MS = 6e4;
|
|
1975
|
+
var MAX_CANCELLATION_TOMBSTONES = 4096;
|
|
42
1976
|
function publishedInstanceId(placeId) {
|
|
43
1977
|
if (placeId === void 0 || !Number.isFinite(placeId) || placeId <= 0)
|
|
44
1978
|
return void 0;
|
|
@@ -46,11 +1980,15 @@ function publishedInstanceId(placeId) {
|
|
|
46
1980
|
}
|
|
47
1981
|
var BridgeService = class {
|
|
48
1982
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
1983
|
+
acceptedRequestIds = /* @__PURE__ */ new Map();
|
|
1984
|
+
pendingCancellations = /* @__PURE__ */ new Map();
|
|
49
1985
|
// Keyed by pluginSessionId (the per-plugin GUID).
|
|
50
1986
|
instances = /* @__PURE__ */ new Map();
|
|
51
1987
|
instanceAliases = /* @__PURE__ */ new Map();
|
|
52
1988
|
instanceRegisteredListeners = /* @__PURE__ */ new Set();
|
|
53
|
-
|
|
1989
|
+
requestAvailableListeners = /* @__PURE__ */ new Set();
|
|
1990
|
+
sessionClosedListeners = /* @__PURE__ */ new Set();
|
|
1991
|
+
deliveryOwnersByPhysicalSession = /* @__PURE__ */ new Map();
|
|
54
1992
|
requestTimeout = 3e4;
|
|
55
1993
|
onInstanceRegistered(listener) {
|
|
56
1994
|
this.instanceRegisteredListeners.add(listener);
|
|
@@ -70,6 +2008,62 @@ var BridgeService = class {
|
|
|
70
2008
|
}
|
|
71
2009
|
}
|
|
72
2010
|
}
|
|
2011
|
+
onRequestAvailable(listener) {
|
|
2012
|
+
this.requestAvailableListeners.add(listener);
|
|
2013
|
+
return () => this.requestAvailableListeners.delete(listener);
|
|
2014
|
+
}
|
|
2015
|
+
onSessionClosed(listener) {
|
|
2016
|
+
this.sessionClosedListeners.add(listener);
|
|
2017
|
+
return () => this.sessionClosedListeners.delete(listener);
|
|
2018
|
+
}
|
|
2019
|
+
setDeliveryActive(physicalSessionId, owner, active) {
|
|
2020
|
+
let owners = this.deliveryOwnersByPhysicalSession.get(physicalSessionId);
|
|
2021
|
+
if (active) {
|
|
2022
|
+
if (!owners) {
|
|
2023
|
+
owners = /* @__PURE__ */ new Set();
|
|
2024
|
+
this.deliveryOwnersByPhysicalSession.set(physicalSessionId, owners);
|
|
2025
|
+
}
|
|
2026
|
+
owners.add(owner);
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
if (!owners)
|
|
2030
|
+
return;
|
|
2031
|
+
owners.delete(owner);
|
|
2032
|
+
if (owners.size === 0)
|
|
2033
|
+
this.deliveryOwnersByPhysicalSession.delete(physicalSessionId);
|
|
2034
|
+
}
|
|
2035
|
+
notifyRequestAvailable(physicalSessionId) {
|
|
2036
|
+
for (const listener of this.requestAvailableListeners) {
|
|
2037
|
+
try {
|
|
2038
|
+
listener(physicalSessionId);
|
|
2039
|
+
} catch {
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
notifyRequestCancelled(request, reason) {
|
|
2044
|
+
const physicalSessionId = request.lastDeliveryPhysicalSessionId;
|
|
2045
|
+
if (!physicalSessionId || this.pendingCancellations.has(request.id))
|
|
2046
|
+
return;
|
|
2047
|
+
const now = Date.now();
|
|
2048
|
+
this.prunePendingCancellations(now);
|
|
2049
|
+
this.pendingCancellations.set(request.id, {
|
|
2050
|
+
requestId: request.id,
|
|
2051
|
+
reason,
|
|
2052
|
+
physicalSessionId,
|
|
2053
|
+
createdAt: now
|
|
2054
|
+
});
|
|
2055
|
+
this.prunePendingCancellations(now);
|
|
2056
|
+
this.notifyRequestAvailable(physicalSessionId);
|
|
2057
|
+
}
|
|
2058
|
+
physicalSessionsForTarget(targetInstanceId, targetRole) {
|
|
2059
|
+
const physicalSessionIds = /* @__PURE__ */ new Set();
|
|
2060
|
+
for (const instance of this.instances.values()) {
|
|
2061
|
+
if (instance.instanceId === targetInstanceId && instance.role === targetRole) {
|
|
2062
|
+
physicalSessionIds.add(instance.physicalSessionId);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return Array.from(physicalSessionIds);
|
|
2066
|
+
}
|
|
73
2067
|
canonicalInstanceId(instanceId, placeId) {
|
|
74
2068
|
return publishedInstanceId(placeId) ?? instanceId;
|
|
75
2069
|
}
|
|
@@ -140,7 +2134,6 @@ var BridgeService = class {
|
|
|
140
2134
|
const pluginVersion = input.pluginVersion ?? "";
|
|
141
2135
|
const pluginVariant = input.pluginVariant ?? "unknown";
|
|
142
2136
|
const serverVersion = input.serverVersion ?? "";
|
|
143
|
-
const versionMismatch = pluginVersion !== "" && serverVersion !== "" && pluginVersion !== serverVersion;
|
|
144
2137
|
this.rememberInstanceAlias(rawInstanceId, instanceId);
|
|
145
2138
|
if (prior && prior.instanceId !== instanceId) {
|
|
146
2139
|
this.rememberInstanceAlias(prior.instanceId, instanceId);
|
|
@@ -166,7 +2159,8 @@ var BridgeService = class {
|
|
|
166
2159
|
}
|
|
167
2160
|
const existing = Array.from(this.instances.values()).find((i) => i.instanceId === instanceId && i.role === assignedRole && i.pluginSessionId !== pluginSessionId);
|
|
168
2161
|
if (existing) {
|
|
169
|
-
|
|
2162
|
+
const existingDeliveryActive = (this.deliveryOwnersByPhysicalSession.get(existing.physicalSessionId)?.size ?? 0) > 0;
|
|
2163
|
+
if (!existingDeliveryActive && Date.now() - existing.lastActivity > DUPLICATE_TAKEOVER_MS) {
|
|
170
2164
|
this.unregisterInstance(existing.pluginSessionId);
|
|
171
2165
|
} else {
|
|
172
2166
|
return {
|
|
@@ -181,6 +2175,7 @@ var BridgeService = class {
|
|
|
181
2175
|
}
|
|
182
2176
|
const registered = {
|
|
183
2177
|
pluginSessionId,
|
|
2178
|
+
physicalSessionId: input.physicalSessionId,
|
|
184
2179
|
instanceId,
|
|
185
2180
|
role: assignedRole,
|
|
186
2181
|
placeId: input.placeId ?? 0,
|
|
@@ -190,29 +2185,39 @@ var BridgeService = class {
|
|
|
190
2185
|
pluginVersion,
|
|
191
2186
|
pluginVariant,
|
|
192
2187
|
serverVersion,
|
|
193
|
-
versionMismatch,
|
|
194
2188
|
lastActivity: Date.now(),
|
|
195
2189
|
connectedAt: prior?.connectedAt ?? Date.now()
|
|
196
2190
|
};
|
|
197
2191
|
this.instances.set(pluginSessionId, registered);
|
|
198
2192
|
this.notifyInstanceRegistered(toPublic(registered));
|
|
199
|
-
this.
|
|
2193
|
+
this.notifyRequestAvailable(registered.physicalSessionId);
|
|
200
2194
|
return { ok: true, assignedRole, instanceId };
|
|
201
2195
|
}
|
|
202
2196
|
unregisterInstance(pluginSessionId) {
|
|
203
2197
|
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
2198
|
if (!removed)
|
|
210
2199
|
return;
|
|
211
|
-
|
|
2200
|
+
const logicalChildren = removed.physicalSessionId === pluginSessionId ? Array.from(this.instances.values()).filter((instance) => instance.pluginSessionId !== pluginSessionId && instance.physicalSessionId === pluginSessionId) : [];
|
|
2201
|
+
this.instances.delete(pluginSessionId);
|
|
2202
|
+
const session = {
|
|
2203
|
+
logicalSessionId: pluginSessionId,
|
|
2204
|
+
physicalSessionId: removed.physicalSessionId
|
|
2205
|
+
};
|
|
2206
|
+
for (const listener of this.sessionClosedListeners) {
|
|
2207
|
+
try {
|
|
2208
|
+
listener(session);
|
|
2209
|
+
} catch {
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
if (removed.physicalSessionId === pluginSessionId) {
|
|
2213
|
+
this.deliveryOwnersByPhysicalSession.delete(pluginSessionId);
|
|
2214
|
+
}
|
|
2215
|
+
for (const child of logicalChildren)
|
|
2216
|
+
this.unregisterInstance(child.pluginSessionId);
|
|
2217
|
+
for (const req of Array.from(this.pendingRequests.values())) {
|
|
212
2218
|
const stillHasHandler = Array.from(this.instances.values()).some((i) => i.instanceId === req.targetInstanceId && i.role === req.targetRole);
|
|
213
2219
|
if (!stillHasHandler) {
|
|
214
|
-
|
|
215
|
-
this.pendingRequests.delete(id);
|
|
2220
|
+
this.removePendingRequest(req);
|
|
216
2221
|
req.reject(new Error(`Target (${req.targetInstanceId}, ${req.targetRole}) disconnected`));
|
|
217
2222
|
}
|
|
218
2223
|
}
|
|
@@ -241,9 +2246,14 @@ var BridgeService = class {
|
|
|
241
2246
|
return this.pendingRequests.size;
|
|
242
2247
|
}
|
|
243
2248
|
updateInstanceActivity(pluginSessionId) {
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
246
|
-
|
|
2249
|
+
const instance = this.instances.get(pluginSessionId);
|
|
2250
|
+
if (!instance)
|
|
2251
|
+
return;
|
|
2252
|
+
const now = Date.now();
|
|
2253
|
+
for (const candidate of this.instances.values()) {
|
|
2254
|
+
if (candidate.physicalSessionId === instance.physicalSessionId) {
|
|
2255
|
+
candidate.lastActivity = now;
|
|
2256
|
+
}
|
|
247
2257
|
}
|
|
248
2258
|
}
|
|
249
2259
|
updateInstanceMetadata(pluginSessionId, metadata) {
|
|
@@ -266,14 +2276,15 @@ var BridgeService = class {
|
|
|
266
2276
|
this.rememberInstanceAlias(priorInstanceId, canonicalInstanceId);
|
|
267
2277
|
this.migratePendingRequests(priorInstanceId, canonicalInstanceId);
|
|
268
2278
|
inst.instanceId = canonicalInstanceId;
|
|
269
|
-
this.
|
|
2279
|
+
this.notifyRequestAvailable(inst.physicalSessionId);
|
|
270
2280
|
}
|
|
271
2281
|
}
|
|
272
2282
|
}
|
|
273
2283
|
cleanupStaleInstances() {
|
|
274
2284
|
const now = Date.now();
|
|
275
2285
|
for (const [id, inst] of this.instances.entries()) {
|
|
276
|
-
|
|
2286
|
+
const deliveryActive = (this.deliveryOwnersByPhysicalSession.get(inst.physicalSessionId)?.size ?? 0) > 0;
|
|
2287
|
+
if (!deliveryActive && now - inst.lastActivity > STALE_INSTANCE_MS) {
|
|
277
2288
|
this.unregisterInstance(id);
|
|
278
2289
|
}
|
|
279
2290
|
}
|
|
@@ -389,23 +2400,28 @@ var BridgeService = class {
|
|
|
389
2400
|
};
|
|
390
2401
|
}
|
|
391
2402
|
if (distinctInstanceIds.size > 1) {
|
|
392
|
-
const
|
|
2403
|
+
const errorCode2 = role ? "ambiguous_target" : "multiple_instances_connected";
|
|
393
2404
|
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:
|
|
2405
|
+
return { ok: false, error: { code: errorCode2, message: msg, data: errorData } };
|
|
395
2406
|
}
|
|
396
2407
|
const onlyInstanceId = distinctInstanceIds.values().next().value;
|
|
397
2408
|
return this.resolveTarget({ instance_id: onlyInstanceId, target });
|
|
398
2409
|
}
|
|
399
|
-
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.requestTimeout) {
|
|
2410
|
+
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.requestTimeout, signal) {
|
|
400
2411
|
const requestId = randomUUID();
|
|
401
2412
|
const effectiveTimeoutMs = Math.max(1, timeoutMs);
|
|
2413
|
+
if (signal?.aborted)
|
|
2414
|
+
throw new Error("Request aborted");
|
|
402
2415
|
return new Promise((resolve5, reject) => {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
2416
|
+
const cancelPending = (reason, error) => {
|
|
2417
|
+
const pending = this.pendingRequests.get(requestId);
|
|
2418
|
+
if (!pending || !this.removePendingRequest(pending))
|
|
2419
|
+
return;
|
|
2420
|
+
this.notifyRequestCancelled(pending, reason);
|
|
2421
|
+
pending.reject(error);
|
|
2422
|
+
};
|
|
2423
|
+
const timeoutId = setTimeout(() => cancelPending("timeout", new Error("Request timeout")), effectiveTimeoutMs);
|
|
2424
|
+
const abortListener = () => cancelPending("aborted", new Error("Request aborted"));
|
|
409
2425
|
const request = {
|
|
410
2426
|
id: requestId,
|
|
411
2427
|
endpoint,
|
|
@@ -413,159 +2429,158 @@ var BridgeService = class {
|
|
|
413
2429
|
targetInstanceId,
|
|
414
2430
|
targetRole,
|
|
415
2431
|
timestamp: Date.now(),
|
|
416
|
-
inFlight: false,
|
|
417
2432
|
resolve: resolve5,
|
|
418
2433
|
reject,
|
|
419
2434
|
timeoutId,
|
|
420
|
-
timeoutMs: effectiveTimeoutMs
|
|
2435
|
+
timeoutMs: effectiveTimeoutMs,
|
|
2436
|
+
abortSignal: signal,
|
|
2437
|
+
abortListener
|
|
421
2438
|
};
|
|
422
2439
|
this.pendingRequests.set(requestId, request);
|
|
423
|
-
|
|
2440
|
+
signal?.addEventListener("abort", abortListener, { once: true });
|
|
2441
|
+
if (signal?.aborted)
|
|
2442
|
+
abortListener();
|
|
2443
|
+
if (this.pendingRequests.has(requestId)) {
|
|
2444
|
+
for (const physicalSessionId of this.physicalSessionsForTarget(targetInstanceId, targetRole)) {
|
|
2445
|
+
this.notifyRequestAvailable(physicalSessionId);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
424
2448
|
});
|
|
425
2449
|
}
|
|
426
|
-
|
|
427
|
-
|
|
2450
|
+
removePendingRequest(request) {
|
|
2451
|
+
if (this.pendingRequests.get(request.id) !== request)
|
|
2452
|
+
return false;
|
|
2453
|
+
clearTimeout(request.timeoutId);
|
|
2454
|
+
if (request.abortSignal && request.abortListener) {
|
|
2455
|
+
request.abortSignal.removeEventListener("abort", request.abortListener);
|
|
2456
|
+
}
|
|
2457
|
+
this.pendingRequests.delete(request.id);
|
|
2458
|
+
return true;
|
|
2459
|
+
}
|
|
2460
|
+
claimNextRequestForPhysical(physicalSessionId, claimOwner) {
|
|
2461
|
+
let oldestRequest;
|
|
2462
|
+
let logicalSessionId = "";
|
|
428
2463
|
for (const request of this.pendingRequests.values()) {
|
|
429
|
-
if (request.
|
|
2464
|
+
if (request.claimOwner !== void 0)
|
|
430
2465
|
continue;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
2466
|
+
let matchingSessionId;
|
|
2467
|
+
for (const candidate of this.instances.values()) {
|
|
2468
|
+
if (candidate.physicalSessionId === physicalSessionId && candidate.instanceId === request.targetInstanceId && candidate.role === request.targetRole) {
|
|
2469
|
+
matchingSessionId = candidate.pluginSessionId;
|
|
2470
|
+
break;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
if (!matchingSessionId)
|
|
434
2474
|
continue;
|
|
435
2475
|
if (!oldestRequest || request.timestamp < oldestRequest.timestamp) {
|
|
436
2476
|
oldestRequest = request;
|
|
2477
|
+
logicalSessionId = matchingSessionId;
|
|
437
2478
|
}
|
|
438
2479
|
}
|
|
439
|
-
if (oldestRequest)
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
|
2480
|
+
if (!oldestRequest)
|
|
2481
|
+
return null;
|
|
2482
|
+
oldestRequest.claimOwner = claimOwner;
|
|
2483
|
+
oldestRequest.lastDeliveryPhysicalSessionId = physicalSessionId;
|
|
2484
|
+
return {
|
|
2485
|
+
requestId: oldestRequest.id,
|
|
2486
|
+
logicalSessionId,
|
|
2487
|
+
target: oldestRequest.targetRole,
|
|
2488
|
+
endpoint: oldestRequest.endpoint,
|
|
2489
|
+
data: oldestRequest.data,
|
|
2490
|
+
remainingMs: Math.max(1, oldestRequest.timeoutMs - (Date.now() - oldestRequest.timestamp))
|
|
490
2491
|
};
|
|
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
2492
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (
|
|
504
|
-
this.settlePendingPoll(waiter, { kind: "session_closed" });
|
|
2493
|
+
claimNextCancellationForPhysical(physicalSessionId, claimOwner) {
|
|
2494
|
+
this.prunePendingCancellations(Date.now());
|
|
2495
|
+
for (const cancellation of this.pendingCancellations.values()) {
|
|
2496
|
+
if (cancellation.physicalSessionId !== physicalSessionId || cancellation.claimOwner !== void 0) {
|
|
505
2497
|
continue;
|
|
506
2498
|
}
|
|
507
|
-
|
|
508
|
-
|
|
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);
|
|
2499
|
+
cancellation.claimOwner = claimOwner;
|
|
2500
|
+
return { requestId: cancellation.requestId, reason: cancellation.reason };
|
|
519
2501
|
}
|
|
2502
|
+
return null;
|
|
520
2503
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
2504
|
+
releaseDeliveryClaims(claimOwner) {
|
|
2505
|
+
const physicalSessionIds = /* @__PURE__ */ new Set();
|
|
2506
|
+
for (const request of this.pendingRequests.values()) {
|
|
2507
|
+
if (request.claimOwner !== claimOwner)
|
|
2508
|
+
continue;
|
|
2509
|
+
request.claimOwner = void 0;
|
|
2510
|
+
for (const physicalSessionId of this.physicalSessionsForTarget(request.targetInstanceId, request.targetRole)) {
|
|
2511
|
+
physicalSessionIds.add(physicalSessionId);
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
for (const cancellation of this.pendingCancellations.values()) {
|
|
2515
|
+
if (cancellation.claimOwner !== claimOwner)
|
|
2516
|
+
continue;
|
|
2517
|
+
cancellation.claimOwner = void 0;
|
|
2518
|
+
physicalSessionIds.add(cancellation.physicalSessionId);
|
|
528
2519
|
}
|
|
529
|
-
|
|
530
|
-
this.
|
|
2520
|
+
for (const physicalSessionId of physicalSessionIds) {
|
|
2521
|
+
this.notifyRequestAvailable(physicalSessionId);
|
|
531
2522
|
}
|
|
532
|
-
waiter.resolve(result);
|
|
533
2523
|
}
|
|
534
2524
|
resolveRequest(requestId, response) {
|
|
535
|
-
|
|
536
|
-
if (request) {
|
|
537
|
-
clearTimeout(request.timeoutId);
|
|
538
|
-
this.pendingRequests.delete(requestId);
|
|
539
|
-
request.resolve(response);
|
|
540
|
-
}
|
|
2525
|
+
return this.settleRequest(requestId, (request) => request.resolve(response));
|
|
541
2526
|
}
|
|
542
2527
|
rejectRequest(requestId, error) {
|
|
2528
|
+
return this.settleRequest(requestId, (request) => request.reject(error));
|
|
2529
|
+
}
|
|
2530
|
+
settleRequest(requestId, settle) {
|
|
2531
|
+
const now = Date.now();
|
|
2532
|
+
this.pruneAcceptedRequestIds(now);
|
|
543
2533
|
const request = this.pendingRequests.get(requestId);
|
|
544
|
-
if (request) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
2534
|
+
if (!request) {
|
|
2535
|
+
return this.acceptedRequestIds.has(requestId) ? "already_settled" : "unknown";
|
|
2536
|
+
}
|
|
2537
|
+
this.removePendingRequest(request);
|
|
2538
|
+
this.acceptedRequestIds.set(requestId, now);
|
|
2539
|
+
this.pruneAcceptedRequestIds(now);
|
|
2540
|
+
settle(request);
|
|
2541
|
+
return "accepted";
|
|
2542
|
+
}
|
|
2543
|
+
pruneAcceptedRequestIds(now) {
|
|
2544
|
+
for (const [requestId, acceptedAt] of this.acceptedRequestIds) {
|
|
2545
|
+
if (now - acceptedAt < ACCEPTED_REQUEST_TOMBSTONE_TTL_MS)
|
|
2546
|
+
break;
|
|
2547
|
+
this.acceptedRequestIds.delete(requestId);
|
|
2548
|
+
}
|
|
2549
|
+
while (this.acceptedRequestIds.size > MAX_ACCEPTED_REQUEST_TOMBSTONES) {
|
|
2550
|
+
const oldestRequestId = this.acceptedRequestIds.keys().next().value;
|
|
2551
|
+
if (oldestRequestId === void 0)
|
|
2552
|
+
break;
|
|
2553
|
+
this.acceptedRequestIds.delete(oldestRequestId);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
prunePendingCancellations(now) {
|
|
2557
|
+
for (const [requestId, cancellation] of this.pendingCancellations) {
|
|
2558
|
+
if (now - cancellation.createdAt < CANCELLATION_TOMBSTONE_TTL_MS)
|
|
2559
|
+
break;
|
|
2560
|
+
this.pendingCancellations.delete(requestId);
|
|
2561
|
+
}
|
|
2562
|
+
while (this.pendingCancellations.size > MAX_CANCELLATION_TOMBSTONES) {
|
|
2563
|
+
const oldestRequestId = this.pendingCancellations.keys().next().value;
|
|
2564
|
+
if (oldestRequestId === void 0)
|
|
2565
|
+
break;
|
|
2566
|
+
this.pendingCancellations.delete(oldestRequestId);
|
|
548
2567
|
}
|
|
549
2568
|
}
|
|
550
2569
|
cleanupOldRequests() {
|
|
551
2570
|
const now = Date.now();
|
|
552
|
-
for (const
|
|
553
|
-
if (now - request.timestamp > request.timeoutMs) {
|
|
554
|
-
|
|
555
|
-
this.pendingRequests.delete(id);
|
|
2571
|
+
for (const request of this.pendingRequests.values()) {
|
|
2572
|
+
if (now - request.timestamp > request.timeoutMs && this.removePendingRequest(request)) {
|
|
2573
|
+
this.notifyRequestCancelled(request, "timeout");
|
|
556
2574
|
request.reject(new Error("Request timeout"));
|
|
557
2575
|
}
|
|
558
2576
|
}
|
|
559
2577
|
}
|
|
560
2578
|
clearAllPendingRequests() {
|
|
561
|
-
for (const
|
|
562
|
-
|
|
2579
|
+
for (const request of Array.from(this.pendingRequests.values())) {
|
|
2580
|
+
this.removePendingRequest(request);
|
|
563
2581
|
request.reject(new Error("Connection closed"));
|
|
564
2582
|
}
|
|
565
|
-
this.
|
|
566
|
-
for (const waiter of Array.from(this.pendingPolls.values())) {
|
|
567
|
-
this.settlePendingPoll(waiter, { kind: "bridge_closed" });
|
|
568
|
-
}
|
|
2583
|
+
this.pendingCancellations.clear();
|
|
569
2584
|
}
|
|
570
2585
|
};
|
|
571
2586
|
|
|
@@ -3377,8 +5392,7 @@ var INTERNAL_RESULT_KEYS = /* @__PURE__ */ new Set([
|
|
|
3377
5392
|
"pluginVariant",
|
|
3378
5393
|
"pluginVersion",
|
|
3379
5394
|
"requestId",
|
|
3380
|
-
"serverVersion"
|
|
3381
|
-
"versionMismatch"
|
|
5395
|
+
"serverVersion"
|
|
3382
5396
|
]);
|
|
3383
5397
|
var TEXT_RESULT_TOOLS = /* @__PURE__ */ new Set(["get_roblox_docs"]);
|
|
3384
5398
|
var SIDE_EFFECTING_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -3591,9 +5605,9 @@ function createToolServer(options) {
|
|
|
3591
5605
|
inputSchema: fromJsonSchema(publicDefinition.inputSchema),
|
|
3592
5606
|
...publicDefinition.outputSchema ? { outputSchema: fromJsonSchema(publicDefinition.outputSchema) } : {},
|
|
3593
5607
|
annotations: publicDefinition.annotations
|
|
3594
|
-
}, async (args) => {
|
|
5608
|
+
}, async (args, context) => {
|
|
3595
5609
|
try {
|
|
3596
|
-
const raw = await options.invoke(options.getTools(), definition.name, args);
|
|
5610
|
+
const raw = await options.invoke(options.getTools(), definition.name, args, { signal: context.mcpReq.signal });
|
|
3597
5611
|
return normalizeToolResult(raw, options.era);
|
|
3598
5612
|
} catch (error) {
|
|
3599
5613
|
return normalizeToolResult({
|
|
@@ -3649,12 +5663,196 @@ function resolveAuthToken() {
|
|
|
3649
5663
|
console.error(`[auth] Could not read/create ${filePath} (${err2 instanceof Error ? err2.message : err2}). Using an in-memory token for this process; set ROBLOX_STUDIO_AUTH_TOKEN to share one across sessions.`);
|
|
3650
5664
|
return { token: randomBytes(32).toString("hex"), source: "file" };
|
|
3651
5665
|
}
|
|
3652
|
-
}
|
|
3653
|
-
function tokensMatch(provided, expected) {
|
|
3654
|
-
const a = createHash("sha256").update(provided).digest();
|
|
3655
|
-
const b = createHash("sha256").update(expected).digest();
|
|
3656
|
-
return timingSafeEqual(a, b);
|
|
3657
|
-
}
|
|
5666
|
+
}
|
|
5667
|
+
function tokensMatch(provided, expected) {
|
|
5668
|
+
const a = createHash("sha256").update(provided).digest();
|
|
5669
|
+
const b = createHash("sha256").update(expected).digest();
|
|
5670
|
+
return timingSafeEqual(a, b);
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5673
|
+
// ../core/dist/studio-transport.js
|
|
5674
|
+
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
5675
|
+
var MAX_ACTIVE_EVENT_STREAMS = 64;
|
|
5676
|
+
var SseStudioTransport = class {
|
|
5677
|
+
queue;
|
|
5678
|
+
streams = /* @__PURE__ */ new Map();
|
|
5679
|
+
unsubscribeRequestAvailable;
|
|
5680
|
+
unsubscribeSessionClosed;
|
|
5681
|
+
nextGeneration = 0;
|
|
5682
|
+
constructor(queue) {
|
|
5683
|
+
this.queue = queue;
|
|
5684
|
+
this.unsubscribeRequestAvailable = queue.onRequestAvailable((physicalSessionId) => {
|
|
5685
|
+
const stream = this.streams.get(physicalSessionId);
|
|
5686
|
+
if (stream)
|
|
5687
|
+
this.pump(stream);
|
|
5688
|
+
});
|
|
5689
|
+
this.unsubscribeSessionClosed = queue.onSessionClosed((route) => {
|
|
5690
|
+
if (route.logicalSessionId === route.physicalSessionId) {
|
|
5691
|
+
this.closePhysical(route.physicalSessionId);
|
|
5692
|
+
}
|
|
5693
|
+
});
|
|
5694
|
+
}
|
|
5695
|
+
get activeStreamCount() {
|
|
5696
|
+
return this.streams.size;
|
|
5697
|
+
}
|
|
5698
|
+
canOpen(physicalSessionId) {
|
|
5699
|
+
return this.streams.has(physicalSessionId) || this.streams.size < MAX_ACTIVE_EVENT_STREAMS;
|
|
5700
|
+
}
|
|
5701
|
+
open(physicalSessionId, sink, status) {
|
|
5702
|
+
if (!this.canOpen(physicalSessionId))
|
|
5703
|
+
return void 0;
|
|
5704
|
+
this.nextGeneration += 1;
|
|
5705
|
+
const claimOwner = `sse:${physicalSessionId}:${this.nextGeneration}`;
|
|
5706
|
+
const stream = {
|
|
5707
|
+
physicalSessionId,
|
|
5708
|
+
claimOwner,
|
|
5709
|
+
sink,
|
|
5710
|
+
status,
|
|
5711
|
+
closed: false,
|
|
5712
|
+
blocked: false,
|
|
5713
|
+
statusPending: true,
|
|
5714
|
+
onClose: () => this.closeStream(stream),
|
|
5715
|
+
onDrain: () => {
|
|
5716
|
+
if (stream.closed)
|
|
5717
|
+
return;
|
|
5718
|
+
stream.blocked = false;
|
|
5719
|
+
this.pump(stream);
|
|
5720
|
+
}
|
|
5721
|
+
};
|
|
5722
|
+
this.queue.setDeliveryActive(physicalSessionId, claimOwner, true);
|
|
5723
|
+
this.queue.updateInstanceActivity(physicalSessionId);
|
|
5724
|
+
const replaced = this.streams.get(physicalSessionId);
|
|
5725
|
+
if (replaced)
|
|
5726
|
+
this.closeStream(replaced, true);
|
|
5727
|
+
this.streams.set(physicalSessionId, stream);
|
|
5728
|
+
sink.on("close", stream.onClose);
|
|
5729
|
+
sink.on("error", stream.onClose);
|
|
5730
|
+
sink.on("drain", stream.onDrain);
|
|
5731
|
+
stream.heartbeatTimer = setInterval(() => {
|
|
5732
|
+
if (!stream.closed && !stream.blocked) {
|
|
5733
|
+
this.queue.updateInstanceActivity(physicalSessionId);
|
|
5734
|
+
stream.statusPending = true;
|
|
5735
|
+
this.pump(stream);
|
|
5736
|
+
if (!stream.blocked) {
|
|
5737
|
+
this.write(stream, { kind: "heartbeat", timestamp: Date.now() });
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
5741
|
+
stream.heartbeatTimer.unref();
|
|
5742
|
+
this.pump(stream);
|
|
5743
|
+
return {
|
|
5744
|
+
physicalSessionId,
|
|
5745
|
+
close: () => this.closeStream(stream, true)
|
|
5746
|
+
};
|
|
5747
|
+
}
|
|
5748
|
+
refreshStatus(physicalSessionId) {
|
|
5749
|
+
if (physicalSessionId !== void 0) {
|
|
5750
|
+
const stream = this.streams.get(physicalSessionId);
|
|
5751
|
+
if (stream) {
|
|
5752
|
+
stream.lastStatusJson = void 0;
|
|
5753
|
+
stream.statusPending = true;
|
|
5754
|
+
this.pump(stream);
|
|
5755
|
+
}
|
|
5756
|
+
return;
|
|
5757
|
+
}
|
|
5758
|
+
for (const stream of this.streams.values()) {
|
|
5759
|
+
stream.lastStatusJson = void 0;
|
|
5760
|
+
stream.statusPending = true;
|
|
5761
|
+
this.pump(stream);
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
closePhysical(physicalSessionId) {
|
|
5765
|
+
const stream = this.streams.get(physicalSessionId);
|
|
5766
|
+
if (stream)
|
|
5767
|
+
this.closeStream(stream, true);
|
|
5768
|
+
}
|
|
5769
|
+
close() {
|
|
5770
|
+
for (const stream of Array.from(this.streams.values())) {
|
|
5771
|
+
this.closeStream(stream, true);
|
|
5772
|
+
}
|
|
5773
|
+
this.unsubscribeRequestAvailable();
|
|
5774
|
+
this.unsubscribeSessionClosed();
|
|
5775
|
+
}
|
|
5776
|
+
pump(stream) {
|
|
5777
|
+
if (stream.closed || stream.blocked || this.streams.get(stream.physicalSessionId) !== stream)
|
|
5778
|
+
return;
|
|
5779
|
+
if (stream.statusPending) {
|
|
5780
|
+
stream.statusPending = false;
|
|
5781
|
+
let status;
|
|
5782
|
+
try {
|
|
5783
|
+
status = stream.status();
|
|
5784
|
+
} catch {
|
|
5785
|
+
this.closeStream(stream);
|
|
5786
|
+
return;
|
|
5787
|
+
}
|
|
5788
|
+
const statusJson = JSON.stringify(status);
|
|
5789
|
+
if (statusJson !== stream.lastStatusJson) {
|
|
5790
|
+
stream.lastStatusJson = statusJson;
|
|
5791
|
+
if (!this.write(stream, status))
|
|
5792
|
+
return;
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
while (!stream.closed && !stream.blocked) {
|
|
5796
|
+
const cancellation = this.queue.claimNextCancellationForPhysical(stream.physicalSessionId, stream.claimOwner);
|
|
5797
|
+
if (!cancellation)
|
|
5798
|
+
break;
|
|
5799
|
+
if (!this.write(stream, { kind: "cancel", ...cancellation }))
|
|
5800
|
+
return;
|
|
5801
|
+
}
|
|
5802
|
+
while (!stream.closed && !stream.blocked) {
|
|
5803
|
+
const request = this.queue.claimNextRequestForPhysical(stream.physicalSessionId, stream.claimOwner);
|
|
5804
|
+
if (!request)
|
|
5805
|
+
return;
|
|
5806
|
+
const event = {
|
|
5807
|
+
kind: "request",
|
|
5808
|
+
requestId: request.requestId,
|
|
5809
|
+
logicalSessionId: request.logicalSessionId,
|
|
5810
|
+
target: request.target,
|
|
5811
|
+
endpoint: request.endpoint,
|
|
5812
|
+
data: request.data === void 0 ? null : request.data,
|
|
5813
|
+
remainingMs: request.remainingMs
|
|
5814
|
+
};
|
|
5815
|
+
if (!this.write(stream, event))
|
|
5816
|
+
return;
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
write(stream, event) {
|
|
5820
|
+
if (stream.closed)
|
|
5821
|
+
return false;
|
|
5822
|
+
try {
|
|
5823
|
+
const writable = stream.sink.write(`data: ${JSON.stringify(event)}
|
|
5824
|
+
|
|
5825
|
+
`);
|
|
5826
|
+
if (!writable)
|
|
5827
|
+
stream.blocked = true;
|
|
5828
|
+
return writable;
|
|
5829
|
+
} catch {
|
|
5830
|
+
this.closeStream(stream);
|
|
5831
|
+
return false;
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
closeStream(stream, endSink = false) {
|
|
5835
|
+
if (stream.closed)
|
|
5836
|
+
return;
|
|
5837
|
+
stream.closed = true;
|
|
5838
|
+
clearInterval(stream.heartbeatTimer);
|
|
5839
|
+
stream.sink.removeListener("close", stream.onClose);
|
|
5840
|
+
stream.sink.removeListener("error", stream.onClose);
|
|
5841
|
+
stream.sink.removeListener("drain", stream.onDrain);
|
|
5842
|
+
if (this.streams.get(stream.physicalSessionId) === stream) {
|
|
5843
|
+
this.streams.delete(stream.physicalSessionId);
|
|
5844
|
+
}
|
|
5845
|
+
this.queue.updateInstanceActivity(stream.physicalSessionId);
|
|
5846
|
+
this.queue.setDeliveryActive(stream.physicalSessionId, stream.claimOwner, false);
|
|
5847
|
+
this.queue.releaseDeliveryClaims(stream.claimOwner);
|
|
5848
|
+
if (endSink) {
|
|
5849
|
+
try {
|
|
5850
|
+
stream.sink.end();
|
|
5851
|
+
} catch {
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
};
|
|
3658
5856
|
|
|
3659
5857
|
// ../core/dist/http-server.js
|
|
3660
5858
|
function parseLineRange(lineRange) {
|
|
@@ -3713,7 +5911,7 @@ var TOOL_HANDLERS = {
|
|
|
3713
5911
|
get_instance_properties: (tools, body) => tools.getInstanceProperties(body.instancePath, body.excludeSource, body.instance_id),
|
|
3714
5912
|
get_project_structure: (tools, body) => tools.getProjectStructure(body.path, body.maxDepth, body.scriptsOnly, body.instance_id),
|
|
3715
5913
|
set_properties: (tools, body) => tools.setProperties(body.instancePath, body.properties, body.instance_id),
|
|
3716
|
-
grep_scripts: (tools, body) => tools.grepScripts(body.pattern, {
|
|
5914
|
+
grep_scripts: (tools, body, context) => tools.grepScripts(body.pattern, {
|
|
3717
5915
|
caseSensitive: body.caseSensitive,
|
|
3718
5916
|
usePattern: body.usePattern,
|
|
3719
5917
|
contextLines: body.contextLines,
|
|
@@ -3722,7 +5920,7 @@ var TOOL_HANDLERS = {
|
|
|
3722
5920
|
filesOnly: body.filesOnly,
|
|
3723
5921
|
path: body.path,
|
|
3724
5922
|
classFilter: body.classFilter
|
|
3725
|
-
}, body.instance_id),
|
|
5923
|
+
}, body.instance_id, context?.signal),
|
|
3726
5924
|
get_script_source: (tools, body) => {
|
|
3727
5925
|
const { startLine, endLine } = optionalLineRange(body, "get_script_source");
|
|
3728
5926
|
return tools.getScriptSource(body.instancePath, startLine, endLine, body.instance_id);
|
|
@@ -3812,26 +6010,26 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3812
6010
|
let lastMCPActivity = 0;
|
|
3813
6011
|
let mcpServerStartTime = 0;
|
|
3814
6012
|
const proxyInstances = /* @__PURE__ */ new Set();
|
|
3815
|
-
const
|
|
3816
|
-
|
|
3817
|
-
|
|
6013
|
+
const rejectedVersionSessions = /* @__PURE__ */ new Set();
|
|
6014
|
+
const eventTransport = new SseStudioTransport(bridge);
|
|
6015
|
+
const eventStreamHandles = /* @__PURE__ */ new Set();
|
|
3818
6016
|
const setMCPServerActive = (active) => {
|
|
3819
6017
|
mcpServerActive = active;
|
|
3820
6018
|
if (active) {
|
|
3821
|
-
if (activePollEpoch.signal.aborted) {
|
|
3822
|
-
activePollEpoch = new AbortController();
|
|
3823
|
-
}
|
|
3824
6019
|
mcpServerStartTime = Date.now();
|
|
3825
6020
|
lastMCPActivity = Date.now();
|
|
3826
6021
|
} else {
|
|
3827
|
-
activePollEpoch.abort();
|
|
3828
6022
|
mcpServerStartTime = 0;
|
|
3829
6023
|
lastMCPActivity = 0;
|
|
3830
6024
|
}
|
|
6025
|
+
eventTransport.refreshStatus();
|
|
3831
6026
|
};
|
|
3832
6027
|
const trackMCPActivity = () => {
|
|
3833
6028
|
if (mcpServerActive) {
|
|
6029
|
+
const wasConnected = Date.now() - lastMCPActivity < 3e4;
|
|
3834
6030
|
lastMCPActivity = Date.now();
|
|
6031
|
+
if (!wasConnected)
|
|
6032
|
+
eventTransport.refreshStatus();
|
|
3835
6033
|
}
|
|
3836
6034
|
};
|
|
3837
6035
|
const isMCPServerActive = () => {
|
|
@@ -3839,6 +6037,18 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3839
6037
|
return false;
|
|
3840
6038
|
return Date.now() - lastMCPActivity < 3e4;
|
|
3841
6039
|
};
|
|
6040
|
+
const eventStatus = (physicalSessionId) => {
|
|
6041
|
+
const instance = bridge.getInstanceBySessionId(physicalSessionId);
|
|
6042
|
+
const knownInstance = instance?.physicalSessionId === physicalSessionId;
|
|
6043
|
+
return {
|
|
6044
|
+
kind: "status",
|
|
6045
|
+
knownInstance,
|
|
6046
|
+
mcpConnected: isMCPServerActive(),
|
|
6047
|
+
serverVersion: serverConfig?.version,
|
|
6048
|
+
pluginVersion: instance?.pluginVersion,
|
|
6049
|
+
pluginVariant: instance?.pluginVariant
|
|
6050
|
+
};
|
|
6051
|
+
};
|
|
3842
6052
|
const isPluginConnected = () => {
|
|
3843
6053
|
return bridge.getInstances().length > 0;
|
|
3844
6054
|
};
|
|
@@ -3908,17 +6118,18 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3908
6118
|
pluginConnected: instances.length > 0,
|
|
3909
6119
|
instanceCount: instances.length,
|
|
3910
6120
|
instances: publicInstances,
|
|
3911
|
-
versionMismatch: publicInstances.some((inst) => inst.versionMismatch),
|
|
3912
6121
|
mcpServerActive: isMCPServerActive(),
|
|
3913
6122
|
uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0,
|
|
3914
6123
|
pendingRequests: bridge.getPendingRequestCount(),
|
|
3915
6124
|
proxyInstanceCount: proxyInstances.size,
|
|
6125
|
+
activeEventStreams: eventTransport.activeStreamCount,
|
|
3916
6126
|
streamableHttp: !!serverConfig
|
|
3917
6127
|
});
|
|
3918
6128
|
});
|
|
3919
6129
|
app.post("/ready", (req, res) => {
|
|
3920
|
-
const { pluginSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning, pluginVersion, pluginVariant } = req.body;
|
|
6130
|
+
const { pluginSessionId, physicalSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning, pluginVersion, pluginVariant } = req.body;
|
|
3921
6131
|
const requestContext = {
|
|
6132
|
+
physicalSessionId: typeof physicalSessionId === "string" ? physicalSessionId : void 0,
|
|
3922
6133
|
instanceId: typeof instanceId === "string" ? instanceId : void 0,
|
|
3923
6134
|
role: typeof role === "string" ? role : void 0,
|
|
3924
6135
|
placeId: typeof placeId === "number" ? placeId : void 0,
|
|
@@ -3928,12 +6139,15 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3928
6139
|
pluginVersion: typeof pluginVersion === "string" ? pluginVersion : void 0,
|
|
3929
6140
|
pluginVariant: typeof pluginVariant === "string" ? pluginVariant : void 0
|
|
3930
6141
|
};
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
6142
|
+
const missingFields = [
|
|
6143
|
+
typeof pluginSessionId !== "string" || pluginSessionId === "" ? "pluginSessionId" : void 0,
|
|
6144
|
+
typeof physicalSessionId !== "string" || physicalSessionId === "" ? "physicalSessionId" : void 0,
|
|
6145
|
+
typeof instanceId !== "string" || instanceId === "" ? "instanceId" : void 0,
|
|
6146
|
+
typeof role !== "string" || role === "" ? "role" : void 0,
|
|
6147
|
+
typeof pluginVersion !== "string" || pluginVersion === "" ? "pluginVersion" : void 0,
|
|
6148
|
+
typeof pluginVariant !== "string" || pluginVariant === "" ? "pluginVariant" : void 0
|
|
6149
|
+
].filter((field) => !!field);
|
|
6150
|
+
if (missingFields.length > 0) {
|
|
3937
6151
|
res.status(400).json({
|
|
3938
6152
|
success: false,
|
|
3939
6153
|
error: "missing_ready_fields",
|
|
@@ -3943,19 +6157,71 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3943
6157
|
});
|
|
3944
6158
|
return;
|
|
3945
6159
|
}
|
|
6160
|
+
const serverVersion = serverConfig?.version;
|
|
6161
|
+
if (!serverVersion) {
|
|
6162
|
+
res.status(503).json({
|
|
6163
|
+
success: false,
|
|
6164
|
+
error: "server_version_unavailable",
|
|
6165
|
+
message: "The MCP server cannot accept Studio connections without a configured version.",
|
|
6166
|
+
request: requestContext
|
|
6167
|
+
});
|
|
6168
|
+
return;
|
|
6169
|
+
}
|
|
6170
|
+
if (pluginVersion !== serverVersion) {
|
|
6171
|
+
if (!rejectedVersionSessions.has(pluginSessionId)) {
|
|
6172
|
+
if (rejectedVersionSessions.size >= 256)
|
|
6173
|
+
rejectedVersionSessions.clear();
|
|
6174
|
+
rejectedVersionSessions.add(pluginSessionId);
|
|
6175
|
+
console.error(`[plugin-version-rejected] Studio plugin v${pluginVersion} (${pluginVariant}) does not match MCP server v${serverVersion} for ${instanceId}/${role}`);
|
|
6176
|
+
}
|
|
6177
|
+
res.status(426).json({
|
|
6178
|
+
success: false,
|
|
6179
|
+
error: "plugin_version_mismatch",
|
|
6180
|
+
message: `Studio plugin v${pluginVersion} does not match MCP server v${serverVersion}.`,
|
|
6181
|
+
pluginVersion,
|
|
6182
|
+
serverVersion,
|
|
6183
|
+
request: requestContext
|
|
6184
|
+
});
|
|
6185
|
+
return;
|
|
6186
|
+
}
|
|
6187
|
+
const isClientRole = role === "client" || /^client-[1-9]\d*$/.test(role);
|
|
6188
|
+
const isLogicalSession = physicalSessionId !== pluginSessionId;
|
|
6189
|
+
if (isLogicalSession && !isClientRole || !isLogicalSession && isClientRole || !isClientRole && role !== "edit" && role !== "server") {
|
|
6190
|
+
res.status(400).json({
|
|
6191
|
+
success: false,
|
|
6192
|
+
error: "invalid_session_topology",
|
|
6193
|
+
message: "Physical sessions must use the edit or server role; client roles must use a distinct physical server session.",
|
|
6194
|
+
request: requestContext
|
|
6195
|
+
});
|
|
6196
|
+
return;
|
|
6197
|
+
}
|
|
6198
|
+
if (isLogicalSession) {
|
|
6199
|
+
const physicalOwner = bridge.getInstanceBySessionId(physicalSessionId);
|
|
6200
|
+
const requestedInstanceId = typeof placeId === "number" && Number.isFinite(placeId) && placeId > 0 ? `place:${Math.trunc(placeId)}` : bridge.resolveInstanceId(instanceId);
|
|
6201
|
+
if (!physicalOwner || physicalOwner.pluginSessionId !== physicalSessionId || physicalOwner.physicalSessionId !== physicalSessionId || physicalOwner.role !== "server" || physicalOwner.instanceId !== requestedInstanceId) {
|
|
6202
|
+
res.status(409).json({
|
|
6203
|
+
success: false,
|
|
6204
|
+
error: "physical_session_unavailable",
|
|
6205
|
+
message: "A logical client requires a registered physical server session for the same Studio instance.",
|
|
6206
|
+
request: requestContext
|
|
6207
|
+
});
|
|
6208
|
+
return;
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
3946
6211
|
let result;
|
|
3947
6212
|
try {
|
|
3948
6213
|
result = bridge.registerInstance({
|
|
3949
6214
|
pluginSessionId,
|
|
6215
|
+
physicalSessionId,
|
|
3950
6216
|
instanceId,
|
|
3951
6217
|
role,
|
|
3952
6218
|
placeId: typeof placeId === "number" ? placeId : 0,
|
|
3953
6219
|
placeName: typeof placeName === "string" ? placeName : "",
|
|
3954
6220
|
dataModelName: typeof dataModelName === "string" ? dataModelName : "",
|
|
3955
6221
|
isRunning: !!isRunning,
|
|
3956
|
-
pluginVersion
|
|
3957
|
-
pluginVariant
|
|
3958
|
-
serverVersion
|
|
6222
|
+
pluginVersion,
|
|
6223
|
+
pluginVariant,
|
|
6224
|
+
serverVersion
|
|
3959
6225
|
});
|
|
3960
6226
|
} catch (err2) {
|
|
3961
6227
|
res.status(500).json({
|
|
@@ -3976,17 +6242,12 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
3976
6242
|
});
|
|
3977
6243
|
return;
|
|
3978
6244
|
}
|
|
3979
|
-
|
|
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
|
-
}
|
|
6245
|
+
eventTransport.refreshStatus(physicalSessionId);
|
|
3984
6246
|
res.json({
|
|
3985
6247
|
success: true,
|
|
3986
6248
|
assignedRole: result.assignedRole,
|
|
3987
6249
|
instanceId: result.instanceId,
|
|
3988
|
-
serverVersion
|
|
3989
|
-
versionMismatch: registered?.versionMismatch ?? false
|
|
6250
|
+
serverVersion
|
|
3990
6251
|
});
|
|
3991
6252
|
});
|
|
3992
6253
|
app.post("/disconnect", (req, res) => {
|
|
@@ -4013,7 +6274,6 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
4013
6274
|
instanceCount: instances.length,
|
|
4014
6275
|
instances: publicInstances,
|
|
4015
6276
|
serverVersion: serverConfig?.version,
|
|
4016
|
-
versionMismatch: publicInstances.some((inst) => inst.versionMismatch),
|
|
4017
6277
|
mcpServerActive: isMCPServerActive(),
|
|
4018
6278
|
lastMCPActivity,
|
|
4019
6279
|
uptime: mcpServerActive ? Date.now() - mcpServerStartTime : 0
|
|
@@ -4023,205 +6283,112 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
4023
6283
|
const instances = bridge.getInstances();
|
|
4024
6284
|
res.json({
|
|
4025
6285
|
instances,
|
|
4026
|
-
serverVersion: serverConfig?.version
|
|
4027
|
-
versionMismatch: instances.some((inst) => inst.versionMismatch)
|
|
6286
|
+
serverVersion: serverConfig?.version
|
|
4028
6287
|
});
|
|
4029
6288
|
});
|
|
4030
|
-
app.get("/
|
|
6289
|
+
app.get("/events", (req, res) => {
|
|
4031
6290
|
const pluginSessionId = typeof req.query.pluginSessionId === "string" ? req.query.pluginSessionId : void 0;
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
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
|
|
6291
|
+
if (!pluginSessionId) {
|
|
6292
|
+
res.status(400).json({
|
|
6293
|
+
error: "missing_plugin_session_id",
|
|
6294
|
+
message: "pluginSessionId is required"
|
|
4057
6295
|
});
|
|
4058
6296
|
return;
|
|
4059
6297
|
}
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
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
|
|
6298
|
+
const instance = bridge.getInstanceBySessionId(pluginSessionId);
|
|
6299
|
+
if (!instance) {
|
|
6300
|
+
res.status(404).json({
|
|
6301
|
+
error: "unknown_session",
|
|
6302
|
+
knownInstance: false
|
|
4072
6303
|
});
|
|
4073
6304
|
return;
|
|
4074
6305
|
}
|
|
4075
|
-
if (
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
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
|
|
6306
|
+
if (instance.physicalSessionId !== pluginSessionId) {
|
|
6307
|
+
res.status(409).json({
|
|
6308
|
+
error: "logical_session_has_no_event_stream",
|
|
6309
|
+
physicalSessionId: instance.physicalSessionId
|
|
4090
6310
|
});
|
|
4091
6311
|
return;
|
|
4092
6312
|
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
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"
|
|
6313
|
+
if (!eventTransport.canOpen(pluginSessionId)) {
|
|
6314
|
+
res.setHeader("Retry-After", "1");
|
|
6315
|
+
res.status(503).json({
|
|
6316
|
+
error: "event_stream_capacity_reached",
|
|
6317
|
+
capacity: MAX_ACTIVE_EVENT_STREAMS
|
|
4185
6318
|
});
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
6319
|
+
return;
|
|
6320
|
+
}
|
|
6321
|
+
bridge.updateInstanceActivity(pluginSessionId);
|
|
6322
|
+
res.status(200);
|
|
6323
|
+
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
6324
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
6325
|
+
res.setHeader("Connection", "keep-alive");
|
|
6326
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
6327
|
+
res.flushHeaders();
|
|
6328
|
+
const handle = eventTransport.open(pluginSessionId, res, () => eventStatus(pluginSessionId));
|
|
6329
|
+
if (!handle) {
|
|
6330
|
+
res.end();
|
|
6331
|
+
return;
|
|
4189
6332
|
}
|
|
6333
|
+
eventStreamHandles.add(handle);
|
|
6334
|
+
res.once("close", () => eventStreamHandles.delete(handle));
|
|
4190
6335
|
});
|
|
4191
6336
|
app.post("/response", (req, res) => {
|
|
4192
6337
|
const { requestId, response, error } = req.body;
|
|
4193
|
-
if (
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
6338
|
+
if (typeof requestId !== "string" || requestId.length === 0) {
|
|
6339
|
+
res.status(400).json({
|
|
6340
|
+
success: false,
|
|
6341
|
+
error: "invalid_request_id"
|
|
6342
|
+
});
|
|
6343
|
+
return;
|
|
4197
6344
|
}
|
|
4198
|
-
|
|
6345
|
+
const disposition = error !== void 0 ? bridge.rejectRequest(requestId, error) : bridge.resolveRequest(requestId, response);
|
|
6346
|
+
if (disposition === "unknown") {
|
|
6347
|
+
res.status(404).json({ success: false, disposition });
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
res.json({ success: true, disposition });
|
|
4199
6351
|
});
|
|
4200
6352
|
app.post("/proxy", async (req, res) => {
|
|
4201
|
-
const { endpoint, data, targetInstanceId, targetRole, proxyInstanceId } = req.body;
|
|
6353
|
+
const { endpoint, data, targetInstanceId, targetRole, proxyInstanceId, timeoutMs } = req.body;
|
|
4202
6354
|
if (!endpoint || !targetInstanceId || !targetRole) {
|
|
4203
6355
|
res.status(400).json({ error: "endpoint, targetInstanceId, and targetRole are required" });
|
|
4204
6356
|
return;
|
|
4205
6357
|
}
|
|
6358
|
+
if (timeoutMs !== void 0 && (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e5)) {
|
|
6359
|
+
res.status(400).json({ error: "timeoutMs must be an integer between 1 and 300000" });
|
|
6360
|
+
return;
|
|
6361
|
+
}
|
|
4206
6362
|
if (proxyInstanceId) {
|
|
4207
6363
|
proxyInstances.add(proxyInstanceId);
|
|
4208
6364
|
}
|
|
6365
|
+
const controller = new AbortController();
|
|
6366
|
+
const abort = () => controller.abort();
|
|
6367
|
+
req.once("aborted", abort);
|
|
6368
|
+
res.once("close", abort);
|
|
4209
6369
|
try {
|
|
4210
|
-
const response = await bridge.sendRequest(endpoint, data, targetInstanceId, targetRole);
|
|
6370
|
+
const response = await bridge.sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs, controller.signal);
|
|
4211
6371
|
res.json({ response });
|
|
4212
|
-
} catch (
|
|
4213
|
-
res.
|
|
6372
|
+
} catch (error) {
|
|
6373
|
+
if (!res.headersSent && !res.destroyed) {
|
|
6374
|
+
res.status(500).json({
|
|
6375
|
+
error: error instanceof Error ? error.message : "Proxy request failed"
|
|
6376
|
+
});
|
|
6377
|
+
}
|
|
6378
|
+
} finally {
|
|
6379
|
+
req.removeListener("aborted", abort);
|
|
6380
|
+
res.removeListener("close", abort);
|
|
4214
6381
|
}
|
|
4215
6382
|
});
|
|
4216
6383
|
const mcpHandler = serverConfig ? createToolHttpHandler({
|
|
4217
6384
|
config: serverConfig,
|
|
4218
6385
|
getTools: () => tools,
|
|
4219
6386
|
allowedTools,
|
|
4220
|
-
invoke: async (currentTools, name, args) => {
|
|
6387
|
+
invoke: async (currentTools, name, args, context) => {
|
|
4221
6388
|
const handler = TOOL_HANDLERS[name];
|
|
4222
6389
|
if (!handler)
|
|
4223
6390
|
throw new Error(`Unknown tool: ${name}`);
|
|
4224
|
-
return handler(currentTools, args);
|
|
6391
|
+
return handler(currentTools, args, context);
|
|
4225
6392
|
}
|
|
4226
6393
|
}) : void 0;
|
|
4227
6394
|
const nodeMcpHandler = mcpHandler ? toNodeHandler(mcpHandler) : void 0;
|
|
@@ -4257,27 +6424,30 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
4257
6424
|
app.isMCPServerActive = isMCPServerActive;
|
|
4258
6425
|
app.trackMCPActivity = trackMCPActivity;
|
|
4259
6426
|
app.closeMcpHandler = () => mcpHandler?.close();
|
|
6427
|
+
app.cleanup = async () => {
|
|
6428
|
+
for (const handle of eventStreamHandles)
|
|
6429
|
+
handle.close();
|
|
6430
|
+
eventStreamHandles.clear();
|
|
6431
|
+
eventTransport.close();
|
|
6432
|
+
await mcpHandler?.close();
|
|
6433
|
+
};
|
|
4260
6434
|
return app;
|
|
4261
6435
|
}
|
|
4262
|
-
function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
console.error(`Port ${port} in use, trying next...`);
|
|
4273
|
-
continue;
|
|
4274
|
-
}
|
|
4275
|
-
reject(err2);
|
|
4276
|
-
return;
|
|
6436
|
+
async function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
6437
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
6438
|
+
const port = startPort + i;
|
|
6439
|
+
try {
|
|
6440
|
+
const server = await bindPort(app, host, port);
|
|
6441
|
+
return { server, port };
|
|
6442
|
+
} catch (error) {
|
|
6443
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "EADDRINUSE") {
|
|
6444
|
+
console.error(`Port ${port} in use, trying next...`);
|
|
6445
|
+
continue;
|
|
4277
6446
|
}
|
|
6447
|
+
throw error;
|
|
4278
6448
|
}
|
|
4279
|
-
|
|
4280
|
-
});
|
|
6449
|
+
}
|
|
6450
|
+
throw new Error(`All ports ${startPort}-${startPort + maxAttempts - 1} are in use. Stop some MCP server instances and retry.`);
|
|
4281
6451
|
}
|
|
4282
6452
|
function bindPort(app, host, port) {
|
|
4283
6453
|
return new Promise((resolve5, reject) => {
|
|
@@ -4300,9 +6470,9 @@ var StudioHttpClient = class {
|
|
|
4300
6470
|
constructor(bridge) {
|
|
4301
6471
|
this.bridge = bridge;
|
|
4302
6472
|
}
|
|
4303
|
-
async request(endpoint, data, targetInstanceId, targetRole, timeoutMs) {
|
|
6473
|
+
async request(endpoint, data, targetInstanceId, targetRole, timeoutMs, signal) {
|
|
4304
6474
|
try {
|
|
4305
|
-
const response = await this.bridge.sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs);
|
|
6475
|
+
const response = await this.bridge.sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs, signal);
|
|
4306
6476
|
return response;
|
|
4307
6477
|
} catch (error) {
|
|
4308
6478
|
if (error instanceof Error && error.message === "Request timeout") {
|
|
@@ -7106,6 +9276,7 @@ function rgbaToPng(rgba, width, height) {
|
|
|
7106
9276
|
import * as fs3 from "fs";
|
|
7107
9277
|
import * as path5 from "path";
|
|
7108
9278
|
var MAX_INLINE_IMAGE_BYTES = 6e6;
|
|
9279
|
+
var MAX_MATRIX_IMAGE_BYTES = 8e6;
|
|
7109
9280
|
var DEFAULT_ASSET_AUDIO_PREVIEWS = 3;
|
|
7110
9281
|
var MAX_ASSET_AUDIO_PREVIEWS = 5;
|
|
7111
9282
|
var MAX_INLINE_AUDIO_PREVIEW_BYTES = 3e6;
|
|
@@ -7116,6 +9287,8 @@ var MAX_SEARCH_ASSET_DESCRIPTION_LENGTH = 240;
|
|
|
7116
9287
|
var ROBLOX_CREATOR_USER_ID = 1;
|
|
7117
9288
|
var MAX_DEVICE_MATRIX_ENTRIES = 6;
|
|
7118
9289
|
var MAX_NETWORK_PACKET_LOSS_PERCENT = 0.5;
|
|
9290
|
+
var GREP_SCRIPTS_TIMEOUT_MS = 12e4;
|
|
9291
|
+
var MAX_GREP_PATTERN_UTF8_BYTES = 4096;
|
|
7119
9292
|
var STUDIO_ASSISTANT_SOURCE_IMAGE_LABEL = "Studio Assistant Source Image";
|
|
7120
9293
|
var CREATOR_STORE_SEARCH_TYPES = /* @__PURE__ */ new Set([
|
|
7121
9294
|
"Audio",
|
|
@@ -7942,7 +10115,7 @@ var RobloxStudioTools = class {
|
|
|
7942
10115
|
// ambiguous, missing, or asks for fanout on a non-fanout-capable tool —
|
|
7943
10116
|
// the MCP transport layer surfaces it as a structured error result so
|
|
7944
10117
|
// the LLM can recover via the embedded data.instances list.
|
|
7945
|
-
async _callSingle(endpoint, data, target, instance_id, timeoutMs) {
|
|
10118
|
+
async _callSingle(endpoint, data, target, instance_id, timeoutMs, signal) {
|
|
7946
10119
|
const r = this.bridge.resolveTarget({ instance_id, target });
|
|
7947
10120
|
if (!r.ok)
|
|
7948
10121
|
throw new RoutingFailure(r.error);
|
|
@@ -7956,7 +10129,10 @@ var RobloxStudioTools = class {
|
|
|
7956
10129
|
}
|
|
7957
10130
|
});
|
|
7958
10131
|
}
|
|
7959
|
-
|
|
10132
|
+
if (signal === void 0) {
|
|
10133
|
+
return this.client.request(endpoint, data, r.targetInstanceId, r.targetRole, timeoutMs);
|
|
10134
|
+
}
|
|
10135
|
+
return this.client.request(endpoint, data, r.targetInstanceId, r.targetRole, timeoutMs, signal);
|
|
7960
10136
|
}
|
|
7961
10137
|
// Resolves which connected place a tool should target and whether a playtest
|
|
7962
10138
|
// CLIENT peer is present on it. Used by capture/input to auto-route to the
|
|
@@ -8047,7 +10223,7 @@ var RobloxStudioTools = class {
|
|
|
8047
10223
|
}
|
|
8048
10224
|
_resolveDeviceSimulatorSingleTarget(target, instance_id, toolName) {
|
|
8049
10225
|
const selectedTarget = target ?? "edit";
|
|
8050
|
-
if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients"
|
|
10226
|
+
if (selectedTarget === "server" || selectedTarget === "all" || selectedTarget === "all-clients") {
|
|
8051
10227
|
throw new Error(`${toolName} target must be "edit" or "client-N" (got: ${selectedTarget})`);
|
|
8052
10228
|
}
|
|
8053
10229
|
if (selectedTarget !== "edit" && !/^client-\d+$/.test(selectedTarget)) {
|
|
@@ -8085,7 +10261,7 @@ var RobloxStudioTools = class {
|
|
|
8085
10261
|
}
|
|
8086
10262
|
_resolveSimulationTargets(target, instance_id, toolName) {
|
|
8087
10263
|
const selectedTarget = target ?? "edit-and-clients";
|
|
8088
|
-
if (selectedTarget === "server" || selectedTarget === "all"
|
|
10264
|
+
if (selectedTarget === "server" || selectedTarget === "all") {
|
|
8089
10265
|
throw new Error(`${toolName} target must be "edit", "client-N", "all-clients", or "edit-and-clients" (got: ${selectedTarget})`);
|
|
8090
10266
|
}
|
|
8091
10267
|
const instanceId = this._resolveInstanceIdOnly(instance_id);
|
|
@@ -8456,14 +10632,17 @@ var RobloxStudioTools = class {
|
|
|
8456
10632
|
]
|
|
8457
10633
|
};
|
|
8458
10634
|
}
|
|
8459
|
-
async grepScripts(pattern, options, instance_id) {
|
|
10635
|
+
async grepScripts(pattern, options, instance_id, signal) {
|
|
8460
10636
|
if (!pattern) {
|
|
8461
10637
|
throw new Error("Pattern is required for grep_scripts");
|
|
8462
10638
|
}
|
|
10639
|
+
if (Buffer.byteLength(pattern, "utf8") > MAX_GREP_PATTERN_UTF8_BYTES) {
|
|
10640
|
+
throw new Error(`Pattern must not exceed ${MAX_GREP_PATTERN_UTF8_BYTES} UTF-8 bytes`);
|
|
10641
|
+
}
|
|
8463
10642
|
const response = await this._callSingle("/api/grep-scripts", {
|
|
8464
10643
|
pattern,
|
|
8465
10644
|
...options ?? {}
|
|
8466
|
-
}, void 0, instance_id);
|
|
10645
|
+
}, void 0, instance_id, GREP_SCRIPTS_TIMEOUT_MS, signal);
|
|
8467
10646
|
return {
|
|
8468
10647
|
content: [
|
|
8469
10648
|
{
|
|
@@ -8886,6 +11065,7 @@ var RobloxStudioTools = class {
|
|
|
8886
11065
|
const entrySummaries = summary.entries;
|
|
8887
11066
|
const content = [];
|
|
8888
11067
|
const failures = [];
|
|
11068
|
+
let imageBudget = MAX_MATRIX_IMAGE_BYTES;
|
|
8889
11069
|
try {
|
|
8890
11070
|
for (let i = 0; i < matrixEntries.length; i++) {
|
|
8891
11071
|
const entry = matrixEntries[i];
|
|
@@ -8903,8 +11083,10 @@ var RobloxStudioTools = class {
|
|
|
8903
11083
|
entrySummary.applied = applied;
|
|
8904
11084
|
if (settleMs > 0)
|
|
8905
11085
|
await sleep(settleMs);
|
|
8906
|
-
const
|
|
11086
|
+
const entryBudget = Math.max(1, Math.floor(imageBudget / (matrixEntries.length - i)));
|
|
11087
|
+
const capture = await this._captureViewportImage(resolved.instanceId, resolved.role, format, quality, entryBudget);
|
|
8907
11088
|
if (capture.success) {
|
|
11089
|
+
imageBudget -= Math.ceil(capture.data.length * 3 / 4);
|
|
8908
11090
|
entrySummary.screenshot = {
|
|
8909
11091
|
width: capture.width,
|
|
8910
11092
|
height: capture.height,
|
|
@@ -9001,7 +11183,7 @@ var RobloxStudioTools = class {
|
|
|
9001
11183
|
content: [{ type: "text", text: JSON.stringify(response) }]
|
|
9002
11184
|
};
|
|
9003
11185
|
}
|
|
9004
|
-
const targets = resolved.targets
|
|
11186
|
+
const targets = resolved.targets;
|
|
9005
11187
|
const originPeerReliable = targets.length > 0 ? await this._isMultiplayerTestRunning(targets[0].targetInstanceId) : false;
|
|
9006
11188
|
const responses = await Promise.allSettled(targets.map(async (t) => {
|
|
9007
11189
|
const r = await this.client.request("/api/get-runtime-logs", data, t.targetInstanceId, t.targetRole);
|
|
@@ -10646,7 +12828,7 @@ var RobloxStudioTools = class {
|
|
|
10646
12828
|
const response = await this.client.request("/api/get-memory-breakdown", data, resolved.targetInstanceId, resolved.targetRole);
|
|
10647
12829
|
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
10648
12830
|
}
|
|
10649
|
-
const targets = resolved.targets
|
|
12831
|
+
const targets = resolved.targets;
|
|
10650
12832
|
const responses = await Promise.allSettled(targets.map(async (t) => ({
|
|
10651
12833
|
peer: t.targetRole,
|
|
10652
12834
|
result: await this.client.request("/api/get-memory-breakdown", data, t.targetInstanceId, t.targetRole)
|
|
@@ -10679,7 +12861,7 @@ var RobloxStudioTools = class {
|
|
|
10679
12861
|
const response = await this.client.request("/api/get-scene-analysis", data, resolved.targetInstanceId, resolved.targetRole);
|
|
10680
12862
|
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
10681
12863
|
}
|
|
10682
|
-
const targets = resolved.targets
|
|
12864
|
+
const targets = resolved.targets;
|
|
10683
12865
|
const responses = await Promise.allSettled(targets.map(async (t) => ({
|
|
10684
12866
|
peer: t.targetRole,
|
|
10685
12867
|
result: await this.client.request("/api/get-scene-analysis", data, t.targetInstanceId, t.targetRole)
|
|
@@ -10803,7 +12985,7 @@ var RobloxStudioTools = class {
|
|
|
10803
12985
|
}, tgt, instance_id);
|
|
10804
12986
|
return { content: [{ type: "text", text: JSON.stringify(response) }] };
|
|
10805
12987
|
}
|
|
10806
|
-
async _captureViewportImage(instanceId, targetRole, format, quality) {
|
|
12988
|
+
async _captureViewportImage(instanceId, targetRole, format, quality, maxBytes = MAX_INLINE_IMAGE_BYTES) {
|
|
10807
12989
|
let response;
|
|
10808
12990
|
if (targetRole.startsWith("client-")) {
|
|
10809
12991
|
const begin = await this._callSingle("/api/capture-begin", {}, targetRole, instanceId);
|
|
@@ -10836,21 +13018,29 @@ var RobloxStudioTools = class {
|
|
|
10836
13018
|
const { mimeType } = encoded;
|
|
10837
13019
|
let usedQ = q;
|
|
10838
13020
|
let note = "";
|
|
10839
|
-
if (buffer.length >
|
|
13021
|
+
if (buffer.length > maxBytes) {
|
|
10840
13022
|
if (fmt === "png") {
|
|
10841
13023
|
const mb = (buffer.length / 1048576).toFixed(1);
|
|
10842
13024
|
return {
|
|
10843
13025
|
success: false,
|
|
10844
|
-
error: `PNG screenshot is ${mb}MB, over the ~${(
|
|
13026
|
+
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
13027
|
};
|
|
10846
13028
|
}
|
|
10847
|
-
while (buffer.length >
|
|
13029
|
+
while (buffer.length > maxBytes && usedQ > 25) {
|
|
10848
13030
|
usedQ = Math.max(25, usedQ - 20);
|
|
10849
13031
|
buffer = encodeImageFromRgbaResponse(response, "jpeg", usedQ).buffer;
|
|
10850
13032
|
}
|
|
13033
|
+
if (buffer.length > maxBytes) {
|
|
13034
|
+
return {
|
|
13035
|
+
success: false,
|
|
13036
|
+
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.`
|
|
13037
|
+
};
|
|
13038
|
+
}
|
|
10851
13039
|
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
13040
|
}
|
|
10853
|
-
const
|
|
13041
|
+
const nativeW = response.nativeWidth ?? w;
|
|
13042
|
+
const nativeH = response.nativeHeight ?? h;
|
|
13043
|
+
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
13044
|
return {
|
|
10855
13045
|
success: true,
|
|
10856
13046
|
width: w,
|
|
@@ -10878,7 +13068,8 @@ var RobloxStudioTools = class {
|
|
|
10878
13068
|
height: capture.height,
|
|
10879
13069
|
format: capture.format,
|
|
10880
13070
|
mimeType: capture.mimeType,
|
|
10881
|
-
...capture.quality === void 0 ? {} : { quality: capture.quality }
|
|
13071
|
+
...capture.quality === void 0 ? {} : { quality: capture.quality },
|
|
13072
|
+
message: capture.message
|
|
10882
13073
|
})
|
|
10883
13074
|
},
|
|
10884
13075
|
{
|
|
@@ -10893,6 +13084,7 @@ var RobloxStudioTools = class {
|
|
|
10893
13084
|
|
|
10894
13085
|
// ../core/dist/proxy-bridge-service.js
|
|
10895
13086
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
13087
|
+
var PROXY_RESPONSE_GRACE_MS = 5e3;
|
|
10896
13088
|
var ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
10897
13089
|
primaryBaseUrl;
|
|
10898
13090
|
authToken;
|
|
@@ -10968,9 +13160,20 @@ var ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
|
10968
13160
|
this.refreshTimer = void 0;
|
|
10969
13161
|
}
|
|
10970
13162
|
}
|
|
10971
|
-
async sendRequest(endpoint, data, targetInstanceId, targetRole) {
|
|
13163
|
+
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.proxyRequestTimeout, signal) {
|
|
13164
|
+
if (signal?.aborted)
|
|
13165
|
+
throw new Error("Request aborted");
|
|
10972
13166
|
const controller = new AbortController();
|
|
10973
|
-
const
|
|
13167
|
+
const effectiveTimeoutMs = Math.max(1, timeoutMs);
|
|
13168
|
+
let timedOut = false;
|
|
13169
|
+
const abortFromCaller = () => controller.abort();
|
|
13170
|
+
signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
13171
|
+
if (signal?.aborted)
|
|
13172
|
+
controller.abort();
|
|
13173
|
+
const timeoutId = setTimeout(() => {
|
|
13174
|
+
timedOut = true;
|
|
13175
|
+
controller.abort();
|
|
13176
|
+
}, effectiveTimeoutMs + PROXY_RESPONSE_GRACE_MS);
|
|
10974
13177
|
try {
|
|
10975
13178
|
const response = await fetch(`${this.primaryBaseUrl}/proxy`, {
|
|
10976
13179
|
method: "POST",
|
|
@@ -10980,26 +13183,34 @@ var ProxyBridgeService = class _ProxyBridgeService extends BridgeService {
|
|
|
10980
13183
|
data,
|
|
10981
13184
|
targetInstanceId,
|
|
10982
13185
|
targetRole,
|
|
10983
|
-
proxyInstanceId: this.proxyInstanceId
|
|
13186
|
+
proxyInstanceId: this.proxyInstanceId,
|
|
13187
|
+
timeoutMs: effectiveTimeoutMs
|
|
10984
13188
|
}),
|
|
10985
13189
|
signal: controller.signal
|
|
10986
13190
|
});
|
|
10987
|
-
clearTimeout(timeoutId);
|
|
10988
13191
|
if (!response.ok) {
|
|
10989
13192
|
const body = await response.text();
|
|
10990
13193
|
throw new Error(`Proxy request failed (${response.status}): ${body}`);
|
|
10991
13194
|
}
|
|
10992
13195
|
const result = await response.json();
|
|
10993
|
-
if (result.
|
|
13196
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
13197
|
+
throw new Error("Proxy returned an invalid response");
|
|
13198
|
+
}
|
|
13199
|
+
if ("error" in result && typeof result.error === "string" && result.error.length > 0) {
|
|
10994
13200
|
throw new Error(result.error);
|
|
10995
13201
|
}
|
|
10996
|
-
return result.response;
|
|
10997
|
-
} catch (
|
|
10998
|
-
|
|
10999
|
-
if (
|
|
13202
|
+
return "response" in result ? result.response : void 0;
|
|
13203
|
+
} catch (error) {
|
|
13204
|
+
const isAbortError = error instanceof Error ? error.name === "AbortError" : !!error && typeof error === "object" && "name" in error && error.name === "AbortError";
|
|
13205
|
+
if (isAbortError) {
|
|
13206
|
+
if (!timedOut && signal?.aborted)
|
|
13207
|
+
throw new Error("Request aborted");
|
|
11000
13208
|
throw new Error("Proxy request timeout");
|
|
11001
13209
|
}
|
|
11002
|
-
throw
|
|
13210
|
+
throw error;
|
|
13211
|
+
} finally {
|
|
13212
|
+
clearTimeout(timeoutId);
|
|
13213
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
11003
13214
|
}
|
|
11004
13215
|
}
|
|
11005
13216
|
cleanupOldRequests() {
|
|
@@ -11050,6 +13261,8 @@ var RobloxStudioMCPServer = class {
|
|
|
11050
13261
|
console.error(`HTTP server listening on ${host}:${boundPort} for Studio plugin (primary mode)`);
|
|
11051
13262
|
console.error(`Streamable HTTP MCP endpoint: http://localhost:${boundPort}/mcp`);
|
|
11052
13263
|
} catch (error) {
|
|
13264
|
+
await primaryApp?.cleanup().catch(() => {
|
|
13265
|
+
});
|
|
11053
13266
|
if (process.env.ROBLOX_STUDIO_REQUIRE_PRIMARY === "1") {
|
|
11054
13267
|
console.error(`Port ${basePort} is unavailable and ROBLOX_STUDIO_REQUIRE_PRIMARY=1; refusing proxy mode`);
|
|
11055
13268
|
throw error;
|
|
@@ -11083,33 +13296,21 @@ var RobloxStudioMCPServer = class {
|
|
|
11083
13296
|
if (promotionInterval)
|
|
11084
13297
|
clearInterval(promotionInterval);
|
|
11085
13298
|
} catch {
|
|
13299
|
+
await candidateApp.cleanup().catch(() => {
|
|
13300
|
+
});
|
|
11086
13301
|
}
|
|
11087
13302
|
}, promotionIntervalMs);
|
|
11088
13303
|
}
|
|
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
13304
|
const stdioHandle = serveStdio((context) => createToolServer({
|
|
11104
13305
|
config: this.config,
|
|
11105
13306
|
getTools: () => this.tools,
|
|
11106
13307
|
allowedTools: this.allowedToolNames,
|
|
11107
13308
|
era: context.era,
|
|
11108
|
-
invoke: async (tools, name, args) => {
|
|
13309
|
+
invoke: async (tools, name, args, invocation) => {
|
|
11109
13310
|
const handler = TOOL_HANDLERS[name];
|
|
11110
13311
|
if (!handler)
|
|
11111
13312
|
throw new Error(`Unknown tool: ${name}`);
|
|
11112
|
-
return handler(tools, args);
|
|
13313
|
+
return handler(tools, args, invocation);
|
|
11113
13314
|
}
|
|
11114
13315
|
}), { onerror: (error) => console.error("[mcp:stdio]", error) });
|
|
11115
13316
|
console.error(`${this.config.name} v${this.config.version} running on stdio`);
|
|
@@ -11121,8 +13322,6 @@ var RobloxStudioMCPServer = class {
|
|
|
11121
13322
|
const activityInterval = setInterval(() => {
|
|
11122
13323
|
if (primaryApp)
|
|
11123
13324
|
primaryApp.trackMCPActivity();
|
|
11124
|
-
if (legacyApp)
|
|
11125
|
-
legacyApp.trackMCPActivity();
|
|
11126
13325
|
if (bridgeMode === "primary" && primaryApp) {
|
|
11127
13326
|
const pluginConnected = primaryApp.isPluginConnected();
|
|
11128
13327
|
const mcpActive = primaryApp.isMCPServerActive();
|
|
@@ -11147,22 +13346,16 @@ var RobloxStudioMCPServer = class {
|
|
|
11147
13346
|
if (promotionInterval)
|
|
11148
13347
|
clearInterval(promotionInterval);
|
|
11149
13348
|
primaryApp?.setMCPServerActive(false);
|
|
11150
|
-
legacyApp?.setMCPServerActive(false);
|
|
11151
13349
|
this.bridge.clearAllPendingRequests();
|
|
11152
13350
|
if (this.bridge instanceof ProxyBridgeService) {
|
|
11153
13351
|
this.bridge.stop();
|
|
11154
13352
|
}
|
|
11155
13353
|
await stdioHandle.close().catch(() => {
|
|
11156
13354
|
});
|
|
11157
|
-
await
|
|
11158
|
-
primaryApp?.closeMcpHandler(),
|
|
11159
|
-
legacyApp?.closeMcpHandler()
|
|
11160
|
-
]).catch(() => {
|
|
13355
|
+
await primaryApp?.cleanup().catch(() => {
|
|
11161
13356
|
});
|
|
11162
13357
|
if (httpHandle)
|
|
11163
13358
|
httpHandle.close();
|
|
11164
|
-
if (legacyHandle)
|
|
11165
|
-
legacyHandle.close();
|
|
11166
13359
|
process.exit(0);
|
|
11167
13360
|
};
|
|
11168
13361
|
process.on("SIGTERM", shutdown);
|
|
@@ -11564,37 +13757,44 @@ var TOOL_DEFINITIONS = [
|
|
|
11564
13757
|
{
|
|
11565
13758
|
name: "grep_scripts",
|
|
11566
13759
|
category: "read",
|
|
11567
|
-
description: "Use to
|
|
13760
|
+
description: "Use to search script sources.",
|
|
11568
13761
|
inputSchema: {
|
|
11569
13762
|
type: "object",
|
|
11570
13763
|
properties: {
|
|
11571
13764
|
pattern: {
|
|
11572
13765
|
type: "string",
|
|
11573
|
-
|
|
13766
|
+
maxLength: 4096,
|
|
13767
|
+
description: "Literal or Lua pattern; max 4096 UTF-8 bytes."
|
|
11574
13768
|
},
|
|
11575
13769
|
caseSensitive: {
|
|
11576
13770
|
type: "boolean",
|
|
11577
|
-
description: "
|
|
13771
|
+
description: "Lua patterns are always case-sensitive."
|
|
11578
13772
|
},
|
|
11579
13773
|
usePattern: {
|
|
11580
13774
|
type: "boolean",
|
|
11581
|
-
description: "
|
|
13775
|
+
description: "Enable Lua pattern with top-level | (not PCRE)."
|
|
11582
13776
|
},
|
|
11583
13777
|
contextLines: {
|
|
11584
|
-
type: "
|
|
11585
|
-
|
|
13778
|
+
type: "integer",
|
|
13779
|
+
minimum: 0,
|
|
13780
|
+
maximum: 100,
|
|
13781
|
+
description: "Surrounding lines (0-100; default 0)."
|
|
11586
13782
|
},
|
|
11587
13783
|
maxResults: {
|
|
11588
|
-
type: "
|
|
11589
|
-
|
|
13784
|
+
type: "integer",
|
|
13785
|
+
minimum: 1,
|
|
13786
|
+
maximum: 1e4,
|
|
13787
|
+
description: "Default 100."
|
|
11590
13788
|
},
|
|
11591
13789
|
maxResultsPerScript: {
|
|
11592
|
-
type: "
|
|
11593
|
-
|
|
13790
|
+
type: "integer",
|
|
13791
|
+
minimum: 0,
|
|
13792
|
+
maximum: 1e4,
|
|
13793
|
+
description: "Per-script cap; 0 is unlimited."
|
|
11594
13794
|
},
|
|
11595
13795
|
filesOnly: {
|
|
11596
13796
|
type: "boolean",
|
|
11597
|
-
description: "
|
|
13797
|
+
description: "Paths only; default false."
|
|
11598
13798
|
},
|
|
11599
13799
|
path: {
|
|
11600
13800
|
type: "string",
|
|
@@ -12909,12 +15109,15 @@ var TOOL_DEFINITIONS = [
|
|
|
12909
15109
|
var getReadOnlyTools = () => TOOL_DEFINITIONS.filter((t) => t.category === "read");
|
|
12910
15110
|
|
|
12911
15111
|
// ../core/dist/install-plugin-helpers.js
|
|
12912
|
-
|
|
15112
|
+
var import_saxes = __toESM(require_saxes(), 1);
|
|
15113
|
+
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
15114
|
import { execSync } from "child_process";
|
|
12914
|
-
import {
|
|
15115
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
15116
|
+
import { basename as basename3, join as join5 } from "path";
|
|
12915
15117
|
import { homedir as homedir4 } from "os";
|
|
15118
|
+
import { isUtf8 } from "buffer";
|
|
12916
15119
|
var DEFAULT_PLUGIN_PORT = 58741;
|
|
12917
|
-
var SERVER_URL_SETTING_KEY_PATTERN = /MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1|MCP_LAST_SUCCESSFUL_SERVER_URL_
|
|
15120
|
+
var SERVER_URL_SETTING_KEY_PATTERN = /MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1|MCP_LAST_SUCCESSFUL_SERVER_URL_/g;
|
|
12918
15121
|
function configurePluginAssetForPort(source, rawPort = process.env.ROBLOX_STUDIO_PORT) {
|
|
12919
15122
|
if (rawPort === void 0 || rawPort === "")
|
|
12920
15123
|
return source;
|
|
@@ -12941,19 +15144,19 @@ function configurePluginAssetForPort(source, rawPort = process.env.ROBLOX_STUDIO
|
|
|
12941
15144
|
function isWSL() {
|
|
12942
15145
|
return getStudioPlatformCapabilities().isWsl;
|
|
12943
15146
|
}
|
|
12944
|
-
function
|
|
15147
|
+
function getWindowsUserLocalAppData() {
|
|
12945
15148
|
try {
|
|
12946
|
-
const
|
|
15149
|
+
const windowsPath = execSync('cmd.exe /c "echo %LOCALAPPDATA%"', {
|
|
12947
15150
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12948
15151
|
}).toString().trim();
|
|
12949
|
-
if (!
|
|
15152
|
+
if (!windowsPath || windowsPath.includes("%"))
|
|
12950
15153
|
return null;
|
|
12951
|
-
const linuxPath = execSync(`wslpath -u '${
|
|
15154
|
+
const linuxPath = execSync(`wslpath -u '${windowsPath.replace(/'/g, "'\\''")}'`, {
|
|
12952
15155
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12953
15156
|
}).toString().trim();
|
|
12954
15157
|
if (!linuxPath)
|
|
12955
15158
|
return null;
|
|
12956
|
-
return
|
|
15159
|
+
return { linuxPath, windowsPath };
|
|
12957
15160
|
} catch {
|
|
12958
15161
|
return null;
|
|
12959
15162
|
}
|
|
@@ -12965,13 +15168,78 @@ function getPluginsFolder() {
|
|
|
12965
15168
|
return join5(process.env.LOCALAPPDATA || join5(homedir4(), "AppData", "Local"), "Roblox", "Plugins");
|
|
12966
15169
|
}
|
|
12967
15170
|
if (isWSL()) {
|
|
12968
|
-
const
|
|
12969
|
-
if (
|
|
12970
|
-
return
|
|
15171
|
+
const localAppData = getWindowsUserLocalAppData();
|
|
15172
|
+
if (localAppData)
|
|
15173
|
+
return join5(localAppData.linuxPath, "Roblox", "Plugins");
|
|
12971
15174
|
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
15175
|
}
|
|
12973
15176
|
return join5(homedir4(), "Documents", "Roblox", "Plugins");
|
|
12974
15177
|
}
|
|
15178
|
+
var STALE_TEST_PLUGINS_DIRECTORY = "RsmcpIsolatedPlugins";
|
|
15179
|
+
function pluginDirectorySettingPattern() {
|
|
15180
|
+
return /(<QDir\b[^>]*\bname="PluginsDir"[^>]*>)([\s\S]*?)(<\/QDir>)/gu;
|
|
15181
|
+
}
|
|
15182
|
+
function escapeXmlText(value) {
|
|
15183
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
15184
|
+
}
|
|
15185
|
+
function repairStaleStudioPluginDirectorySetting({ settingsPath, studioPluginsDirectory }) {
|
|
15186
|
+
if (!existsSync6(settingsPath))
|
|
15187
|
+
return false;
|
|
15188
|
+
if (studioPluginsDirectory === "") {
|
|
15189
|
+
throw new Error("studioPluginsDirectory must be a non-empty path.");
|
|
15190
|
+
}
|
|
15191
|
+
const contents = readFileSync6(settingsPath, "utf8");
|
|
15192
|
+
const matches = [...contents.matchAll(pluginDirectorySettingPattern())];
|
|
15193
|
+
if (matches.length !== 1) {
|
|
15194
|
+
throw new Error(`Expected exactly one Studio.PluginsDir QDir in ${settingsPath}; found ${matches.length}.`);
|
|
15195
|
+
}
|
|
15196
|
+
if (matches[0][2] !== STALE_TEST_PLUGINS_DIRECTORY)
|
|
15197
|
+
return false;
|
|
15198
|
+
const studioPath = escapeXmlText(studioPluginsDirectory);
|
|
15199
|
+
const updated = contents.replace(pluginDirectorySettingPattern(), (_match, open2, _value, close) => `${open2}${studioPath}${close}`);
|
|
15200
|
+
const temporaryPath = `${settingsPath}.rsmcp-install-${process.pid}-${randomUUID5()}.tmp`;
|
|
15201
|
+
try {
|
|
15202
|
+
writeFileSync3(temporaryPath, updated, {
|
|
15203
|
+
encoding: "utf8",
|
|
15204
|
+
mode: statSync3(settingsPath).mode
|
|
15205
|
+
});
|
|
15206
|
+
renameSync(temporaryPath, settingsPath);
|
|
15207
|
+
} finally {
|
|
15208
|
+
rmSync2(temporaryPath, { force: true });
|
|
15209
|
+
}
|
|
15210
|
+
return true;
|
|
15211
|
+
}
|
|
15212
|
+
function repairDefaultStudioPluginDirectorySetting(pluginsFolder, log) {
|
|
15213
|
+
if (process.env.MCP_PLUGINS_DIR)
|
|
15214
|
+
return;
|
|
15215
|
+
let settingsPath;
|
|
15216
|
+
let defaultPluginsFolder;
|
|
15217
|
+
let studioPluginsDirectory;
|
|
15218
|
+
if (process.platform === "win32") {
|
|
15219
|
+
const localAppData = process.env.LOCALAPPDATA || join5(homedir4(), "AppData", "Local");
|
|
15220
|
+
defaultPluginsFolder = join5(localAppData, "Roblox", "Plugins");
|
|
15221
|
+
settingsPath = join5(localAppData, "Roblox", "GlobalSettings_13.xml");
|
|
15222
|
+
studioPluginsDirectory = defaultPluginsFolder.replaceAll("\\", "/");
|
|
15223
|
+
} else if (isWSL()) {
|
|
15224
|
+
const localAppData = getWindowsUserLocalAppData();
|
|
15225
|
+
if (!localAppData)
|
|
15226
|
+
return;
|
|
15227
|
+
defaultPluginsFolder = join5(localAppData.linuxPath, "Roblox", "Plugins");
|
|
15228
|
+
settingsPath = join5(localAppData.linuxPath, "Roblox", "GlobalSettings_13.xml");
|
|
15229
|
+
studioPluginsDirectory = [
|
|
15230
|
+
localAppData.windowsPath.replace(/[\\/]+$/u, ""),
|
|
15231
|
+
"Roblox",
|
|
15232
|
+
"Plugins"
|
|
15233
|
+
].join("/").replaceAll("\\", "/");
|
|
15234
|
+
} else {
|
|
15235
|
+
return;
|
|
15236
|
+
}
|
|
15237
|
+
if (pluginsFolder !== defaultPluginsFolder)
|
|
15238
|
+
return;
|
|
15239
|
+
if (repairStaleStudioPluginDirectorySetting({ settingsPath, studioPluginsDirectory })) {
|
|
15240
|
+
log(`Restored Studio PluginsDir to ${studioPluginsDirectory}.`);
|
|
15241
|
+
}
|
|
15242
|
+
}
|
|
12975
15243
|
function handleVariantConflict({ pluginsFolder, otherAssetName, replace, log = console.log, warn = console.warn }) {
|
|
12976
15244
|
const otherDest = join5(pluginsFolder, otherAssetName);
|
|
12977
15245
|
if (!existsSync6(otherDest))
|
|
@@ -12991,12 +15259,190 @@ Only one MCP plugin variant should be present. If both variants are in the Studi
|
|
|
12991
15259
|
Delete ${otherAssetName} manually or use the default CLI installer behavior to replace it.
|
|
12992
15260
|
`);
|
|
12993
15261
|
}
|
|
15262
|
+
var PLUGIN_INSTALL_LOCK_NAME = ".robloxstudio-mcp-plugin-install.lock";
|
|
15263
|
+
function errorCode(error) {
|
|
15264
|
+
return error.code;
|
|
15265
|
+
}
|
|
15266
|
+
function acquirePluginInstallLock(pluginsFolder, warn) {
|
|
15267
|
+
const lockPath = join5(pluginsFolder, PLUGIN_INSTALL_LOCK_NAME);
|
|
15268
|
+
const ownerPath = join5(lockPath, "owner.json");
|
|
15269
|
+
const owner = {
|
|
15270
|
+
pid: process.pid,
|
|
15271
|
+
token: randomUUID5(),
|
|
15272
|
+
createdAt: Date.now()
|
|
15273
|
+
};
|
|
15274
|
+
try {
|
|
15275
|
+
mkdirSync4(lockPath);
|
|
15276
|
+
} catch (error) {
|
|
15277
|
+
if (errorCode(error) !== "EEXIST")
|
|
15278
|
+
throw error;
|
|
15279
|
+
throw new Error(`Another Studio plugin installation is already in progress: ${lockPath}. If no installer is running, remove that lock directory and retry.`);
|
|
15280
|
+
}
|
|
15281
|
+
try {
|
|
15282
|
+
writeFileSync3(ownerPath, `${JSON.stringify(owner)}
|
|
15283
|
+
`, {
|
|
15284
|
+
encoding: "utf8",
|
|
15285
|
+
flag: "wx"
|
|
15286
|
+
});
|
|
15287
|
+
} catch (error) {
|
|
15288
|
+
try {
|
|
15289
|
+
rmSync2(lockPath, { recursive: true, force: true });
|
|
15290
|
+
} catch (cleanupError) {
|
|
15291
|
+
warn(`[install-plugin] Could not clean failed install lock ${lockPath}: ${cleanupError}`);
|
|
15292
|
+
}
|
|
15293
|
+
throw error;
|
|
15294
|
+
}
|
|
15295
|
+
return () => {
|
|
15296
|
+
try {
|
|
15297
|
+
const currentOwner = JSON.parse(readFileSync6(ownerPath, "utf8"));
|
|
15298
|
+
if (currentOwner.pid === owner.pid && currentOwner.token === owner.token) {
|
|
15299
|
+
rmSync2(lockPath, { recursive: true, force: true });
|
|
15300
|
+
}
|
|
15301
|
+
} catch (error) {
|
|
15302
|
+
if (errorCode(error) !== "ENOENT") {
|
|
15303
|
+
warn(`[install-plugin] Could not release install lock ${lockPath}: ${error}`);
|
|
15304
|
+
}
|
|
15305
|
+
}
|
|
15306
|
+
};
|
|
15307
|
+
}
|
|
15308
|
+
function assertAssetFileName(name, field) {
|
|
15309
|
+
if (name === "" || basename3(name) !== name) {
|
|
15310
|
+
throw new Error(`${field} must be a file name, received ${JSON.stringify(name)}`);
|
|
15311
|
+
}
|
|
15312
|
+
}
|
|
15313
|
+
function embeddedPluginValue(source, name, assetName) {
|
|
15314
|
+
const pattern = new RegExp(`\\blocal\\s+${name}\\s*=\\s*"([^"]+)"\\s*;?`, "g");
|
|
15315
|
+
const matches = [...source.matchAll(pattern)];
|
|
15316
|
+
if (matches.length !== 1) {
|
|
15317
|
+
throw new Error(`${assetName} must contain exactly one embedded ${name}; found ${matches.length}.`);
|
|
15318
|
+
}
|
|
15319
|
+
return matches[0][1];
|
|
15320
|
+
}
|
|
15321
|
+
var PLUGIN_SOURCE_CLASSES = {
|
|
15322
|
+
Script: true,
|
|
15323
|
+
ModuleScript: true,
|
|
15324
|
+
LocalScript: true
|
|
15325
|
+
};
|
|
15326
|
+
function parsePluginScriptSources(source, assetName) {
|
|
15327
|
+
if (!isUtf8(source)) {
|
|
15328
|
+
throw new Error(`${assetName} is not valid UTF-8 Roblox XML.`);
|
|
15329
|
+
}
|
|
15330
|
+
const text = source.toString("utf8");
|
|
15331
|
+
const elementStack = [];
|
|
15332
|
+
const scriptSources = [];
|
|
15333
|
+
let rootName;
|
|
15334
|
+
let currentSource;
|
|
15335
|
+
const parser = new import_saxes.SaxesParser({ fragment: false, xmlns: false });
|
|
15336
|
+
parser.on("doctype", () => {
|
|
15337
|
+
throw new Error("DOCTYPE declarations are not allowed in plugin artifacts.");
|
|
15338
|
+
});
|
|
15339
|
+
parser.on("opentag", (tag) => {
|
|
15340
|
+
if (elementStack.length === 0)
|
|
15341
|
+
rootName = tag.name;
|
|
15342
|
+
if (currentSource === void 0 && tag.name === "string" && tag.attributes.name === "Source") {
|
|
15343
|
+
const nearestItem = [...elementStack].reverse().find((element) => element.name === "Item");
|
|
15344
|
+
const sourceClass = nearestItem?.attributes.class;
|
|
15345
|
+
if (sourceClass !== void 0 && PLUGIN_SOURCE_CLASSES[sourceClass] === true) {
|
|
15346
|
+
currentSource = { depth: elementStack.length, text: "" };
|
|
15347
|
+
}
|
|
15348
|
+
}
|
|
15349
|
+
elementStack.push({ name: tag.name, attributes: tag.attributes });
|
|
15350
|
+
});
|
|
15351
|
+
parser.on("text", (value) => {
|
|
15352
|
+
if (currentSource !== void 0)
|
|
15353
|
+
currentSource.text += value;
|
|
15354
|
+
});
|
|
15355
|
+
parser.on("cdata", (value) => {
|
|
15356
|
+
if (currentSource !== void 0)
|
|
15357
|
+
currentSource.text += value;
|
|
15358
|
+
});
|
|
15359
|
+
parser.on("closetag", (tag) => {
|
|
15360
|
+
if (currentSource !== void 0 && tag.name === "string" && elementStack.length === currentSource.depth + 1) {
|
|
15361
|
+
scriptSources.push(currentSource.text);
|
|
15362
|
+
currentSource = void 0;
|
|
15363
|
+
}
|
|
15364
|
+
elementStack.pop();
|
|
15365
|
+
});
|
|
15366
|
+
try {
|
|
15367
|
+
parser.write(text).close();
|
|
15368
|
+
} catch (error) {
|
|
15369
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
15370
|
+
throw new Error(`${assetName} is not well-formed Roblox XML: ${detail}`, {
|
|
15371
|
+
cause: error
|
|
15372
|
+
});
|
|
15373
|
+
}
|
|
15374
|
+
if (rootName !== "roblox" || scriptSources.length === 0) {
|
|
15375
|
+
throw new Error(`${assetName} is not a Roblox XML model containing a Script source.`);
|
|
15376
|
+
}
|
|
15377
|
+
return scriptSources;
|
|
15378
|
+
}
|
|
15379
|
+
function assertPluginAssetIdentity(source, assetName, expectedVersion, expectedVariant) {
|
|
15380
|
+
const pluginSource = parsePluginScriptSources(source, assetName).join("\n");
|
|
15381
|
+
const actualVersion = embeddedPluginValue(pluginSource, "CURRENT_VERSION", assetName);
|
|
15382
|
+
if (actualVersion !== expectedVersion) {
|
|
15383
|
+
throw new Error(`${assetName} embeds version ${actualVersion}; expected ${expectedVersion}.`);
|
|
15384
|
+
}
|
|
15385
|
+
const actualVariant = embeddedPluginValue(pluginSource, "PLUGIN_VARIANT", assetName);
|
|
15386
|
+
if (actualVariant !== expectedVariant) {
|
|
15387
|
+
throw new Error(`${assetName} embeds variant ${actualVariant}; expected ${expectedVariant}.`);
|
|
15388
|
+
}
|
|
15389
|
+
}
|
|
15390
|
+
function installedFileMatches(source, destination) {
|
|
15391
|
+
if (!existsSync6(destination))
|
|
15392
|
+
return false;
|
|
15393
|
+
if (!lstatSync(destination).isFile())
|
|
15394
|
+
return false;
|
|
15395
|
+
const installed = readFileSync6(destination);
|
|
15396
|
+
return installed.length === source.length && installed.equals(source);
|
|
15397
|
+
}
|
|
15398
|
+
function installPluginAsset({ pluginsFolder, assetName, otherAssetName, source, expectedVersion, expectedVariant, rawPort, replaceVariant = true, log = console.log, warn = console.warn }) {
|
|
15399
|
+
assertAssetFileName(assetName, "assetName");
|
|
15400
|
+
assertAssetFileName(otherAssetName, "otherAssetName");
|
|
15401
|
+
if (assetName === otherAssetName) {
|
|
15402
|
+
throw new Error("assetName and otherAssetName must identify different files.");
|
|
15403
|
+
}
|
|
15404
|
+
const configured = configurePluginAssetForPort(source, rawPort);
|
|
15405
|
+
assertPluginAssetIdentity(configured, assetName, expectedVersion, expectedVariant);
|
|
15406
|
+
mkdirSync4(pluginsFolder, { recursive: true });
|
|
15407
|
+
const destination = join5(pluginsFolder, assetName);
|
|
15408
|
+
const releaseLock = acquirePluginInstallLock(pluginsFolder, warn);
|
|
15409
|
+
try {
|
|
15410
|
+
let installed = false;
|
|
15411
|
+
if (!installedFileMatches(configured, destination)) {
|
|
15412
|
+
const staged = join5(pluginsFolder, `.${assetName}.${process.pid}.${randomUUID5()}.tmp`);
|
|
15413
|
+
try {
|
|
15414
|
+
writeFileSync3(staged, configured, { flag: "wx" });
|
|
15415
|
+
renameSync(staged, destination);
|
|
15416
|
+
installed = true;
|
|
15417
|
+
} finally {
|
|
15418
|
+
try {
|
|
15419
|
+
unlinkSync(staged);
|
|
15420
|
+
} catch (error) {
|
|
15421
|
+
if (errorCode(error) !== "ENOENT") {
|
|
15422
|
+
warn(`[install-plugin] Could not remove staging file ${staged}: ${error}`);
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
}
|
|
15426
|
+
}
|
|
15427
|
+
handleVariantConflict({
|
|
15428
|
+
pluginsFolder,
|
|
15429
|
+
otherAssetName,
|
|
15430
|
+
replace: replaceVariant,
|
|
15431
|
+
log,
|
|
15432
|
+
warn
|
|
15433
|
+
});
|
|
15434
|
+
repairDefaultStudioPluginDirectorySetting(pluginsFolder, log);
|
|
15435
|
+
return { destination, installed };
|
|
15436
|
+
} finally {
|
|
15437
|
+
releaseLock();
|
|
15438
|
+
}
|
|
15439
|
+
}
|
|
12994
15440
|
|
|
12995
15441
|
// src/index.ts
|
|
12996
15442
|
import { createRequire } from "module";
|
|
12997
15443
|
|
|
12998
15444
|
// src/install-plugin.ts
|
|
12999
|
-
import {
|
|
15445
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
13000
15446
|
import { dirname as dirname5, join as join6 } from "path";
|
|
13001
15447
|
import { fileURLToPath } from "url";
|
|
13002
15448
|
import { get } from "https";
|
|
@@ -13005,6 +15451,7 @@ var ASSET_NAME = "MCPInspectorPlugin.rbxmx";
|
|
|
13005
15451
|
var OTHER_VARIANT = "MCPPlugin.rbxmx";
|
|
13006
15452
|
var TIMEOUT_MS = 3e4;
|
|
13007
15453
|
var MAX_REDIRECTS = 5;
|
|
15454
|
+
var MAX_ASSET_BYTES = 64 * 1024 * 1024;
|
|
13008
15455
|
function httpsGet(url) {
|
|
13009
15456
|
return new Promise((resolve5, reject) => {
|
|
13010
15457
|
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } }, resolve5);
|
|
@@ -13014,36 +15461,33 @@ function httpsGet(url) {
|
|
|
13014
15461
|
});
|
|
13015
15462
|
});
|
|
13016
15463
|
}
|
|
13017
|
-
async function download(url,
|
|
15464
|
+
async function download(url, redirects = 0) {
|
|
13018
15465
|
const res = await httpsGet(url);
|
|
13019
15466
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
13020
15467
|
if (redirects >= MAX_REDIRECTS) throw new Error(`Too many redirects (max ${MAX_REDIRECTS})`);
|
|
13021
15468
|
const location = res.headers.location;
|
|
13022
15469
|
if (!location) throw new Error("Redirect with no location header");
|
|
13023
|
-
|
|
15470
|
+
for await (const _chunk of res) {
|
|
15471
|
+
}
|
|
15472
|
+
return download(location, redirects + 1);
|
|
13024
15473
|
}
|
|
13025
15474
|
if (res.statusCode !== 200) {
|
|
13026
15475
|
throw new Error(`Download failed: HTTP ${res.statusCode}`);
|
|
13027
15476
|
}
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
}
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
resolve5();
|
|
13043
|
-
});
|
|
13044
|
-
file.on("error", cleanup);
|
|
13045
|
-
res.on("error", cleanup);
|
|
13046
|
-
});
|
|
15477
|
+
const chunks = [];
|
|
15478
|
+
let totalBytes = 0;
|
|
15479
|
+
for await (const chunk of res) {
|
|
15480
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
15481
|
+
totalBytes += bytes.length;
|
|
15482
|
+
if (totalBytes > MAX_ASSET_BYTES) {
|
|
15483
|
+
res.destroy();
|
|
15484
|
+
throw new Error(
|
|
15485
|
+
`${ASSET_NAME} download exceeds the ${MAX_ASSET_BYTES}-byte limit.`
|
|
15486
|
+
);
|
|
15487
|
+
}
|
|
15488
|
+
chunks.push(bytes);
|
|
15489
|
+
}
|
|
15490
|
+
return Buffer.concat(chunks, totalBytes);
|
|
13047
15491
|
}
|
|
13048
15492
|
async function fetchJson(url) {
|
|
13049
15493
|
const res = await httpsGet(url);
|
|
@@ -13056,24 +15500,6 @@ async function fetchJson(url) {
|
|
|
13056
15500
|
}
|
|
13057
15501
|
return JSON.parse(Buffer.concat(chunks).toString());
|
|
13058
15502
|
}
|
|
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
15503
|
function bundledAssetPath() {
|
|
13078
15504
|
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
13079
15505
|
const candidates = [
|
|
@@ -13088,47 +15514,36 @@ function resolvePluginAssetPath(sourcePath) {
|
|
|
13088
15514
|
}
|
|
13089
15515
|
function packageVersion() {
|
|
13090
15516
|
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
13091
|
-
const pkg = JSON.parse(
|
|
15517
|
+
const pkg = JSON.parse(readFileSync7(join6(currentDir, "..", "package.json"), "utf8"));
|
|
13092
15518
|
if (!pkg.version) {
|
|
13093
15519
|
throw new Error("Package version not found");
|
|
13094
15520
|
}
|
|
13095
15521
|
return pkg.version;
|
|
13096
15522
|
}
|
|
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
15523
|
async function installBundledPlugin(options = {}) {
|
|
13116
15524
|
const log = options.log ?? console.log;
|
|
13117
15525
|
const warn = options.warn ?? console.warn;
|
|
13118
15526
|
const replaceVariant = options.replaceVariant ?? true;
|
|
13119
|
-
const
|
|
13120
|
-
if (!
|
|
15527
|
+
const sourcePath = resolvePluginAssetPath(options.sourcePath);
|
|
15528
|
+
if (!sourcePath) {
|
|
13121
15529
|
throw new Error(
|
|
13122
15530
|
`Bundled ${ASSET_NAME} not found. Run npm run build:plugin:inspector in this worktree first.`
|
|
13123
15531
|
);
|
|
13124
15532
|
}
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
15533
|
+
const result = await installPluginAsset({
|
|
15534
|
+
pluginsFolder: getPluginsFolder(),
|
|
15535
|
+
assetName: ASSET_NAME,
|
|
15536
|
+
otherAssetName: OTHER_VARIANT,
|
|
15537
|
+
source: readFileSync7(sourcePath),
|
|
15538
|
+
expectedVersion: packageVersion(),
|
|
15539
|
+
expectedVariant: "inspector",
|
|
15540
|
+
replaceVariant,
|
|
15541
|
+
log,
|
|
15542
|
+
warn
|
|
15543
|
+
});
|
|
15544
|
+
if (result.installed) {
|
|
15545
|
+
log(`Installed ${ASSET_NAME} to ${result.destination}`);
|
|
15546
|
+
}
|
|
13132
15547
|
}
|
|
13133
15548
|
async function installPlugin(options = {}) {
|
|
13134
15549
|
const replaceVariant = options.replaceVariant ?? true;
|
|
@@ -13138,17 +15553,23 @@ async function installPlugin(options = {}) {
|
|
|
13138
15553
|
if (options.sourcePath !== void 0 && !bundled) {
|
|
13139
15554
|
throw new Error(`Plugin asset not found at explicit path ${options.sourcePath}.`);
|
|
13140
15555
|
}
|
|
13141
|
-
const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
|
|
13142
15556
|
if (bundled) {
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
15557
|
+
const result2 = await installPluginAsset({
|
|
15558
|
+
pluginsFolder: getPluginsFolder(),
|
|
15559
|
+
assetName: ASSET_NAME,
|
|
15560
|
+
otherAssetName: OTHER_VARIANT,
|
|
15561
|
+
source: readFileSync7(bundled),
|
|
15562
|
+
expectedVersion: packageVersion(),
|
|
15563
|
+
expectedVariant: "inspector",
|
|
15564
|
+
replaceVariant,
|
|
15565
|
+
log,
|
|
15566
|
+
warn
|
|
15567
|
+
});
|
|
15568
|
+
if (result2.installed) {
|
|
15569
|
+
log(`Installed bundled ${ASSET_NAME} to ${result2.destination}`);
|
|
15570
|
+
} else {
|
|
13147
15571
|
log(`${ASSET_NAME} already installed.`);
|
|
13148
|
-
return;
|
|
13149
15572
|
}
|
|
13150
|
-
writeFileSync3(dest2, configured2);
|
|
13151
|
-
log(`Installed bundled ${ASSET_NAME} to ${dest2}`);
|
|
13152
15573
|
return;
|
|
13153
15574
|
}
|
|
13154
15575
|
log("Fetching latest release...");
|
|
@@ -13157,13 +15578,24 @@ async function installPlugin(options = {}) {
|
|
|
13157
15578
|
if (!asset) {
|
|
13158
15579
|
throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
|
|
13159
15580
|
}
|
|
13160
|
-
const dest = join6(pluginsFolder, ASSET_NAME);
|
|
13161
15581
|
log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
13162
|
-
await download(asset.browser_download_url
|
|
13163
|
-
const
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
15582
|
+
const downloaded = await download(asset.browser_download_url);
|
|
15583
|
+
const result = await installPluginAsset({
|
|
15584
|
+
pluginsFolder: getPluginsFolder(),
|
|
15585
|
+
assetName: ASSET_NAME,
|
|
15586
|
+
otherAssetName: OTHER_VARIANT,
|
|
15587
|
+
source: downloaded,
|
|
15588
|
+
expectedVersion: release.tag_name.replace(/^v/, ""),
|
|
15589
|
+
expectedVariant: "inspector",
|
|
15590
|
+
replaceVariant,
|
|
15591
|
+
log,
|
|
15592
|
+
warn
|
|
15593
|
+
});
|
|
15594
|
+
if (result.installed) {
|
|
15595
|
+
log(`Installed to ${result.destination}`);
|
|
15596
|
+
} else {
|
|
15597
|
+
log(`${ASSET_NAME} already installed.`);
|
|
15598
|
+
}
|
|
13167
15599
|
}
|
|
13168
15600
|
|
|
13169
15601
|
// src/index.ts
|
|
@@ -13201,3 +15633,32 @@ if (installBundledOnly || installWithFallback) {
|
|
|
13201
15633
|
process.exit(1);
|
|
13202
15634
|
});
|
|
13203
15635
|
}
|
|
15636
|
+
/*! Bundled license information:
|
|
15637
|
+
|
|
15638
|
+
xmlchars/xml/1.0/ed5.js:
|
|
15639
|
+
(**
|
|
15640
|
+
* Character classes and associated utilities for the 5th edition of XML 1.0.
|
|
15641
|
+
*
|
|
15642
|
+
* @author Louis-Dominique Dubeau
|
|
15643
|
+
* @license MIT
|
|
15644
|
+
* @copyright Louis-Dominique Dubeau
|
|
15645
|
+
*)
|
|
15646
|
+
|
|
15647
|
+
xmlchars/xml/1.1/ed2.js:
|
|
15648
|
+
(**
|
|
15649
|
+
* Character classes and associated utilities for the 2nd edition of XML 1.1.
|
|
15650
|
+
*
|
|
15651
|
+
* @author Louis-Dominique Dubeau
|
|
15652
|
+
* @license MIT
|
|
15653
|
+
* @copyright Louis-Dominique Dubeau
|
|
15654
|
+
*)
|
|
15655
|
+
|
|
15656
|
+
xmlchars/xmlns/1.0/ed3.js:
|
|
15657
|
+
(**
|
|
15658
|
+
* Character class utilities for XML NS 1.0 edition 3.
|
|
15659
|
+
*
|
|
15660
|
+
* @author Louis-Dominique Dubeau
|
|
15661
|
+
* @license MIT
|
|
15662
|
+
* @copyright Louis-Dominique Dubeau
|
|
15663
|
+
*)
|
|
15664
|
+
*/
|