mrmd-js 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +842 -0
- package/dist/index.cjs +7613 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +7530 -0
- package/dist/index.js.map +1 -0
- package/dist/mrmd-js.iife.js +7618 -0
- package/dist/mrmd-js.iife.js.map +1 -0
- package/package.json +47 -0
- package/src/analysis/format.js +371 -0
- package/src/analysis/index.js +18 -0
- package/src/analysis/is-complete.js +394 -0
- package/src/constants.js +44 -0
- package/src/execute/css.js +205 -0
- package/src/execute/html.js +162 -0
- package/src/execute/index.js +41 -0
- package/src/execute/interface.js +144 -0
- package/src/execute/javascript.js +197 -0
- package/src/execute/registry.js +245 -0
- package/src/index.js +136 -0
- package/src/lsp/complete.js +353 -0
- package/src/lsp/format.js +310 -0
- package/src/lsp/hover.js +126 -0
- package/src/lsp/index.js +55 -0
- package/src/lsp/inspect.js +466 -0
- package/src/lsp/parse.js +455 -0
- package/src/lsp/variables.js +283 -0
- package/src/runtime.js +518 -0
- package/src/session/console-capture.js +181 -0
- package/src/session/context/iframe.js +407 -0
- package/src/session/context/index.js +12 -0
- package/src/session/context/interface.js +38 -0
- package/src/session/context/main.js +357 -0
- package/src/session/index.js +16 -0
- package/src/session/manager.js +327 -0
- package/src/session/session.js +678 -0
- package/src/transform/async.js +133 -0
- package/src/transform/extract.js +251 -0
- package/src/transform/index.js +10 -0
- package/src/transform/persistence.js +176 -0
- package/src/types/analysis.js +24 -0
- package/src/types/capabilities.js +44 -0
- package/src/types/completion.js +47 -0
- package/src/types/execution.js +62 -0
- package/src/types/index.js +16 -0
- package/src/types/inspection.js +39 -0
- package/src/types/session.js +32 -0
- package/src/types/streaming.js +74 -0
- package/src/types/variables.js +54 -0
- package/src/utils/ansi-renderer.js +301 -0
- package/src/utils/css-applicator.js +149 -0
- package/src/utils/html-renderer.js +355 -0
- package/src/utils/index.js +25 -0
package/src/runtime.js
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MRP Runtime
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for the mrmd-js runtime. Implements the MRMD Runtime
|
|
5
|
+
* Protocol (MRP) as a JavaScript API for browser-based execution.
|
|
6
|
+
*
|
|
7
|
+
* @module runtime
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { SessionManager } from './session/manager.js';
|
|
11
|
+
import { createDefaultExecutorRegistry } from './execute/index.js';
|
|
12
|
+
import { RUNTIME_NAME, RUNTIME_VERSION, DEFAULT_MAX_SESSIONS } from './constants.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {import('./types/capabilities.js').Capabilities} Capabilities
|
|
16
|
+
* @typedef {import('./types/capabilities.js').Features} Features
|
|
17
|
+
* @typedef {import('./types/capabilities.js').BrowserEnvironment} BrowserEnvironment
|
|
18
|
+
* @typedef {import('./types/session.js').SessionInfo} SessionInfo
|
|
19
|
+
* @typedef {import('./types/session.js').CreateSessionOptions} CreateSessionOptions
|
|
20
|
+
* @typedef {import('./types/session.js').IsolationMode} IsolationMode
|
|
21
|
+
* @typedef {import('./types/execution.js').ExecuteOptions} ExecuteOptions
|
|
22
|
+
* @typedef {import('./types/execution.js').ExecutionResult} ExecutionResult
|
|
23
|
+
* @typedef {import('./types/streaming.js').StreamEvent} StreamEvent
|
|
24
|
+
* @typedef {import('./types/completion.js').CompleteOptions} CompleteOptions
|
|
25
|
+
* @typedef {import('./types/completion.js').CompletionResult} CompletionResult
|
|
26
|
+
* @typedef {import('./types/inspection.js').InspectOptions} InspectOptions
|
|
27
|
+
* @typedef {import('./types/inspection.js').InspectResult} InspectResult
|
|
28
|
+
* @typedef {import('./types/inspection.js').HoverResult} HoverResult
|
|
29
|
+
* @typedef {import('./types/variables.js').VariableFilter} VariableFilter
|
|
30
|
+
* @typedef {import('./types/variables.js').VariableInfo} VariableInfo
|
|
31
|
+
* @typedef {import('./types/variables.js').VariableDetailOptions} VariableDetailOptions
|
|
32
|
+
* @typedef {import('./types/variables.js').VariableDetail} VariableDetail
|
|
33
|
+
* @typedef {import('./types/analysis.js').IsCompleteResult} IsCompleteResult
|
|
34
|
+
* @typedef {import('./types/analysis.js').FormatResult} FormatResult
|
|
35
|
+
* @typedef {import('./session/session.js').Session} Session
|
|
36
|
+
* @typedef {import('./execute/registry.js').ExecutorRegistry} ExecutorRegistry
|
|
37
|
+
* @typedef {import('./execute/interface.js').Executor} Executor
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} MrpRuntimeOptions
|
|
42
|
+
* @property {number} [maxSessions] - Maximum concurrent sessions
|
|
43
|
+
* @property {IsolationMode} [defaultIsolation='iframe'] - Default isolation mode
|
|
44
|
+
* @property {boolean} [defaultAllowMainAccess=false] - Allow main window access by default
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Asset stored in the runtime
|
|
49
|
+
* @typedef {Object} StoredAsset
|
|
50
|
+
* @property {string} url - Blob URL
|
|
51
|
+
* @property {string} mimeType - MIME type
|
|
52
|
+
* @property {string} assetType - Asset type category
|
|
53
|
+
* @property {number} size - Size in bytes
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Main MRP runtime for browser JavaScript
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const runtime = new MrpRuntime();
|
|
61
|
+
* const session = runtime.createSession({ language: 'javascript' });
|
|
62
|
+
* const result = await session.execute('const x = 1 + 2; x');
|
|
63
|
+
* console.log(result.resultString); // "3"
|
|
64
|
+
*/
|
|
65
|
+
export class MrpRuntime {
|
|
66
|
+
/** @type {SessionManager} */
|
|
67
|
+
#sessionManager;
|
|
68
|
+
|
|
69
|
+
/** @type {ExecutorRegistry} */
|
|
70
|
+
#executorRegistry;
|
|
71
|
+
|
|
72
|
+
/** @type {MrpRuntimeOptions} */
|
|
73
|
+
#options;
|
|
74
|
+
|
|
75
|
+
/** @type {Map<string, StoredAsset>} */
|
|
76
|
+
#assets = new Map();
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a new MRP runtime
|
|
80
|
+
* @param {MrpRuntimeOptions} [options]
|
|
81
|
+
*/
|
|
82
|
+
constructor(options = {}) {
|
|
83
|
+
this.#options = {
|
|
84
|
+
maxSessions: options.maxSessions ?? DEFAULT_MAX_SESSIONS,
|
|
85
|
+
defaultIsolation: options.defaultIsolation ?? 'iframe',
|
|
86
|
+
defaultAllowMainAccess: options.defaultAllowMainAccess ?? false,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.#executorRegistry = createDefaultExecutorRegistry();
|
|
90
|
+
this.#sessionManager = new SessionManager({
|
|
91
|
+
maxSessions: this.#options.maxSessions,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Capabilities
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get runtime capabilities (MRP /capabilities)
|
|
101
|
+
* @returns {Capabilities}
|
|
102
|
+
*/
|
|
103
|
+
getCapabilities() {
|
|
104
|
+
return {
|
|
105
|
+
runtime: RUNTIME_NAME,
|
|
106
|
+
version: RUNTIME_VERSION,
|
|
107
|
+
languages: this.#executorRegistry.languages(),
|
|
108
|
+
features: this.#getFeatures(),
|
|
109
|
+
defaultSession: 'default',
|
|
110
|
+
maxSessions: this.#options.maxSessions ?? DEFAULT_MAX_SESSIONS,
|
|
111
|
+
environment: this.#getEnvironment(),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get feature flags
|
|
117
|
+
* @returns {Features}
|
|
118
|
+
*/
|
|
119
|
+
#getFeatures() {
|
|
120
|
+
return {
|
|
121
|
+
execute: true,
|
|
122
|
+
executeStream: true,
|
|
123
|
+
interrupt: true,
|
|
124
|
+
complete: true,
|
|
125
|
+
inspect: true,
|
|
126
|
+
hover: true,
|
|
127
|
+
variables: true,
|
|
128
|
+
variableExpand: true,
|
|
129
|
+
reset: true,
|
|
130
|
+
isComplete: true,
|
|
131
|
+
format: true,
|
|
132
|
+
assets: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get environment info
|
|
138
|
+
* @returns {BrowserEnvironment}
|
|
139
|
+
*/
|
|
140
|
+
#getEnvironment() {
|
|
141
|
+
return {
|
|
142
|
+
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
|
|
143
|
+
language: typeof navigator !== 'undefined' ? navigator.language : 'en',
|
|
144
|
+
platform: typeof navigator !== 'undefined' ? navigator.platform : 'unknown',
|
|
145
|
+
isSecureContext: typeof window !== 'undefined' ? window.isSecureContext : false,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Sessions
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* List all active sessions (MRP GET /sessions)
|
|
155
|
+
* @returns {SessionInfo[]}
|
|
156
|
+
*/
|
|
157
|
+
listSessions() {
|
|
158
|
+
return this.#sessionManager.list();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a new session (MRP POST /sessions)
|
|
163
|
+
* @param {CreateSessionOptions} [options]
|
|
164
|
+
* @returns {Session}
|
|
165
|
+
*/
|
|
166
|
+
createSession(options = {}) {
|
|
167
|
+
const session = this.#sessionManager.create({
|
|
168
|
+
...options,
|
|
169
|
+
isolation: options.isolation ?? this.#options.defaultIsolation,
|
|
170
|
+
allowMainAccess: options.allowMainAccess ?? this.#options.defaultAllowMainAccess,
|
|
171
|
+
executorRegistry: this.#executorRegistry,
|
|
172
|
+
});
|
|
173
|
+
return session;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get a session by ID (MRP GET /sessions/{id})
|
|
178
|
+
* @param {string} id
|
|
179
|
+
* @returns {Session | undefined}
|
|
180
|
+
*/
|
|
181
|
+
getSession(id) {
|
|
182
|
+
return this.#sessionManager.get(id);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get or create a session
|
|
187
|
+
* @param {string} id
|
|
188
|
+
* @param {CreateSessionOptions} [options]
|
|
189
|
+
* @returns {Session}
|
|
190
|
+
*/
|
|
191
|
+
getOrCreateSession(id, options = {}) {
|
|
192
|
+
const existing = this.#sessionManager.get(id);
|
|
193
|
+
if (existing) return existing;
|
|
194
|
+
|
|
195
|
+
return this.createSession({ ...options, id });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Destroy a session (MRP DELETE /sessions/{id})
|
|
200
|
+
* @param {string} id
|
|
201
|
+
* @returns {boolean}
|
|
202
|
+
*/
|
|
203
|
+
destroySession(id) {
|
|
204
|
+
return this.#sessionManager.destroy(id);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Reset a session (MRP POST /sessions/{id}/reset)
|
|
209
|
+
* @param {string} id
|
|
210
|
+
* @returns {boolean}
|
|
211
|
+
*/
|
|
212
|
+
resetSession(id) {
|
|
213
|
+
const session = this.#sessionManager.get(id);
|
|
214
|
+
if (!session) return false;
|
|
215
|
+
session.reset();
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Execution (convenience methods using default session)
|
|
221
|
+
// ============================================================================
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Execute code (MRP POST /execute)
|
|
225
|
+
* @param {string} code
|
|
226
|
+
* @param {ExecuteOptions} [options]
|
|
227
|
+
* @returns {Promise<ExecutionResult>}
|
|
228
|
+
*/
|
|
229
|
+
async execute(code, options = {}) {
|
|
230
|
+
const session = this.getOrCreateSession(options.session ?? 'default');
|
|
231
|
+
return session.execute(code, options);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Execute code with streaming output (MRP POST /execute/stream)
|
|
236
|
+
* @param {string} code
|
|
237
|
+
* @param {ExecuteOptions} [options]
|
|
238
|
+
* @returns {AsyncGenerator<StreamEvent>}
|
|
239
|
+
*/
|
|
240
|
+
async *executeStream(code, options = {}) {
|
|
241
|
+
const session = this.getOrCreateSession(options.session ?? 'default');
|
|
242
|
+
yield* session.executeStream(code, options);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Send input to a waiting execution (MRP POST /input)
|
|
247
|
+
* @param {string} sessionId
|
|
248
|
+
* @param {string} execId
|
|
249
|
+
* @param {string} text
|
|
250
|
+
* @returns {boolean}
|
|
251
|
+
*/
|
|
252
|
+
sendInput(sessionId, execId, text) {
|
|
253
|
+
const session = this.#sessionManager.get(sessionId);
|
|
254
|
+
if (!session) return false;
|
|
255
|
+
return session.sendInput(execId, text);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Interrupt execution (MRP POST /interrupt)
|
|
260
|
+
* @param {string} [sessionId]
|
|
261
|
+
* @param {string} [execId]
|
|
262
|
+
* @returns {boolean}
|
|
263
|
+
*/
|
|
264
|
+
interrupt(sessionId, execId) {
|
|
265
|
+
if (sessionId) {
|
|
266
|
+
const session = this.#sessionManager.get(sessionId);
|
|
267
|
+
if (!session) return false;
|
|
268
|
+
return session.interrupt(execId);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Interrupt all sessions
|
|
272
|
+
return this.#sessionManager.interruptAll();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// LSP Features (convenience methods)
|
|
277
|
+
// ============================================================================
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get completions (MRP POST /complete)
|
|
281
|
+
* @param {string} code
|
|
282
|
+
* @param {number} cursor
|
|
283
|
+
* @param {CompleteOptions} [options]
|
|
284
|
+
* @returns {CompletionResult}
|
|
285
|
+
*/
|
|
286
|
+
complete(code, cursor, options = {}) {
|
|
287
|
+
const session = this.getOrCreateSession(options.session ?? 'default');
|
|
288
|
+
return session.complete(code, cursor, options);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get hover info (MRP POST /hover)
|
|
293
|
+
* @param {string} code
|
|
294
|
+
* @param {number} cursor
|
|
295
|
+
* @param {string} [sessionId='default']
|
|
296
|
+
* @returns {HoverResult}
|
|
297
|
+
*/
|
|
298
|
+
hover(code, cursor, sessionId = 'default') {
|
|
299
|
+
const session = this.getOrCreateSession(sessionId);
|
|
300
|
+
return session.hover(code, cursor);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get inspection info (MRP POST /inspect)
|
|
305
|
+
* @param {string} code
|
|
306
|
+
* @param {number} cursor
|
|
307
|
+
* @param {InspectOptions} [options]
|
|
308
|
+
* @returns {InspectResult}
|
|
309
|
+
*/
|
|
310
|
+
inspect(code, cursor, options = {}) {
|
|
311
|
+
const session = this.getOrCreateSession(options.session ?? 'default');
|
|
312
|
+
return session.inspect(code, cursor, options);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* List variables (MRP POST /variables)
|
|
317
|
+
* @param {VariableFilter} [filter]
|
|
318
|
+
* @param {string} [sessionId='default']
|
|
319
|
+
* @returns {VariableInfo[]}
|
|
320
|
+
*/
|
|
321
|
+
listVariables(filter, sessionId = 'default') {
|
|
322
|
+
const session = this.getOrCreateSession(sessionId);
|
|
323
|
+
return session.listVariables(filter);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get variable detail (MRP POST /variables/{name})
|
|
328
|
+
* @param {string} name
|
|
329
|
+
* @param {VariableDetailOptions} [options]
|
|
330
|
+
* @returns {VariableDetail | null}
|
|
331
|
+
*/
|
|
332
|
+
getVariable(name, options = {}) {
|
|
333
|
+
const session = this.getOrCreateSession(options.session ?? 'default');
|
|
334
|
+
return session.getVariable(name, options);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// Analysis
|
|
339
|
+
// ============================================================================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Check if code is complete (MRP POST /is_complete)
|
|
343
|
+
* @param {string} code
|
|
344
|
+
* @param {string} [sessionId='default']
|
|
345
|
+
* @returns {IsCompleteResult}
|
|
346
|
+
*/
|
|
347
|
+
isComplete(code, sessionId = 'default') {
|
|
348
|
+
const session = this.getOrCreateSession(sessionId);
|
|
349
|
+
return session.isComplete(code);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Format code (MRP POST /format)
|
|
354
|
+
* @param {string} code
|
|
355
|
+
* @param {string} [sessionId='default']
|
|
356
|
+
* @returns {Promise<FormatResult>}
|
|
357
|
+
*/
|
|
358
|
+
async format(code, sessionId = 'default') {
|
|
359
|
+
const session = this.getOrCreateSession(sessionId);
|
|
360
|
+
return session.format(code);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ============================================================================
|
|
364
|
+
// Assets
|
|
365
|
+
// ============================================================================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Create an asset (blob URL)
|
|
369
|
+
* @param {Blob | string} content
|
|
370
|
+
* @param {string} mimeType
|
|
371
|
+
* @param {string} [name]
|
|
372
|
+
* @returns {import('./types/execution.js').Asset}
|
|
373
|
+
*/
|
|
374
|
+
createAsset(content, mimeType, name) {
|
|
375
|
+
const blob = typeof content === 'string'
|
|
376
|
+
? new Blob([content], { type: mimeType })
|
|
377
|
+
: content;
|
|
378
|
+
|
|
379
|
+
const url = URL.createObjectURL(blob);
|
|
380
|
+
const path = name ?? `asset-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
381
|
+
|
|
382
|
+
const assetType = mimeType.startsWith('image/')
|
|
383
|
+
? 'image'
|
|
384
|
+
: mimeType === 'text/html'
|
|
385
|
+
? 'html'
|
|
386
|
+
: mimeType === 'application/json'
|
|
387
|
+
? 'json'
|
|
388
|
+
: 'other';
|
|
389
|
+
|
|
390
|
+
this.#assets.set(path, {
|
|
391
|
+
url,
|
|
392
|
+
mimeType,
|
|
393
|
+
assetType,
|
|
394
|
+
size: blob.size,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
path,
|
|
399
|
+
url,
|
|
400
|
+
mimeType,
|
|
401
|
+
assetType,
|
|
402
|
+
size: blob.size,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get an asset by path (MRP GET /assets/{path})
|
|
408
|
+
* @param {string} path
|
|
409
|
+
* @returns {string | null} Blob URL or null if not found
|
|
410
|
+
*/
|
|
411
|
+
getAsset(path) {
|
|
412
|
+
const asset = this.#assets.get(path);
|
|
413
|
+
return asset?.url ?? null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get asset info
|
|
418
|
+
* @param {string} path
|
|
419
|
+
* @returns {StoredAsset | null}
|
|
420
|
+
*/
|
|
421
|
+
getAssetInfo(path) {
|
|
422
|
+
return this.#assets.get(path) ?? null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* List all assets
|
|
427
|
+
* @returns {Array<{ path: string } & StoredAsset>}
|
|
428
|
+
*/
|
|
429
|
+
listAssets() {
|
|
430
|
+
return Array.from(this.#assets.entries()).map(([path, asset]) => ({
|
|
431
|
+
path,
|
|
432
|
+
...asset,
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Remove an asset
|
|
438
|
+
* @param {string} path
|
|
439
|
+
* @returns {boolean}
|
|
440
|
+
*/
|
|
441
|
+
removeAsset(path) {
|
|
442
|
+
const asset = this.#assets.get(path);
|
|
443
|
+
if (!asset) return false;
|
|
444
|
+
|
|
445
|
+
URL.revokeObjectURL(asset.url);
|
|
446
|
+
this.#assets.delete(path);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Clear all assets
|
|
452
|
+
*/
|
|
453
|
+
clearAssets() {
|
|
454
|
+
for (const asset of this.#assets.values()) {
|
|
455
|
+
URL.revokeObjectURL(asset.url);
|
|
456
|
+
}
|
|
457
|
+
this.#assets.clear();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ============================================================================
|
|
461
|
+
// Extensibility
|
|
462
|
+
// ============================================================================
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Register a custom executor for a language
|
|
466
|
+
* @param {string} language
|
|
467
|
+
* @param {Executor} executor
|
|
468
|
+
*/
|
|
469
|
+
registerExecutor(language, executor) {
|
|
470
|
+
this.#executorRegistry.register(language, executor);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Register a language alias
|
|
475
|
+
* @param {string} alias
|
|
476
|
+
* @param {string} language
|
|
477
|
+
*/
|
|
478
|
+
registerLanguageAlias(alias, language) {
|
|
479
|
+
this.#executorRegistry.registerAlias(alias, language);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Get the executor registry
|
|
484
|
+
* @returns {ExecutorRegistry}
|
|
485
|
+
*/
|
|
486
|
+
getExecutorRegistry() {
|
|
487
|
+
return this.#executorRegistry;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Get the session manager
|
|
492
|
+
* @returns {SessionManager}
|
|
493
|
+
*/
|
|
494
|
+
getSessionManager() {
|
|
495
|
+
return this.#sessionManager;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ============================================================================
|
|
499
|
+
// Lifecycle
|
|
500
|
+
// ============================================================================
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Destroy the runtime and clean up all resources
|
|
504
|
+
*/
|
|
505
|
+
destroy() {
|
|
506
|
+
this.#sessionManager.destroyAll();
|
|
507
|
+
this.clearAssets();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Create a new MRP runtime
|
|
513
|
+
* @param {MrpRuntimeOptions} [options]
|
|
514
|
+
* @returns {MrpRuntime}
|
|
515
|
+
*/
|
|
516
|
+
export function createRuntime(options) {
|
|
517
|
+
return new MrpRuntime(options);
|
|
518
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Capture
|
|
3
|
+
*
|
|
4
|
+
* Intercepts console methods to capture output during execution.
|
|
5
|
+
* @module session/console-capture
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('./context/interface.js').LogEntry} LogEntry
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Format arguments for logging
|
|
14
|
+
* @param {Array<*>} args
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function formatArgs(args) {
|
|
18
|
+
return args
|
|
19
|
+
.map((arg) => {
|
|
20
|
+
if (arg === null) return 'null';
|
|
21
|
+
if (arg === undefined) return 'undefined';
|
|
22
|
+
if (typeof arg === 'object') {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.stringify(arg, null, 2);
|
|
25
|
+
} catch {
|
|
26
|
+
return String(arg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return String(arg);
|
|
30
|
+
})
|
|
31
|
+
.join(' ');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a console capture for a window context
|
|
36
|
+
*/
|
|
37
|
+
export class ConsoleCapture {
|
|
38
|
+
/** @type {Window} */
|
|
39
|
+
#context;
|
|
40
|
+
|
|
41
|
+
/** @type {LogEntry[]} */
|
|
42
|
+
#queue = [];
|
|
43
|
+
|
|
44
|
+
/** @type {Partial<Console> | null} */
|
|
45
|
+
#originalConsole = null;
|
|
46
|
+
|
|
47
|
+
/** @type {boolean} */
|
|
48
|
+
#active = false;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {Window} context - The window context to capture console from
|
|
52
|
+
*/
|
|
53
|
+
constructor(context) {
|
|
54
|
+
this.#context = context;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Start capturing console output
|
|
59
|
+
*/
|
|
60
|
+
start() {
|
|
61
|
+
if (this.#active) return;
|
|
62
|
+
|
|
63
|
+
const console = this.#context.console;
|
|
64
|
+
|
|
65
|
+
// Save originals
|
|
66
|
+
this.#originalConsole = {
|
|
67
|
+
log: console.log.bind(console),
|
|
68
|
+
info: console.info.bind(console),
|
|
69
|
+
warn: console.warn.bind(console),
|
|
70
|
+
error: console.error.bind(console),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Intercept methods
|
|
74
|
+
console.log = (...args) => {
|
|
75
|
+
this.#queue.push({ type: 'log', args, timestamp: Date.now() });
|
|
76
|
+
this.#originalConsole?.log?.(...args);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
console.info = (...args) => {
|
|
80
|
+
this.#queue.push({ type: 'info', args, timestamp: Date.now() });
|
|
81
|
+
this.#originalConsole?.info?.(...args);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
console.warn = (...args) => {
|
|
85
|
+
this.#queue.push({ type: 'warn', args, timestamp: Date.now() });
|
|
86
|
+
this.#originalConsole?.warn?.(...args);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
console.error = (...args) => {
|
|
90
|
+
this.#queue.push({ type: 'error', args, timestamp: Date.now() });
|
|
91
|
+
this.#originalConsole?.error?.(...args);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.#active = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Stop capturing and restore original console
|
|
99
|
+
*/
|
|
100
|
+
stop() {
|
|
101
|
+
if (!this.#active || !this.#originalConsole) return;
|
|
102
|
+
|
|
103
|
+
const console = this.#context.console;
|
|
104
|
+
|
|
105
|
+
if (this.#originalConsole.log) {
|
|
106
|
+
console.log = this.#originalConsole.log;
|
|
107
|
+
}
|
|
108
|
+
if (this.#originalConsole.info) {
|
|
109
|
+
console.info = this.#originalConsole.info;
|
|
110
|
+
}
|
|
111
|
+
if (this.#originalConsole.warn) {
|
|
112
|
+
console.warn = this.#originalConsole.warn;
|
|
113
|
+
}
|
|
114
|
+
if (this.#originalConsole.error) {
|
|
115
|
+
console.error = this.#originalConsole.error;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.#originalConsole = null;
|
|
119
|
+
this.#active = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Clear the log queue
|
|
124
|
+
*/
|
|
125
|
+
clear() {
|
|
126
|
+
this.#queue = [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get captured logs and clear queue
|
|
131
|
+
* @returns {LogEntry[]}
|
|
132
|
+
*/
|
|
133
|
+
flush() {
|
|
134
|
+
const logs = this.#queue;
|
|
135
|
+
this.#queue = [];
|
|
136
|
+
return logs;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get captured logs without clearing
|
|
141
|
+
* @returns {LogEntry[]}
|
|
142
|
+
*/
|
|
143
|
+
peek() {
|
|
144
|
+
return [...this.#queue];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Convert logs to stdout/stderr strings
|
|
149
|
+
* @param {LogEntry[]} logs
|
|
150
|
+
* @returns {{ stdout: string, stderr: string }}
|
|
151
|
+
*/
|
|
152
|
+
static toOutput(logs) {
|
|
153
|
+
const stdout = [];
|
|
154
|
+
const stderr = [];
|
|
155
|
+
|
|
156
|
+
for (const log of logs) {
|
|
157
|
+
const formatted = formatArgs(log.args);
|
|
158
|
+
if (log.type === 'error') {
|
|
159
|
+
stderr.push(`Error: ${formatted}`);
|
|
160
|
+
} else if (log.type === 'warn') {
|
|
161
|
+
stderr.push(`Warning: ${formatted}`);
|
|
162
|
+
} else {
|
|
163
|
+
stdout.push(formatted);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
stdout: stdout.join('\n'),
|
|
169
|
+
stderr: stderr.join('\n'),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Create a console capture for a context
|
|
176
|
+
* @param {Window} context
|
|
177
|
+
* @returns {ConsoleCapture}
|
|
178
|
+
*/
|
|
179
|
+
export function createConsoleCapture(context) {
|
|
180
|
+
return new ConsoleCapture(context);
|
|
181
|
+
}
|