@tlockney/cdp-kernel 0.7.2 → 0.9.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 +4 -2
- package/dist/browser/kernel.js +211 -42
- package/dist/browser/kernel.js.map +2 -2
- package/dist/browser/target.d.ts +2 -1
- package/dist/browser/target.js +29 -7
- package/dist/browser/worker.js +47 -4
- package/dist/browser/worker.js.map +2 -2
- package/dist/bun-target.d.ts +5 -1
- package/dist/bun-target.js +32 -7
- package/dist/crash-reports.d.ts +47 -0
- package/dist/crash-reports.js +155 -0
- package/dist/globals.d.ts +35 -4
- package/dist/kernel-core.d.ts +7 -0
- package/dist/kernel-core.js +215 -35
- package/dist/kernel.js +10 -0
- package/dist/main.js +31 -14
- package/dist/message.d.ts +1 -1
- package/dist/message.js +1 -1
- package/dist/package.d.ts +2 -2
- package/dist/package.js +2 -2
- package/dist/target-api.d.ts +11 -0
- package/dist/target.d.ts +9 -2
- package/dist/target.js +66 -12
- package/dist/transform.d.ts +10 -1
- package/dist/transform.js +23 -3
- package/dist/usage.d.ts +25 -0
- package/dist/usage.js +197 -0
- package/dist/workerd-target.d.ts +13 -2
- package/dist/workerd-target.js +36 -6
- package/dist/workers-project.js +2 -0
- package/package.json +1 -1
- package/runtime/bootstrap.mjs +65 -7
- package/runtime/browser-worker.mjs +6 -2
- package/runtime/comm.d.mts +87 -1
- package/runtime/display.d.mts +261 -4
- package/runtime/display.mjs +42 -1
- package/runtime/evaluator.d.mts +5 -1
- package/runtime/evaluator.mjs +12 -1
- package/runtime/widget.d.mts +205 -6
- package/runtime/workerd-worker.mjs +6 -2
package/dist/kernel-core.js
CHANGED
|
@@ -19,6 +19,8 @@ import { buildTraceback, cellName, TRACEBACK_MIME } from "./traceback.js";
|
|
|
19
19
|
* installed TypeScript's: the major version package.json depends on.
|
|
20
20
|
*/
|
|
21
21
|
export const TYPESCRIPT_VERSION = "6";
|
|
22
|
+
/** Errors from launching a target, which `KernelHost.fault` doesn't get. */
|
|
23
|
+
const launchFailures = new WeakSet();
|
|
22
24
|
function fromBase64(text) {
|
|
23
25
|
return Uint8Array.from(atob(text), (c) => c.charCodeAt(0));
|
|
24
26
|
}
|
|
@@ -28,8 +30,46 @@ function fromBase64(text) {
|
|
|
28
30
|
*/
|
|
29
31
|
const INTERRUPT_REPLY_WAIT_MS = 1_000;
|
|
30
32
|
const NO_INPUT = { value: null, interrupted: false };
|
|
33
|
+
/**
|
|
34
|
+
* Shell messages handled one at a time, in order: the main shell, or a
|
|
35
|
+
* kernel subshell (JEP 91, #417), each with its own.
|
|
36
|
+
*/
|
|
37
|
+
class ShellQueue {
|
|
38
|
+
items = [];
|
|
39
|
+
#wake = null;
|
|
40
|
+
#closed = false;
|
|
41
|
+
push(item) {
|
|
42
|
+
if (this.#closed)
|
|
43
|
+
return;
|
|
44
|
+
this.items.push(item);
|
|
45
|
+
this.#wake?.();
|
|
46
|
+
}
|
|
47
|
+
/** The next message; null once closed. */
|
|
48
|
+
async next() {
|
|
49
|
+
for (;;) {
|
|
50
|
+
if (this.#closed)
|
|
51
|
+
return null;
|
|
52
|
+
const item = this.items.shift();
|
|
53
|
+
if (item !== undefined)
|
|
54
|
+
return item;
|
|
55
|
+
await new Promise((resolve) => (this.#wake = resolve));
|
|
56
|
+
this.#wake = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Stops taking messages; returns those still queued. */
|
|
60
|
+
close() {
|
|
61
|
+
this.#closed = true;
|
|
62
|
+
const dropped = this.items.splice(0);
|
|
63
|
+
this.#wake?.();
|
|
64
|
+
return dropped;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
31
67
|
/** Cells remembered for tracebacks: a frame can be in a function any earlier cell defined. */
|
|
32
68
|
const MAX_CELLS = 1000;
|
|
69
|
+
/** The error reply for a subshell that doesn't exist (#417). */
|
|
70
|
+
function subshellNotFound(evalue) {
|
|
71
|
+
return { status: "error", ename: "SubshellNotFound", evalue, traceback: [`SubshellNotFound: ${evalue}`] };
|
|
72
|
+
}
|
|
33
73
|
/** Shell requests other than execute_request, and their reply types. */
|
|
34
74
|
const SHELL_REPLIES = {
|
|
35
75
|
kernel_info_request: "kernel_info_reply",
|
|
@@ -84,13 +124,16 @@ export class KernelCore {
|
|
|
84
124
|
#executionCount = 0;
|
|
85
125
|
#parent = {};
|
|
86
126
|
#allowStdin = false;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
#abortQueued = 0;
|
|
127
|
+
/** Cells and comm handlers running in the target. */
|
|
128
|
+
#running = 0;
|
|
129
|
+
/** A cell is running: its parent header and allow_stdin hold until it ends (#417). */
|
|
130
|
+
#cellRunning = false;
|
|
131
|
+
/** stop_on_error (#123): the message being handled was queued behind a failed cell. */
|
|
93
132
|
#aborting = false;
|
|
133
|
+
/** Messages without a subshell_id, in the order they came. */
|
|
134
|
+
#mainShell = new ShellQueue();
|
|
135
|
+
/** Kernel subshells (JEP 91, #417): subshell_id -> its queue. */
|
|
136
|
+
#subshells = new Map();
|
|
94
137
|
/** The input_request waiting for its input_reply, if any. */
|
|
95
138
|
#input = null;
|
|
96
139
|
/** An interrupt is waiting on the target. */
|
|
@@ -117,7 +160,10 @@ export class KernelCore {
|
|
|
117
160
|
// Transforms are synchronous, so the stripper loads before any cell.
|
|
118
161
|
if (typeof this.#typeStrip === "string")
|
|
119
162
|
await loadTypeStripper(this.#typeStrip);
|
|
120
|
-
|
|
163
|
+
// No shell or control reply until every port is bound: a client that
|
|
164
|
+
// has kernel_info_reply goes on to the heartbeat port (#438).
|
|
165
|
+
await this.#channels.ready;
|
|
166
|
+
await Promise.all([this.#readShell(), this.#runShell(this.#mainShell),
|
|
121
167
|
this.#loop("control", (m, p) => this.#handleControl(m, p)),
|
|
122
168
|
this.#loop("stdin", (m) => this.#handleStdin(m))]);
|
|
123
169
|
}
|
|
@@ -141,8 +187,9 @@ export class KernelCore {
|
|
|
141
187
|
targetState = await target.diagnose().catch((err) => `diagnose failed: ${err}`);
|
|
142
188
|
this.#log(`state: ${JSON.stringify({
|
|
143
189
|
shell: handling && { msgType: handling.msgType, msgId: handling.msgId, ms: Date.now() - handling.since },
|
|
144
|
-
queued: this.#channels.shellQueued(),
|
|
145
|
-
|
|
190
|
+
queued: this.#mainShell.items.length + this.#channels.shellQueued(),
|
|
191
|
+
subshells: this.#subshells.size,
|
|
192
|
+
running: this.#running,
|
|
146
193
|
interrupting: this.#interrupting,
|
|
147
194
|
awaitingInput: this.#input !== null,
|
|
148
195
|
target: targetState,
|
|
@@ -175,16 +222,21 @@ export class KernelCore {
|
|
|
175
222
|
const type = msg.header.msg_type;
|
|
176
223
|
const commId = String(msg.content.comm_id ?? "");
|
|
177
224
|
const target = await this.#ensureTarget();
|
|
178
|
-
// Kernel messages the handlers send are replies to this message
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.#
|
|
225
|
+
// Kernel messages the handlers send are replies to this message, unless
|
|
226
|
+
// it came on a subshell while a cell runs (#417): the target's output
|
|
227
|
+
// doesn't say which it came from, so it stays the cell's.
|
|
228
|
+
if (!this.#cellRunning) {
|
|
229
|
+
this.#parent = msg.header;
|
|
230
|
+
this.#allowStdin = false;
|
|
231
|
+
}
|
|
232
|
+
const parent = msg.header;
|
|
182
233
|
let handled = false;
|
|
234
|
+
this.#running++;
|
|
183
235
|
try {
|
|
184
236
|
const result = await target.dispatchComm(type, msg.content, msg.metadata, msg.buffers);
|
|
185
237
|
if ("error" in result) {
|
|
186
238
|
const { name, message, stack } = result.error;
|
|
187
|
-
this.#publish("stream", { name: "stderr", text: `Error in ${type} handler: ${stack || `${name}: ${message}`}\n` });
|
|
239
|
+
this.#publish("stream", { name: "stderr", text: `Error in ${type} handler: ${stack || `${name}: ${message}`}\n` }, { parent });
|
|
188
240
|
}
|
|
189
241
|
else {
|
|
190
242
|
handled = result.handled;
|
|
@@ -193,10 +245,10 @@ export class KernelCore {
|
|
|
193
245
|
catch (err) {
|
|
194
246
|
if (!(err instanceof Interrupted))
|
|
195
247
|
throw err;
|
|
196
|
-
this.#publish("stream", { name: "stderr", text: `${type} handler interrupted\n` });
|
|
248
|
+
this.#publish("stream", { name: "stderr", text: `${type} handler interrupted\n` }, { parent });
|
|
197
249
|
}
|
|
198
250
|
finally {
|
|
199
|
-
this.#
|
|
251
|
+
this.#running--;
|
|
200
252
|
}
|
|
201
253
|
await target.flush().catch(() => { });
|
|
202
254
|
if (type === "comm_open") {
|
|
@@ -206,7 +258,7 @@ export class KernelCore {
|
|
|
206
258
|
else {
|
|
207
259
|
// An unknown target, or a handler that failed: the protocol asks the
|
|
208
260
|
// kernel to close the comm so the frontend doesn't wait on it.
|
|
209
|
-
this.#publish("comm_close", { comm_id: commId, data: {} });
|
|
261
|
+
this.#publish("comm_close", { comm_id: commId, data: {} }, { parent });
|
|
210
262
|
}
|
|
211
263
|
}
|
|
212
264
|
else if (type === "comm_close") {
|
|
@@ -228,6 +280,10 @@ export class KernelCore {
|
|
|
228
280
|
return target;
|
|
229
281
|
}, (err) => {
|
|
230
282
|
this.#targetStarting = null;
|
|
283
|
+
// Usually the user's environment (a missing runtime, a bad config),
|
|
284
|
+
// not a kernel fault, and its message quotes the child's stderr.
|
|
285
|
+
if (typeof err === "object" && err !== null)
|
|
286
|
+
launchFailures.add(err);
|
|
231
287
|
throw err;
|
|
232
288
|
});
|
|
233
289
|
return this.#targetStarting;
|
|
@@ -249,10 +305,19 @@ export class KernelCore {
|
|
|
249
305
|
this.#interruptStart = null;
|
|
250
306
|
}
|
|
251
307
|
}
|
|
308
|
+
/** Tells the host of a fault in the kernel's own code; a target that failed to launch isn't one (#422). */
|
|
309
|
+
#fault(err, where) {
|
|
310
|
+
if (typeof err === "object" && err !== null && launchFailures.has(err))
|
|
311
|
+
return;
|
|
312
|
+
this.#host.fault?.(err, where);
|
|
313
|
+
}
|
|
252
314
|
#targetEvents() {
|
|
253
315
|
return {
|
|
254
316
|
stream: (name, text) => this.#publish("stream", { name, text }),
|
|
255
|
-
message: (msg) => void this.#onTargetMessage(msg).catch((err) =>
|
|
317
|
+
message: (msg) => void this.#onTargetMessage(msg).catch((err) => {
|
|
318
|
+
this.#log(`error handling target message: ${err}`);
|
|
319
|
+
this.#fault(err, "target message");
|
|
320
|
+
}),
|
|
256
321
|
exit: (code, note) => {
|
|
257
322
|
this.#target = null;
|
|
258
323
|
// What its cells declared died with it.
|
|
@@ -302,7 +367,7 @@ export class KernelCore {
|
|
|
302
367
|
this.#interruptStart();
|
|
303
368
|
return;
|
|
304
369
|
}
|
|
305
|
-
if (!this.#target ||
|
|
370
|
+
if (!this.#target || this.#running === 0)
|
|
306
371
|
return;
|
|
307
372
|
this.#interrupting = true;
|
|
308
373
|
try {
|
|
@@ -334,27 +399,85 @@ export class KernelCore {
|
|
|
334
399
|
async #loop(channel, handle) {
|
|
335
400
|
while (!this.#shuttingDown) {
|
|
336
401
|
const received = await this.#channels.recv(channel);
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
402
|
+
// Malformed, or on ZeroMQ unsigned or forged: dropped, as in Deno.
|
|
403
|
+
if (received === null)
|
|
404
|
+
continue;
|
|
405
|
+
try {
|
|
406
|
+
await handle(received.msg, received.peer);
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
this.#log(`error handling message: ${err instanceof Error ? err.stack : err}`);
|
|
410
|
+
this.#fault(err, `${channel} handler`);
|
|
343
411
|
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Takes every shell message as it comes and queues it for the main shell
|
|
416
|
+
* or its subshell, so a subshell's message isn't stuck behind a running
|
|
417
|
+
* cell (#417).
|
|
418
|
+
*/
|
|
419
|
+
async #readShell() {
|
|
420
|
+
while (!this.#shuttingDown) {
|
|
421
|
+
const received = await this.#channels.recv("shell");
|
|
344
422
|
// Malformed, or on ZeroMQ unsigned or forged: dropped, as in Deno.
|
|
345
423
|
if (received === null)
|
|
346
424
|
continue;
|
|
347
|
-
const
|
|
425
|
+
const subshellId = received.msg.header.subshell_id;
|
|
426
|
+
if (typeof subshellId !== "string") {
|
|
427
|
+
this.#mainShell.push({ ...received, abort: false });
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const queue = this.#subshells.get(subshellId);
|
|
431
|
+
if (queue === undefined) {
|
|
432
|
+
this.#refuseShell(received, `No subshell with id ${subshellId}`);
|
|
433
|
+
}
|
|
434
|
+
else if (received.msg.header.msg_type === "execute_request") {
|
|
435
|
+
// Cells share one target and one parent header, so a cell sent on
|
|
436
|
+
// a subshell waits its turn on the main shell.
|
|
437
|
+
this.#mainShell.push({ ...received, abort: false });
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
queue.push({ ...received, abort: false });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Answers a shell message for a subshell that doesn't exist, or was
|
|
446
|
+
* deleted with the message queued, so the frontend doesn't wait on it.
|
|
447
|
+
*/
|
|
448
|
+
#refuseShell({ peer, msg }, evalue) {
|
|
449
|
+
const type = msg.header.msg_type;
|
|
450
|
+
this.#log(`refusing ${type}: ${evalue}`);
|
|
451
|
+
this.#status("busy", msg.header);
|
|
452
|
+
const replyType = type === "execute_request" ? "execute_reply" : SHELL_REPLIES[type];
|
|
453
|
+
if (replyType !== undefined)
|
|
454
|
+
this.#reply("shell", peer, msg, replyType, subshellNotFound(evalue));
|
|
455
|
+
// As for an unknown target: the frontend shouldn't wait on the comm.
|
|
456
|
+
if (type === "comm_open")
|
|
457
|
+
this.#publish("comm_close", { comm_id: String(msg.content.comm_id ?? ""), data: {} }, { parent: msg.header });
|
|
458
|
+
this.#status("idle", msg.header);
|
|
459
|
+
}
|
|
460
|
+
/** Handles one queue's messages in turn. */
|
|
461
|
+
async #runShell(queue) {
|
|
462
|
+
const main = queue === this.#mainShell;
|
|
463
|
+
while (!this.#shuttingDown) {
|
|
464
|
+
const queued = await queue.next();
|
|
465
|
+
if (queued === null)
|
|
466
|
+
return;
|
|
467
|
+
const { peer, msg } = queued;
|
|
348
468
|
try {
|
|
349
|
-
if (
|
|
469
|
+
if (main) {
|
|
470
|
+
this.#aborting = queued.abort;
|
|
350
471
|
this.#handling = { msgType: msg.header.msg_type, msgId: msg.header.msg_id, since: Date.now() };
|
|
351
|
-
|
|
472
|
+
}
|
|
473
|
+
await this.#handleShell(msg, peer);
|
|
352
474
|
}
|
|
353
475
|
catch (err) {
|
|
354
476
|
this.#log(`error handling message: ${err instanceof Error ? err.stack : err}`);
|
|
477
|
+
this.#fault(err, "shell handler");
|
|
355
478
|
}
|
|
356
479
|
finally {
|
|
357
|
-
if (
|
|
480
|
+
if (main)
|
|
358
481
|
this.#handling = null;
|
|
359
482
|
}
|
|
360
483
|
}
|
|
@@ -369,7 +492,12 @@ export class KernelCore {
|
|
|
369
492
|
language_info: {
|
|
370
493
|
name: "typescript",
|
|
371
494
|
version: this.#host.languageVersion ?? TYPESCRIPT_VERSION,
|
|
372
|
-
|
|
495
|
+
// JupyterLab's LSP adapter picks the language server by a language
|
|
496
|
+
// it derives from this MIME type, not from name: it drops the type
|
|
497
|
+
// and an "x-" prefix, so "text/x.typescript" became "x.typescript"
|
|
498
|
+
// and matched no server (#400). This is CodeMirror's MIME type for
|
|
499
|
+
// TypeScript and in jupyter-lsp's typescript-language-server spec.
|
|
500
|
+
mimetype: "application/typescript",
|
|
373
501
|
file_extension: ".ts",
|
|
374
502
|
pygments_lexer: "typescript",
|
|
375
503
|
codemirror_mode: "typescript",
|
|
@@ -381,12 +509,22 @@ export class KernelCore {
|
|
|
381
509
|
debugger: this.#debugger !== null,
|
|
382
510
|
// What current ipykernel advertises; JupyterLab itself goes by the
|
|
383
511
|
// kernelspec's metadata.debugger.
|
|
384
|
-
supported_features: this.#debugger !== null ? ["debugger"] : [],
|
|
512
|
+
supported_features: [...(this.#debugger !== null ? ["debugger"] : []), "kernel subshells"],
|
|
385
513
|
...(this.#host.capabilities ? { capabilities: this.#host.capabilities } : {}),
|
|
386
514
|
};
|
|
387
515
|
}
|
|
388
516
|
// --- control channel ------------------------------------------------------
|
|
389
517
|
async #handleControl(msg, peer) {
|
|
518
|
+
// A frontend polls usage, and measuring can take a moment (`ps` on
|
|
519
|
+
// macOS): answer it beside the queue, so an interrupt or shutdown
|
|
520
|
+
// never waits behind a poll (#409).
|
|
521
|
+
if (msg.header.msg_type === "usage_request") {
|
|
522
|
+
void this.#handleControlRequest(msg, peer).catch((err) => this.#log(`usage_request failed: ${err}`));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
return this.#handleControlRequest(msg, peer);
|
|
526
|
+
}
|
|
527
|
+
async #handleControlRequest(msg, peer) {
|
|
390
528
|
// Every request is wrapped in busy/idle, as on shell (#124). JupyterLab
|
|
391
529
|
// treats a debug request as done only once the kernel reports idle for
|
|
392
530
|
// it. (Jupyter Server leaves debug_request out of the kernel's status.)
|
|
@@ -412,6 +550,42 @@ export class KernelCore {
|
|
|
412
550
|
]);
|
|
413
551
|
this.#reply(channel, peer, msg, "interrupt_reply", { status: "ok" });
|
|
414
552
|
break;
|
|
553
|
+
case "create_subshell_request": {
|
|
554
|
+
const subshellId = crypto.randomUUID();
|
|
555
|
+
const queue = new ShellQueue();
|
|
556
|
+
this.#subshells.set(subshellId, queue);
|
|
557
|
+
void this.#runShell(queue);
|
|
558
|
+
this.#reply(channel, peer, msg, "create_subshell_reply", { status: "ok", subshell_id: subshellId });
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
case "delete_subshell_request": {
|
|
562
|
+
const subshellId = String(msg.content.subshell_id ?? "");
|
|
563
|
+
const queue = this.#subshells.get(subshellId);
|
|
564
|
+
if (queue === undefined) {
|
|
565
|
+
this.#reply(channel, peer, msg, "delete_subshell_reply", subshellNotFound(`No subshell with id ${subshellId}`));
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
// A message it is handling finishes; what's queued is refused.
|
|
569
|
+
this.#subshells.delete(subshellId);
|
|
570
|
+
for (const dropped of queue.close())
|
|
571
|
+
this.#refuseShell(dropped, `Subshell ${subshellId} was deleted`);
|
|
572
|
+
this.#reply(channel, peer, msg, "delete_subshell_reply", { status: "ok" });
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
case "list_subshell_request":
|
|
576
|
+
this.#reply(channel, peer, msg, "list_subshell_reply", { status: "ok", subshell_id: [...this.#subshells.keys()] });
|
|
577
|
+
break;
|
|
578
|
+
case "usage_request": {
|
|
579
|
+
// Without starting a target: a frontend polls this (#409). A host
|
|
580
|
+
// that can't measure anything still answers, so the frontend
|
|
581
|
+
// doesn't wait out its timeout.
|
|
582
|
+
const usage = this.#host.usage?.(this.#target).catch((err) => {
|
|
583
|
+
this.#log(`usage_request failed: ${err}`);
|
|
584
|
+
return {};
|
|
585
|
+
});
|
|
586
|
+
this.#reply(channel, peer, msg, "usage_reply", (await usage) ?? {});
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
415
589
|
case "debug_request": {
|
|
416
590
|
let reply;
|
|
417
591
|
if (this.#debugger === null) {
|
|
@@ -745,7 +919,8 @@ export class KernelCore {
|
|
|
745
919
|
let userExpressions = {};
|
|
746
920
|
// nteract's structured traceback, sent as the error message's metadata (#322).
|
|
747
921
|
let structured = null;
|
|
748
|
-
this.#
|
|
922
|
+
this.#running++;
|
|
923
|
+
this.#cellRunning = true;
|
|
749
924
|
try {
|
|
750
925
|
const target = await this.#targetForCell();
|
|
751
926
|
if (target.notice !== undefined && this.#noticeShownFor !== target) {
|
|
@@ -796,7 +971,8 @@ export class KernelCore {
|
|
|
796
971
|
}
|
|
797
972
|
}
|
|
798
973
|
finally {
|
|
799
|
-
this.#
|
|
974
|
+
this.#running--;
|
|
975
|
+
this.#cellRunning = false;
|
|
800
976
|
}
|
|
801
977
|
// The result and error are shown: nothing refers to them any more (#286).
|
|
802
978
|
await this.#target?.releaseResults?.();
|
|
@@ -805,10 +981,14 @@ export class KernelCore {
|
|
|
805
981
|
// Abort what was queued behind this cell (JupyterLab's Run All sends
|
|
806
982
|
// every cell at once), but nothing sent after the client sees the
|
|
807
983
|
// error. A macrotask first lets bytes already received be parsed.
|
|
984
|
+
// Shell messages are queued as they're read, so by then each is.
|
|
808
985
|
await new Promise((r) => setTimeout(r, 0));
|
|
809
|
-
|
|
986
|
+
for (const queued of this.#mainShell.items)
|
|
987
|
+
queued.abort = true;
|
|
810
988
|
}
|
|
811
|
-
|
|
989
|
+
// With its parent: a subshell's comm handler may have taken #parent
|
|
990
|
+
// since the cell ended (#417).
|
|
991
|
+
this.#publish("error", error, { parent, ...(structured ? { metadata: { [TRACEBACK_MIME]: structured } } : {}) });
|
|
812
992
|
this.#reply("shell", peer, msg, "execute_reply", { status: "error", execution_count: count, ...error });
|
|
813
993
|
}
|
|
814
994
|
else {
|
package/dist/kernel.js
CHANGED
|
@@ -18,6 +18,8 @@ import { cacheRoot, VERSION } from "./package.js";
|
|
|
18
18
|
import { watchParent } from "./parent.js";
|
|
19
19
|
import { KernelCore } from "./kernel-core.js";
|
|
20
20
|
import { ZmqChannels } from "./zmq-channels.js";
|
|
21
|
+
import { UsageMeter } from "./usage.js";
|
|
22
|
+
import { crashReporter, reportUncaught } from "./crash-reports.js";
|
|
21
23
|
/** The installed TypeScript's version, for kernel_info's language_info (#340). */
|
|
22
24
|
function typescriptVersion() {
|
|
23
25
|
try {
|
|
@@ -141,6 +143,12 @@ export class Kernel {
|
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
const denoCompat = options.denoCompat ?? false;
|
|
146
|
+
const usage = new UsageMeter();
|
|
147
|
+
// Opt-in reports of the kernel's own faults (#422): null, and nothing
|
|
148
|
+
// installed, unless CDP_KERNEL_CRASH_REPORTS=1.
|
|
149
|
+
const reporter = crashReporter({ target: runtime });
|
|
150
|
+
if (reporter)
|
|
151
|
+
reportUncaught(reporter);
|
|
144
152
|
this.#core = new KernelCore({
|
|
145
153
|
runtime,
|
|
146
154
|
version: VERSION,
|
|
@@ -164,9 +172,11 @@ export class Kernel {
|
|
|
164
172
|
createDebugger: runtime === "node" || runtime === "deno"
|
|
165
173
|
? (publish, transformOptions) => new Debugger(publish, transformOptions)
|
|
166
174
|
: undefined,
|
|
175
|
+
usage: (target) => usage.reply(target),
|
|
167
176
|
readSource,
|
|
168
177
|
kernelFiles: kernelFiles(),
|
|
169
178
|
log,
|
|
179
|
+
fault: reporter ? (err, where) => void reporter.report(err, where) : undefined,
|
|
170
180
|
exit: () => process.exit(0),
|
|
171
181
|
});
|
|
172
182
|
}
|
package/dist/main.js
CHANGED
|
@@ -135,19 +135,36 @@ function uninstall(names, all) {
|
|
|
135
135
|
if (failed)
|
|
136
136
|
process.exit(1);
|
|
137
137
|
}
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
138
|
+
const USAGE = "usage: cdp-kernel install [--runtime node|deno|bun|workerd|celld] [--deno-compat] [--type-strip esbuild|sucrase] [--no-language-service] | list | uninstall [NAME...] [--all] | kernel --runtime node|deno|bun|workerd|celld --conn FILE | version";
|
|
139
|
+
/** The command line, or exit 2 with the usage when it doesn't parse (an unknown option, a missing value). */
|
|
140
|
+
function parseCommandLine() {
|
|
141
|
+
try {
|
|
142
|
+
return parseArgs({
|
|
143
|
+
allowPositionals: true,
|
|
144
|
+
options: {
|
|
145
|
+
runtime: { type: "string", default: "node" },
|
|
146
|
+
conn: { type: "string" },
|
|
147
|
+
name: { type: "string" },
|
|
148
|
+
display: { type: "string" },
|
|
149
|
+
all: { type: "boolean", default: false },
|
|
150
|
+
"deno-compat": { type: "boolean", default: false },
|
|
151
|
+
"type-strip": { type: "string", default: "esbuild" },
|
|
152
|
+
"no-language-service": { type: "boolean", default: false },
|
|
153
|
+
help: { type: "boolean", short: "h", default: false },
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error(`cdp-kernel: ${err instanceof Error ? err.message.split(". ")[0] : String(err)}`);
|
|
159
|
+
console.error(USAGE);
|
|
160
|
+
process.exit(2);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const { positionals, values } = parseCommandLine();
|
|
164
|
+
if (values.help || positionals[0] === "help") {
|
|
165
|
+
console.log(USAGE);
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
151
168
|
const runtime = values.runtime;
|
|
152
169
|
if (!isRuntime(runtime)) {
|
|
153
170
|
console.error(`unknown runtime ${runtime}; expected node, deno, bun, workerd or celld`);
|
|
@@ -186,6 +203,6 @@ switch (positionals[0]) {
|
|
|
186
203
|
console.log(VERSION);
|
|
187
204
|
break;
|
|
188
205
|
default:
|
|
189
|
-
console.error(
|
|
206
|
+
console.error(USAGE);
|
|
190
207
|
process.exit(2);
|
|
191
208
|
}
|
package/dist/message.d.ts
CHANGED
package/dist/message.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Jupyter message shapes and headers, without the wire format: what the
|
|
2
2
|
// kernel's message handling (src/kernel-core.ts) builds and reads. The
|
|
3
3
|
// framing and HMAC signing that ZeroMQ peers need are in src/wire.ts.
|
|
4
|
-
export const PROTOCOL_VERSION = "5.
|
|
4
|
+
export const PROTOCOL_VERSION = "5.5";
|
|
5
5
|
export function makeHeader(session, msgType) {
|
|
6
6
|
return {
|
|
7
7
|
msg_id: crypto.randomUUID(),
|
package/dist/package.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.9.0";
|
|
3
3
|
/**
|
|
4
4
|
* The UTC day this version was released, set with VERSION. The publish
|
|
5
5
|
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
6
6
|
* stale: see dependencyCutoff.
|
|
7
7
|
*/
|
|
8
|
-
export declare const RELEASE_DATE = "2026-11-
|
|
8
|
+
export declare const RELEASE_DATE = "2026-11-09";
|
|
9
9
|
/**
|
|
10
10
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
11
11
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
package/dist/package.js
CHANGED
|
@@ -14,13 +14,13 @@ import { join, sep } from "node:path";
|
|
|
14
14
|
import process from "node:process";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
17
|
-
export const VERSION = "0.
|
|
17
|
+
export const VERSION = "0.9.0";
|
|
18
18
|
/**
|
|
19
19
|
* The UTC day this version was released, set with VERSION. The publish
|
|
20
20
|
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
21
21
|
* stale: see dependencyCutoff.
|
|
22
22
|
*/
|
|
23
|
-
export const RELEASE_DATE = "2026-11-
|
|
23
|
+
export const RELEASE_DATE = "2026-11-09";
|
|
24
24
|
/**
|
|
25
25
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
26
26
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
package/dist/target-api.d.ts
CHANGED
|
@@ -52,8 +52,19 @@ export interface KernelTarget {
|
|
|
52
52
|
diagnose?(): Promise<CdpParams>;
|
|
53
53
|
/** Lets go of the last cell's result and error, once they are shown (#286). */
|
|
54
54
|
releaseResults?(): Promise<void>;
|
|
55
|
+
/** The target process's memory and CPU time, for usage_request (#409). */
|
|
56
|
+
usage?(): Promise<TargetUsage>;
|
|
55
57
|
kill(): void;
|
|
56
58
|
}
|
|
59
|
+
/** What a target measured of its own process; a field it can't measure is left out (#409). */
|
|
60
|
+
export interface TargetUsage {
|
|
61
|
+
/** Resident memory, in bytes. */
|
|
62
|
+
memory?: number;
|
|
63
|
+
/** CPU time used since it started, in µs. */
|
|
64
|
+
cpuTime?: number;
|
|
65
|
+
/** When it started (ms since the epoch), the start of its first CPU interval. */
|
|
66
|
+
startedAt?: number;
|
|
67
|
+
}
|
|
57
68
|
/** Messages the bootstrap sends: message records on stdout, or Bun's channel. */
|
|
58
69
|
export type TargetMessage = {
|
|
59
70
|
kind: "iopub";
|
package/dist/target.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CdpParams, type EvaluateResult, type RemoteObject } from "./cdp.ts";
|
|
2
2
|
import { transformCell } from "./transform.ts";
|
|
3
3
|
import type { DebugChannel } from "./debugger.ts";
|
|
4
|
-
import { type CdpRuntime, Interrupted, isTargetMessage, type KernelTarget, parseBundle, parseJson, type TargetEvents, type TargetMessage } from "./target-api.ts";
|
|
4
|
+
import { type CdpRuntime, Interrupted, isTargetMessage, type KernelTarget, parseBundle, parseJson, type TargetEvents, type TargetMessage, type TargetUsage } from "./target-api.ts";
|
|
5
5
|
export { Interrupted, isTargetMessage, parseBundle, parseJson };
|
|
6
6
|
export type { CdpRuntime, KernelTarget, RuntimeName, TargetEvents, TargetMessage } from "./target-api.ts";
|
|
7
7
|
export interface LaunchOptions {
|
|
@@ -89,6 +89,12 @@ export declare class Target implements KernelTarget {
|
|
|
89
89
|
}): Promise<Target>;
|
|
90
90
|
/** The Debugger domain on this target's session, for src/debugger.ts. */
|
|
91
91
|
get debug(): DebugChannel;
|
|
92
|
+
/**
|
|
93
|
+
* The runtime process's resident memory and CPU time, from the OS (#409).
|
|
94
|
+
* Where the OS can't say, the memory is the V8 heap's size instead,
|
|
95
|
+
* which leaves out native memory such as Buffers; CDP has no CPU time.
|
|
96
|
+
*/
|
|
97
|
+
usage(): Promise<TargetUsage>;
|
|
92
98
|
/**
|
|
93
99
|
* What the host is waiting on, whether the runtime still evaluates, runs
|
|
94
100
|
* immediates and runs timers, and where its JavaScript is if it is
|
|
@@ -106,7 +112,8 @@ export declare class Target implements KernelTarget {
|
|
|
106
112
|
* CDP equivalent is Runtime.terminateExecution, sent on a second session
|
|
107
113
|
* because the shell session is the one blocked on the running evaluate.
|
|
108
114
|
* Terminated code never settles the awaited promise, so the pending
|
|
109
|
-
* evaluate is abandoned here and the session stays usable.
|
|
115
|
+
* evaluate is abandoned here and the session stays usable; jupyter.signal
|
|
116
|
+
* tells code still waiting on such a promise to stop (#418).
|
|
110
117
|
*/
|
|
111
118
|
interrupt(): Promise<void>;
|
|
112
119
|
/**
|