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
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Class
|
|
3
|
+
*
|
|
4
|
+
* A session is an isolated execution context that persists variables
|
|
5
|
+
* across executions. It wraps an ExecutionContext and provides the
|
|
6
|
+
* full MRP session API.
|
|
7
|
+
*
|
|
8
|
+
* @module session/session
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { IframeContext } from './context/iframe.js';
|
|
12
|
+
import { MainContext } from './context/main.js';
|
|
13
|
+
import { extractDeclaredVariables } from '../transform/extract.js';
|
|
14
|
+
import { JavaScriptExecutor } from '../execute/javascript.js';
|
|
15
|
+
|
|
16
|
+
// LSP Features
|
|
17
|
+
import { getCompletions } from '../lsp/complete.js';
|
|
18
|
+
import { getHoverInfo } from '../lsp/hover.js';
|
|
19
|
+
import { getInspectInfo } from '../lsp/inspect.js';
|
|
20
|
+
import {
|
|
21
|
+
listVariables as lspListVariables,
|
|
22
|
+
getVariableDetail as lspGetVariableDetail,
|
|
23
|
+
} from '../lsp/variables.js';
|
|
24
|
+
|
|
25
|
+
// Analysis Features
|
|
26
|
+
import { isComplete as analysisIsComplete } from '../analysis/is-complete.js';
|
|
27
|
+
import { formatCode } from '../analysis/format.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {import('../execute/registry.js').ExecutorRegistry} ExecutorRegistry
|
|
31
|
+
* @typedef {import('../execute/interface.js').Executor} Executor
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {import('./context/interface.js').ExecutionContext} ExecutionContext
|
|
36
|
+
* @typedef {import('./context/interface.js').RawExecutionResult} RawExecutionResult
|
|
37
|
+
* @typedef {import('../types/session.js').SessionInfo} SessionInfo
|
|
38
|
+
* @typedef {import('../types/session.js').CreateSessionOptions} CreateSessionOptions
|
|
39
|
+
* @typedef {import('../types/session.js').IsolationMode} IsolationMode
|
|
40
|
+
* @typedef {import('../types/execution.js').ExecuteOptions} ExecuteOptions
|
|
41
|
+
* @typedef {import('../types/execution.js').ExecutionResult} ExecutionResult
|
|
42
|
+
* @typedef {import('../types/execution.js').ExecutionError} ExecutionError
|
|
43
|
+
* @typedef {import('../types/execution.js').DisplayData} DisplayData
|
|
44
|
+
* @typedef {import('../types/streaming.js').StreamEvent} StreamEvent
|
|
45
|
+
* @typedef {import('../types/completion.js').CompleteOptions} CompleteOptions
|
|
46
|
+
* @typedef {import('../types/completion.js').CompletionResult} CompletionResult
|
|
47
|
+
* @typedef {import('../types/inspection.js').InspectOptions} InspectOptions
|
|
48
|
+
* @typedef {import('../types/inspection.js').InspectResult} InspectResult
|
|
49
|
+
* @typedef {import('../types/inspection.js').HoverResult} HoverResult
|
|
50
|
+
* @typedef {import('../types/variables.js').VariableFilter} VariableFilter
|
|
51
|
+
* @typedef {import('../types/variables.js').VariableInfo} VariableInfo
|
|
52
|
+
* @typedef {import('../types/variables.js').VariableDetailOptions} VariableDetailOptions
|
|
53
|
+
* @typedef {import('../types/variables.js').VariableDetail} VariableDetail
|
|
54
|
+
* @typedef {import('../types/analysis.js').IsCompleteResult} IsCompleteResult
|
|
55
|
+
* @typedef {import('../types/analysis.js').FormatResult} FormatResult
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate a unique execution ID
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
function generateExecId() {
|
|
63
|
+
return `exec-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Session class - represents an isolated execution context
|
|
68
|
+
*/
|
|
69
|
+
export class Session {
|
|
70
|
+
/** @type {string} */
|
|
71
|
+
#id;
|
|
72
|
+
|
|
73
|
+
/** @type {string} */
|
|
74
|
+
#language;
|
|
75
|
+
|
|
76
|
+
/** @type {IsolationMode} */
|
|
77
|
+
#isolation;
|
|
78
|
+
|
|
79
|
+
/** @type {Date} */
|
|
80
|
+
#created;
|
|
81
|
+
|
|
82
|
+
/** @type {Date} */
|
|
83
|
+
#lastActivity;
|
|
84
|
+
|
|
85
|
+
/** @type {number} */
|
|
86
|
+
#executionCount = 0;
|
|
87
|
+
|
|
88
|
+
/** @type {ExecutionContext} */
|
|
89
|
+
#context;
|
|
90
|
+
|
|
91
|
+
/** @type {ExecutorRegistry | null} */
|
|
92
|
+
#executorRegistry = null;
|
|
93
|
+
|
|
94
|
+
/** @type {JavaScriptExecutor} */
|
|
95
|
+
#defaultJsExecutor;
|
|
96
|
+
|
|
97
|
+
/** @type {Map<string, AbortController>} */
|
|
98
|
+
#runningExecutions = new Map();
|
|
99
|
+
|
|
100
|
+
/** @type {Map<string, (text: string) => void>} */
|
|
101
|
+
#pendingInputs = new Map();
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} id - Session ID
|
|
105
|
+
* @param {CreateSessionOptions & { executorRegistry?: ExecutorRegistry }} [options]
|
|
106
|
+
*/
|
|
107
|
+
constructor(id, options = {}) {
|
|
108
|
+
this.#id = id;
|
|
109
|
+
this.#language = options.language || 'javascript';
|
|
110
|
+
this.#isolation = options.isolation || 'iframe';
|
|
111
|
+
this.#created = new Date();
|
|
112
|
+
this.#lastActivity = new Date();
|
|
113
|
+
|
|
114
|
+
// Store executor registry if provided
|
|
115
|
+
this.#executorRegistry = options.executorRegistry || null;
|
|
116
|
+
|
|
117
|
+
// Create default JS executor for fallback
|
|
118
|
+
this.#defaultJsExecutor = new JavaScriptExecutor();
|
|
119
|
+
|
|
120
|
+
// Create the appropriate context
|
|
121
|
+
this.#context = this.#createContext(options);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create the execution context based on isolation mode
|
|
126
|
+
* @param {CreateSessionOptions} options
|
|
127
|
+
* @returns {ExecutionContext}
|
|
128
|
+
*/
|
|
129
|
+
#createContext(options) {
|
|
130
|
+
switch (this.#isolation) {
|
|
131
|
+
case 'none':
|
|
132
|
+
return new MainContext({
|
|
133
|
+
utilities: options.utilities,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
case 'iframe':
|
|
137
|
+
default:
|
|
138
|
+
return new IframeContext({
|
|
139
|
+
visible: false,
|
|
140
|
+
allowMainAccess: options.allowMainAccess ?? false,
|
|
141
|
+
utilities: options.utilities,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Properties
|
|
148
|
+
// ============================================================================
|
|
149
|
+
|
|
150
|
+
/** @returns {string} */
|
|
151
|
+
get id() {
|
|
152
|
+
return this.#id;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** @returns {string} */
|
|
156
|
+
get language() {
|
|
157
|
+
return this.#language;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @returns {IsolationMode} */
|
|
161
|
+
get isolation() {
|
|
162
|
+
return this.#isolation;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @returns {Date} */
|
|
166
|
+
get created() {
|
|
167
|
+
return this.#created;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @returns {Date} */
|
|
171
|
+
get lastActivity() {
|
|
172
|
+
return this.#lastActivity;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @returns {number} */
|
|
176
|
+
get executionCount() {
|
|
177
|
+
return this.#executionCount;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// Execution
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Execute code and return result
|
|
186
|
+
* @param {string} code - Code to execute
|
|
187
|
+
* @param {ExecuteOptions} [options]
|
|
188
|
+
* @returns {Promise<ExecutionResult>}
|
|
189
|
+
*/
|
|
190
|
+
async execute(code, options = {}) {
|
|
191
|
+
const execId = options.execId || generateExecId();
|
|
192
|
+
const language = options.language || this.#language;
|
|
193
|
+
|
|
194
|
+
// Update activity
|
|
195
|
+
this.#lastActivity = new Date();
|
|
196
|
+
|
|
197
|
+
// Track execution
|
|
198
|
+
const abortController = new AbortController();
|
|
199
|
+
this.#runningExecutions.set(execId, abortController);
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Get the executor for this language
|
|
203
|
+
const executor = this.#getExecutor(language);
|
|
204
|
+
|
|
205
|
+
// Execute using the executor
|
|
206
|
+
const result = await executor.execute(code, this.#context, {
|
|
207
|
+
...options,
|
|
208
|
+
execId,
|
|
209
|
+
language,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Update execution count
|
|
213
|
+
if (options.storeHistory !== false) {
|
|
214
|
+
this.#executionCount++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Update result with session's execution count
|
|
218
|
+
result.executionCount = this.#executionCount;
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
} finally {
|
|
222
|
+
this.#runningExecutions.delete(execId);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get the executor for a language
|
|
228
|
+
* @param {string} language
|
|
229
|
+
* @returns {Executor}
|
|
230
|
+
*/
|
|
231
|
+
#getExecutor(language) {
|
|
232
|
+
// Try registry first
|
|
233
|
+
if (this.#executorRegistry) {
|
|
234
|
+
const executor = this.#executorRegistry.get(language);
|
|
235
|
+
if (executor) return executor;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Fall back to default JS executor for JavaScript
|
|
239
|
+
const lang = language.toLowerCase();
|
|
240
|
+
if (['javascript', 'js', 'ecmascript', 'es'].includes(lang)) {
|
|
241
|
+
return this.#defaultJsExecutor;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Return a no-op executor that reports unsupported language
|
|
245
|
+
return {
|
|
246
|
+
languages: [],
|
|
247
|
+
async execute(code, context, options) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
stdout: '',
|
|
251
|
+
stderr: `No executor registered for language: ${language}`,
|
|
252
|
+
error: {
|
|
253
|
+
type: 'ExecutorError',
|
|
254
|
+
message: `No executor registered for language: ${language}. Register an ExecutorRegistry with HTML/CSS executors to support this language.`,
|
|
255
|
+
},
|
|
256
|
+
displayData: [],
|
|
257
|
+
assets: [],
|
|
258
|
+
executionCount: 0,
|
|
259
|
+
duration: 0,
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Execute code with streaming output
|
|
267
|
+
* @param {string} code - Code to execute
|
|
268
|
+
* @param {ExecuteOptions} [options]
|
|
269
|
+
* @returns {AsyncGenerator<StreamEvent>}
|
|
270
|
+
*/
|
|
271
|
+
async *executeStream(code, options = {}) {
|
|
272
|
+
const execId = options.execId || generateExecId();
|
|
273
|
+
const language = options.language || this.#language;
|
|
274
|
+
|
|
275
|
+
// Update activity
|
|
276
|
+
this.#lastActivity = new Date();
|
|
277
|
+
|
|
278
|
+
// Track execution
|
|
279
|
+
const abortController = new AbortController();
|
|
280
|
+
this.#runningExecutions.set(execId, abortController);
|
|
281
|
+
|
|
282
|
+
// Event queue for stdin_request events
|
|
283
|
+
/** @type {Array<import('../types/streaming.js').StdinRequestEvent>} */
|
|
284
|
+
const stdinEventQueue = [];
|
|
285
|
+
let stdinEventResolve = null;
|
|
286
|
+
|
|
287
|
+
// Set up stdin handler on context to capture input requests
|
|
288
|
+
// and yield stdin_request events
|
|
289
|
+
const previousHandler = this.#context.getStdinHandler?.();
|
|
290
|
+
|
|
291
|
+
if (this.#context.setStdinHandler) {
|
|
292
|
+
this.#context.setStdinHandler((request) => {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
// Store the resolver for when sendInput is called
|
|
295
|
+
this.#pendingInputs.set(request.execId, resolve);
|
|
296
|
+
|
|
297
|
+
// Queue the stdin_request event to be yielded
|
|
298
|
+
stdinEventQueue.push({
|
|
299
|
+
type: 'stdin_request',
|
|
300
|
+
prompt: request.prompt,
|
|
301
|
+
password: request.password,
|
|
302
|
+
execId: request.execId,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Wake up the event loop if waiting
|
|
306
|
+
if (stdinEventResolve) {
|
|
307
|
+
stdinEventResolve();
|
|
308
|
+
stdinEventResolve = null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Set up abort handling
|
|
312
|
+
abortController.signal.addEventListener('abort', () => {
|
|
313
|
+
this.#pendingInputs.delete(request.execId);
|
|
314
|
+
reject(new Error('Execution aborted'));
|
|
315
|
+
}, { once: true });
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
// Get the executor for this language
|
|
322
|
+
const executor = this.#getExecutor(language);
|
|
323
|
+
|
|
324
|
+
// Use executor's streaming if available
|
|
325
|
+
if (executor.executeStream) {
|
|
326
|
+
let executionCount = this.#executionCount;
|
|
327
|
+
|
|
328
|
+
for await (const event of executor.executeStream(code, this.#context, {
|
|
329
|
+
...options,
|
|
330
|
+
execId,
|
|
331
|
+
language,
|
|
332
|
+
})) {
|
|
333
|
+
// Yield any pending stdin_request events first
|
|
334
|
+
while (stdinEventQueue.length > 0) {
|
|
335
|
+
yield /** @type {import('../types/streaming.js').StdinRequestEvent} */ (stdinEventQueue.shift());
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Update execution count on result event
|
|
339
|
+
if (event.type === 'result' && options.storeHistory !== false) {
|
|
340
|
+
this.#executionCount++;
|
|
341
|
+
event.result.executionCount = this.#executionCount;
|
|
342
|
+
}
|
|
343
|
+
yield event;
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
// Fall back to wrapping execute()
|
|
347
|
+
const timestamp = new Date().toISOString();
|
|
348
|
+
|
|
349
|
+
yield /** @type {import('../types/streaming.js').StartEvent} */ ({
|
|
350
|
+
type: 'start',
|
|
351
|
+
execId,
|
|
352
|
+
timestamp,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Start execution in background so we can yield events
|
|
356
|
+
const executionPromise = executor.execute(code, this.#context, {
|
|
357
|
+
...options,
|
|
358
|
+
execId,
|
|
359
|
+
language,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Poll for stdin events while execution is running
|
|
363
|
+
let result = null;
|
|
364
|
+
let error = null;
|
|
365
|
+
let done = false;
|
|
366
|
+
|
|
367
|
+
executionPromise.then(r => {
|
|
368
|
+
result = r;
|
|
369
|
+
done = true;
|
|
370
|
+
if (stdinEventResolve) {
|
|
371
|
+
stdinEventResolve();
|
|
372
|
+
stdinEventResolve = null;
|
|
373
|
+
}
|
|
374
|
+
}).catch(e => {
|
|
375
|
+
error = e;
|
|
376
|
+
done = true;
|
|
377
|
+
if (stdinEventResolve) {
|
|
378
|
+
stdinEventResolve();
|
|
379
|
+
stdinEventResolve = null;
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Wait for either stdin events or completion
|
|
384
|
+
while (!done) {
|
|
385
|
+
// Yield any pending stdin_request events
|
|
386
|
+
while (stdinEventQueue.length > 0) {
|
|
387
|
+
yield /** @type {import('../types/streaming.js').StdinRequestEvent} */ (stdinEventQueue.shift());
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (!done) {
|
|
391
|
+
// Wait for next event
|
|
392
|
+
await new Promise(resolve => {
|
|
393
|
+
stdinEventResolve = resolve;
|
|
394
|
+
// Also resolve on short timeout to check done flag
|
|
395
|
+
setTimeout(resolve, 50);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Yield any remaining stdin events
|
|
401
|
+
while (stdinEventQueue.length > 0) {
|
|
402
|
+
yield /** @type {import('../types/streaming.js').StdinRequestEvent} */ (stdinEventQueue.shift());
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (error) {
|
|
406
|
+
yield /** @type {import('../types/streaming.js').ErrorEvent} */ ({
|
|
407
|
+
type: 'error',
|
|
408
|
+
error: {
|
|
409
|
+
type: error instanceof Error ? error.name : 'Error',
|
|
410
|
+
message: error instanceof Error ? error.message : String(error),
|
|
411
|
+
traceback: error instanceof Error && error.stack ? error.stack.split('\n') : undefined,
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
} else if (result) {
|
|
415
|
+
if (options.storeHistory !== false) {
|
|
416
|
+
this.#executionCount++;
|
|
417
|
+
}
|
|
418
|
+
result.executionCount = this.#executionCount;
|
|
419
|
+
|
|
420
|
+
if (result.stdout) {
|
|
421
|
+
yield /** @type {import('../types/streaming.js').StdoutEvent} */ ({
|
|
422
|
+
type: 'stdout',
|
|
423
|
+
content: result.stdout,
|
|
424
|
+
accumulated: result.stdout,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (result.stderr) {
|
|
429
|
+
yield /** @type {import('../types/streaming.js').StderrEvent} */ ({
|
|
430
|
+
type: 'stderr',
|
|
431
|
+
content: result.stderr,
|
|
432
|
+
accumulated: result.stderr,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
for (const display of result.displayData) {
|
|
437
|
+
yield /** @type {import('../types/streaming.js').DisplayEvent} */ ({
|
|
438
|
+
type: 'display',
|
|
439
|
+
data: display.data,
|
|
440
|
+
metadata: display.metadata,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
for (const asset of result.assets) {
|
|
445
|
+
yield /** @type {import('../types/streaming.js').AssetEvent} */ ({
|
|
446
|
+
type: 'asset',
|
|
447
|
+
path: asset.path,
|
|
448
|
+
url: asset.url,
|
|
449
|
+
mimeType: asset.mimeType,
|
|
450
|
+
assetType: asset.assetType,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
yield /** @type {import('../types/streaming.js').ResultEvent} */ ({
|
|
455
|
+
type: 'result',
|
|
456
|
+
result,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
yield /** @type {import('../types/streaming.js').DoneEvent} */ ({
|
|
461
|
+
type: 'done',
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
} finally {
|
|
465
|
+
// Restore previous stdin handler
|
|
466
|
+
if (this.#context.setStdinHandler) {
|
|
467
|
+
this.#context.setStdinHandler(previousHandler || null);
|
|
468
|
+
}
|
|
469
|
+
this.#runningExecutions.delete(execId);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Set the executor registry
|
|
475
|
+
* @param {ExecutorRegistry} registry
|
|
476
|
+
*/
|
|
477
|
+
setExecutorRegistry(registry) {
|
|
478
|
+
this.#executorRegistry = registry;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get the executor registry
|
|
483
|
+
* @returns {ExecutorRegistry | null}
|
|
484
|
+
*/
|
|
485
|
+
getExecutorRegistry() {
|
|
486
|
+
return this.#executorRegistry;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Get supported languages
|
|
491
|
+
* @returns {string[]}
|
|
492
|
+
*/
|
|
493
|
+
getSupportedLanguages() {
|
|
494
|
+
if (this.#executorRegistry) {
|
|
495
|
+
return this.#executorRegistry.languages();
|
|
496
|
+
}
|
|
497
|
+
return ['javascript', 'js', 'ecmascript', 'es'];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Send input to a waiting execution
|
|
502
|
+
* @param {string} execId - Execution ID
|
|
503
|
+
* @param {string} text - Input text
|
|
504
|
+
* @returns {boolean} Whether input was accepted
|
|
505
|
+
*/
|
|
506
|
+
sendInput(execId, text) {
|
|
507
|
+
const handler = this.#pendingInputs.get(execId);
|
|
508
|
+
if (handler) {
|
|
509
|
+
handler(text);
|
|
510
|
+
this.#pendingInputs.delete(execId);
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Interrupt a running execution
|
|
518
|
+
* @param {string} [execId] - Specific execution ID, or all if not provided
|
|
519
|
+
* @returns {boolean} Whether any execution was interrupted
|
|
520
|
+
*/
|
|
521
|
+
interrupt(execId) {
|
|
522
|
+
if (execId) {
|
|
523
|
+
const controller = this.#runningExecutions.get(execId);
|
|
524
|
+
if (controller) {
|
|
525
|
+
controller.abort();
|
|
526
|
+
this.#runningExecutions.delete(execId);
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Interrupt all
|
|
533
|
+
if (this.#runningExecutions.size > 0) {
|
|
534
|
+
for (const controller of this.#runningExecutions.values()) {
|
|
535
|
+
controller.abort();
|
|
536
|
+
}
|
|
537
|
+
this.#runningExecutions.clear();
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ============================================================================
|
|
545
|
+
// LSP Features (delegated to lsp/ modules)
|
|
546
|
+
// ============================================================================
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Get completions at cursor position
|
|
550
|
+
* @param {string} code
|
|
551
|
+
* @param {number} cursor
|
|
552
|
+
* @param {CompleteOptions} [options]
|
|
553
|
+
* @returns {CompletionResult}
|
|
554
|
+
*/
|
|
555
|
+
complete(code, cursor, options = {}) {
|
|
556
|
+
return getCompletions(code, cursor, this.#context, options);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Get hover information at cursor position
|
|
561
|
+
* @param {string} code
|
|
562
|
+
* @param {number} cursor
|
|
563
|
+
* @returns {HoverResult}
|
|
564
|
+
*/
|
|
565
|
+
hover(code, cursor) {
|
|
566
|
+
return getHoverInfo(code, cursor, this.#context);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Get detailed inspection at cursor position
|
|
571
|
+
* @param {string} code
|
|
572
|
+
* @param {number} cursor
|
|
573
|
+
* @param {InspectOptions} [options]
|
|
574
|
+
* @returns {InspectResult}
|
|
575
|
+
*/
|
|
576
|
+
inspect(code, cursor, options = {}) {
|
|
577
|
+
return getInspectInfo(code, cursor, this.#context, options);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* List all variables in session
|
|
582
|
+
* @param {VariableFilter} [filter]
|
|
583
|
+
* @returns {VariableInfo[]}
|
|
584
|
+
*/
|
|
585
|
+
listVariables(filter = {}) {
|
|
586
|
+
return lspListVariables(this.#context, filter);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Get detailed information about a variable
|
|
591
|
+
* @param {string} name
|
|
592
|
+
* @param {VariableDetailOptions} [options]
|
|
593
|
+
* @returns {VariableDetail | null}
|
|
594
|
+
*/
|
|
595
|
+
getVariable(name, options = {}) {
|
|
596
|
+
return lspGetVariableDetail(name, this.#context, options);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ============================================================================
|
|
600
|
+
// Analysis
|
|
601
|
+
// ============================================================================
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Check if code is a complete statement
|
|
605
|
+
* @param {string} code
|
|
606
|
+
* @returns {IsCompleteResult}
|
|
607
|
+
*/
|
|
608
|
+
isComplete(code) {
|
|
609
|
+
return analysisIsComplete(code);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Format code
|
|
614
|
+
* @param {string} code
|
|
615
|
+
* @returns {Promise<FormatResult>}
|
|
616
|
+
*/
|
|
617
|
+
async format(code) {
|
|
618
|
+
return formatCode(code);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ============================================================================
|
|
622
|
+
// Lifecycle
|
|
623
|
+
// ============================================================================
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Reset the session (clear variables but keep session)
|
|
627
|
+
*/
|
|
628
|
+
reset() {
|
|
629
|
+
this.#context.reset();
|
|
630
|
+
this.#executionCount = 0;
|
|
631
|
+
this.#lastActivity = new Date();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Destroy the session and release resources
|
|
636
|
+
*/
|
|
637
|
+
destroy() {
|
|
638
|
+
// Cancel any running executions
|
|
639
|
+
this.interrupt();
|
|
640
|
+
|
|
641
|
+
// Destroy context
|
|
642
|
+
this.#context.destroy();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Get session info
|
|
647
|
+
* @returns {SessionInfo}
|
|
648
|
+
*/
|
|
649
|
+
getInfo() {
|
|
650
|
+
return {
|
|
651
|
+
id: this.#id,
|
|
652
|
+
language: this.#language,
|
|
653
|
+
created: this.#created.toISOString(),
|
|
654
|
+
lastActivity: this.#lastActivity.toISOString(),
|
|
655
|
+
executionCount: this.#executionCount,
|
|
656
|
+
variableCount: this.#context.getTrackedVariables().size,
|
|
657
|
+
isolation: this.#isolation,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Get the underlying execution context (for advanced use)
|
|
663
|
+
* @returns {ExecutionContext}
|
|
664
|
+
*/
|
|
665
|
+
getContext() {
|
|
666
|
+
return this.#context;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Create a session
|
|
672
|
+
* @param {string} id
|
|
673
|
+
* @param {CreateSessionOptions} [options]
|
|
674
|
+
* @returns {Session}
|
|
675
|
+
*/
|
|
676
|
+
export function createSession(id, options) {
|
|
677
|
+
return new Session(id, options);
|
|
678
|
+
}
|