@tlockney/cdp-kernel 0.8.0 → 0.10.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 +592 -426
- package/dist/browser/kernel.js.map +3 -3
- package/dist/browser/target.d.ts +7 -0
- package/dist/browser/target.js +2 -1
- package/dist/browser/worker.js +224 -6
- package/dist/browser/worker.js.map +4 -4
- package/dist/celld-target.d.ts +2 -1
- package/dist/celld-target.js +5 -3
- package/dist/globals.d.ts +35 -4
- package/dist/kernel-core.js +11 -0
- package/dist/package.d.ts +3 -3
- package/dist/package.js +3 -2
- package/dist/transform.d.ts +10 -1
- package/dist/transform.js +20 -2
- package/dist/workerd-target.js +6 -2
- package/dist/workers-project.js +1 -1
- package/package.json +3 -3
- package/runtime/bootstrap.mjs +2 -0
- package/runtime/browser-worker.mjs +5 -1
- package/runtime/comm.d.mts +87 -1
- package/runtime/display.d.mts +315 -6
- package/runtime/display.mjs +63 -0
- package/runtime/host.d.mts +246 -0
- package/runtime/host.mjs +215 -0
- package/runtime/widget.d.mts +205 -6
- package/runtime/workerd-worker.mjs +5 -1
package/dist/kernel-core.js
CHANGED
|
@@ -160,6 +160,9 @@ export class KernelCore {
|
|
|
160
160
|
// Transforms are synchronous, so the stripper loads before any cell.
|
|
161
161
|
if (typeof this.#typeStrip === "string")
|
|
162
162
|
await loadTypeStripper(this.#typeStrip);
|
|
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;
|
|
163
166
|
await Promise.all([this.#readShell(), this.#runShell(this.#mainShell),
|
|
164
167
|
this.#loop("control", (m, p) => this.#handleControl(m, p)),
|
|
165
168
|
this.#loop("stdin", (m) => this.#handleStdin(m))]);
|
|
@@ -322,6 +325,14 @@ export class KernelCore {
|
|
|
322
325
|
// Its prompt, if any, died with it; a late reply is dropped.
|
|
323
326
|
this.#input = null;
|
|
324
327
|
void this.#debugger?.attachTarget(null);
|
|
328
|
+
// Its comms died with it: tell the frontends, so a widget or a
|
|
329
|
+
// Ganymede host comm isn't left waiting on a runtime that is gone,
|
|
330
|
+
// and the next runtime can open its own (#461).
|
|
331
|
+
if (!this.#shuttingDown) {
|
|
332
|
+
for (const commId of this.#comms.keys())
|
|
333
|
+
this.#publish("comm_close", { comm_id: commId, data: {} });
|
|
334
|
+
}
|
|
335
|
+
this.#comms.clear();
|
|
325
336
|
if (!this.#shuttingDown) {
|
|
326
337
|
const text = note ?? `[cdp-kernel] ${this.#runtime} exited (${code}); restarting on next cell`;
|
|
327
338
|
this.#publish("stream", { name: "stderr", text: `\n${text}\n` });
|
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.10.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-11";
|
|
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
|
|
@@ -14,7 +14,7 @@ export declare const RELEASE_DATE = "2026-11-06";
|
|
|
14
14
|
* late in the day its tag was pushed), and nothing newer ever is.
|
|
15
15
|
*/
|
|
16
16
|
export declare function dependencyCutoff(releaseDate?: string): string;
|
|
17
|
-
export declare const RUNTIME_FILES: readonly ["bootstrap.mjs", "bun-channel.mjs", "evaluator.mjs", "introspect.mjs", "workerd-worker.mjs", "display.mjs", "comm.mjs", "widget.mjs", "node-hooks.mjs", "deno-compat.mjs", "node-modules-resolve.mjs"];
|
|
17
|
+
export declare const RUNTIME_FILES: readonly ["bootstrap.mjs", "bun-channel.mjs", "evaluator.mjs", "introspect.mjs", "workerd-worker.mjs", "display.mjs", "comm.mjs", "host.mjs", "widget.mjs", "node-hooks.mjs", "deno-compat.mjs", "node-modules-resolve.mjs"];
|
|
18
18
|
export type HostRuntime = "node" | "deno";
|
|
19
19
|
export declare function hostRuntime(): HostRuntime;
|
|
20
20
|
/** Where the CLI module was loaded from. */
|
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.10.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-11";
|
|
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
|
|
@@ -43,6 +43,7 @@ export const RUNTIME_FILES = [
|
|
|
43
43
|
"workerd-worker.mjs",
|
|
44
44
|
"display.mjs",
|
|
45
45
|
"comm.mjs",
|
|
46
|
+
"host.mjs",
|
|
46
47
|
"widget.mjs",
|
|
47
48
|
"node-hooks.mjs",
|
|
48
49
|
"deno-compat.mjs",
|
package/dist/transform.d.ts
CHANGED
|
@@ -51,7 +51,16 @@ export declare function isTypeStripperName(value: unknown): value is TypeStrippe
|
|
|
51
51
|
* imported (#238).
|
|
52
52
|
*/
|
|
53
53
|
export declare function loadTypeStripper(name: TypeStripperName): Promise<TypeStripper>;
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Rewrites module-only syntax in plain JavaScript into script syntax.
|
|
56
|
+
*
|
|
57
|
+
* Top-level `const` becomes `let`, as do import bindings (#444). Under
|
|
58
|
+
* replMode a later evaluation may redeclare a top-level name only with the
|
|
59
|
+
* same kind of declaration: `const a` then `let a`, or `const a` then
|
|
60
|
+
* `import { a }`, fails with "already been declared" until a restart. With
|
|
61
|
+
* one kind, cells can be re-run and edited freely; the cost is that
|
|
62
|
+
* assigning to a top-level `const` from another cell doesn't throw.
|
|
63
|
+
*/
|
|
55
64
|
export declare function rewriteModuleSyntax(js: string): string;
|
|
56
65
|
export declare function transformCell(code: string, options?: CellTransformOptions): string;
|
|
57
66
|
/**
|
package/dist/transform.js
CHANGED
|
@@ -113,7 +113,9 @@ function rewriteImport(code, node, index) {
|
|
|
113
113
|
return `${local} = ${ns}`;
|
|
114
114
|
return `${local} = ${ns}[${JSON.stringify(importedName(spec))}]`;
|
|
115
115
|
});
|
|
116
|
-
|
|
116
|
+
// `let`, like top-level `const` (see toLet): replMode lets a later
|
|
117
|
+
// cell redeclare a name only with the same kind of declaration.
|
|
118
|
+
return `var ${ns} = ${load}; let ${bindings.join(", ")};`;
|
|
117
119
|
}
|
|
118
120
|
function parseCell(js) {
|
|
119
121
|
try {
|
|
@@ -128,10 +130,26 @@ function parseCell(js) {
|
|
|
128
130
|
throw new CellSyntaxError(err instanceof Error ? err.message : String(err));
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Rewrites module-only syntax in plain JavaScript into script syntax.
|
|
135
|
+
*
|
|
136
|
+
* Top-level `const` becomes `let`, as do import bindings (#444). Under
|
|
137
|
+
* replMode a later evaluation may redeclare a top-level name only with the
|
|
138
|
+
* same kind of declaration: `const a` then `let a`, or `const a` then
|
|
139
|
+
* `import { a }`, fails with "already been declared" until a restart. With
|
|
140
|
+
* one kind, cells can be re-run and edited freely; the cost is that
|
|
141
|
+
* assigning to a top-level `const` from another cell doesn't throw.
|
|
142
|
+
*/
|
|
132
143
|
export function rewriteModuleSyntax(js) {
|
|
133
144
|
const program = parseCell(js);
|
|
134
145
|
const edits = [];
|
|
146
|
+
for (const statement of program.body) {
|
|
147
|
+
const decl = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
148
|
+
if (decl?.type === "VariableDeclaration" && decl.kind === "const") {
|
|
149
|
+
// Padded to `const`'s length, so columns stay where the debugger expects them.
|
|
150
|
+
edits.push({ start: decl.start, end: decl.start + "const".length, text: "let " });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
135
153
|
let importIndex = 0;
|
|
136
154
|
full(program, (node) => {
|
|
137
155
|
switch (node.type) {
|
package/dist/workerd-target.js
CHANGED
|
@@ -35,7 +35,7 @@ import { connectUnixWebSocket } from "./ws-client.js";
|
|
|
35
35
|
import { findWranglerConfig, startMiniflare } from "./workers-project.js";
|
|
36
36
|
import { ensureInstalledAsync, findLockFile, jsrToNpm, parsePackageSpecifier } from "../runtime/node-hooks.mjs";
|
|
37
37
|
/** The driver Worker's modules; the first is its main module. */
|
|
38
|
-
export const WORKER_MODULES = ["workerd-worker.mjs", "display.mjs", "comm.mjs", "widget.mjs", "evaluator.mjs", "introspect.mjs"];
|
|
38
|
+
export const WORKER_MODULES = ["workerd-worker.mjs", "display.mjs", "comm.mjs", "host.mjs", "widget.mjs", "evaluator.mjs", "introspect.mjs"];
|
|
39
39
|
/** See BunTarget: how long an interrupted cell has to show it isn't busy. */
|
|
40
40
|
export const INTERRUPT_GRACE_MS = 500;
|
|
41
41
|
export function restartNote(runtime) {
|
|
@@ -71,6 +71,7 @@ const driver :Workerd.Worker = (
|
|
|
71
71
|
(name = "UNSAFE", unsafeEval = void),
|
|
72
72
|
(name = "TOKEN", fromEnvironment = "CDPK_TOKEN"),
|
|
73
73
|
(name = "META", fromEnvironment = "CDPK_META"),
|
|
74
|
+
(name = "CDPK_PARAMS", fromEnvironment = "CDPK_PARAMS"),
|
|
74
75
|
],
|
|
75
76
|
);
|
|
76
77
|
`;
|
|
@@ -332,7 +333,10 @@ export class RawWorkerd {
|
|
|
332
333
|
copyFileSync(join(source, name), join(dir, name));
|
|
333
334
|
writeFileSync(join(dir, "config.capnp"), workerdConfig({ socket, compatibilityDate }));
|
|
334
335
|
const token = randomBytes(32).toString("hex");
|
|
335
|
-
const env = { ...process.env, CDPK_TOKEN: token, CDPK_META: JSON.stringify(cellMeta(cwd))
|
|
336
|
+
const env = { ...process.env, CDPK_TOKEN: token, CDPK_META: JSON.stringify(cellMeta(cwd)),
|
|
337
|
+
// jupyter.params (#456); always set, so the binding exists.
|
|
338
|
+
CDPK_PARAMS: process.env.GANYMEDE_PARAMS ?? "",
|
|
339
|
+
};
|
|
336
340
|
// --experimental: the UnsafeEval binding.
|
|
337
341
|
const child = spawn(workerd, ["serve", "config.capnp", "--experimental"], { cwd: dir, stdio: ["ignore", "pipe", "pipe"], env });
|
|
338
342
|
child.once("exit", () => rmSync(dir, { recursive: true, force: true }));
|
package/dist/workers-project.js
CHANGED
|
@@ -185,7 +185,7 @@ export async function startMiniflare(configPath, cwd, startTimeoutMs) {
|
|
|
185
185
|
unsafeEvalBinding: "UNSAFE",
|
|
186
186
|
compatibilityDate: date,
|
|
187
187
|
compatibilityFlags: compatibilityFlagsFor(workerOptions.compatibilityFlags ?? []),
|
|
188
|
-
bindings: { ...workerOptions.bindings, TOKEN: token, META: JSON.stringify(cellMeta(cwd)) },
|
|
188
|
+
bindings: { ...workerOptions.bindings, TOKEN: token, META: JSON.stringify(cellMeta(cwd)), CDPK_PARAMS: process.env.GANYMEDE_PARAMS ?? "" },
|
|
189
189
|
// Miniflare 5 renamed Miniflare 4's defaultPersistRoot.
|
|
190
190
|
...(v5 ? { resourcePersistencePath: state } : { defaultPersistRoot: state }),
|
|
191
191
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tlockney/cdp-kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Deno's Jupyter kernel, extracted to run notebooks on Node, Deno, Bun, workerd and celld",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Lockney <thomas@lockney.net>",
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"acorn": "^8.15.0",
|
|
79
79
|
"acorn-walk": "^8.3.4",
|
|
80
|
-
"esbuild": "^0.
|
|
80
|
+
"esbuild": "^0.28.2",
|
|
81
81
|
"typescript": "^6.0.3"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@types/node": "^
|
|
84
|
+
"@types/node": "^26.6.6",
|
|
85
85
|
"medium-zoom": "^1.1.0",
|
|
86
86
|
"vitepress": "^1.6.4",
|
|
87
87
|
"workerd": "1.20261015.1",
|
package/runtime/bootstrap.mjs
CHANGED
|
@@ -83,6 +83,8 @@ function send(payload) {
|
|
|
83
83
|
|
|
84
84
|
const jupyter = createJupyter({
|
|
85
85
|
inspect: (value) => inspect(value),
|
|
86
|
+
// jupyter.params (#456): the kernel's environment reaches the target.
|
|
87
|
+
params: () => (isDeno ? Deno.env.get("GANYMEDE_PARAMS") : process.env.GANYMEDE_PARAMS),
|
|
86
88
|
base64,
|
|
87
89
|
readFile,
|
|
88
90
|
send(msgType, content, metadata, buffers) {
|
|
@@ -65,7 +65,10 @@ const jupyter = createJupyter({
|
|
|
65
65
|
// A synchronous prompt would need SharedArrayBuffer and Atomics.wait, so
|
|
66
66
|
// a cross-origin isolated page; until then, as with no stdin.
|
|
67
67
|
input: () => null,
|
|
68
|
+
// jupyter.params (#456): the target's `params` option, sent with info.
|
|
69
|
+
params: () => paramsText,
|
|
68
70
|
});
|
|
71
|
+
let paramsText;
|
|
69
72
|
|
|
70
73
|
// --- Modules ----------------------------------------------------------------
|
|
71
74
|
let cdn = DEFAULT_CDN;
|
|
@@ -150,7 +153,8 @@ async function preload(urls, base) {
|
|
|
150
153
|
// --- The shim cells and the evaluator use -----------------------------------
|
|
151
154
|
let handle = null;
|
|
152
155
|
|
|
153
|
-
function install({ base, cdn: cdnBase }) {
|
|
156
|
+
function install({ base, cdn: cdnBase, params }) {
|
|
157
|
+
if (typeof params === "string") paramsText = params;
|
|
154
158
|
if (typeof cdnBase === "string") cdn = cdnBase.endsWith("/") ? cdnBase : cdnBase + "/";
|
|
155
159
|
const url = new URL("$cdpk$repl.mjs", base).href;
|
|
156
160
|
globalThis.__cdpk = {
|
package/runtime/comm.d.mts
CHANGED
|
@@ -1,30 +1,116 @@
|
|
|
1
1
|
/** A comm message as a handler receives it. */
|
|
2
2
|
export interface CommMessage {
|
|
3
|
+
/** The message's JSON data. */
|
|
3
4
|
data: { [key: string]: unknown };
|
|
5
|
+
/** The message's metadata. */
|
|
4
6
|
metadata: { [key: string]: unknown };
|
|
7
|
+
/** Binary buffers sent with the message. */
|
|
5
8
|
buffers: Uint8Array[];
|
|
6
9
|
}
|
|
7
10
|
|
|
11
|
+
/** Options for sending, opening or closing a comm. */
|
|
8
12
|
export interface CommSendOptions {
|
|
13
|
+
/** Metadata for the message. Default `{}`. */
|
|
9
14
|
metadata?: { [key: string]: unknown };
|
|
15
|
+
/** Binary buffers sent with the message. Default `[]`. */
|
|
10
16
|
buffers?: Uint8Array[];
|
|
11
17
|
}
|
|
12
18
|
|
|
13
|
-
/** One end of a Jupyter comm channel. */
|
|
19
|
+
/** One end of a Jupyter comm channel, from `jupyter.comm.open` or a target handler. */
|
|
14
20
|
export interface Comm {
|
|
21
|
+
/** The comm id, shared with the frontend's end. */
|
|
15
22
|
readonly id: string;
|
|
23
|
+
/** The target the comm was opened on. */
|
|
16
24
|
readonly targetName: string;
|
|
25
|
+
/** Whether either end has closed the comm. */
|
|
17
26
|
readonly closed: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Send a message to the frontend's end. Does nothing once closed.
|
|
29
|
+
*
|
|
30
|
+
* @param data The message's JSON data. Default `{}`.
|
|
31
|
+
* @param options Metadata and binary buffers.
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const comm = jupyter.comm.open("my.target");
|
|
35
|
+
* comm.send({ ping: true }, { buffers: [new Uint8Array([1, 2, 3])] });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
18
38
|
send(data?: { [key: string]: unknown }, options?: CommSendOptions): void;
|
|
39
|
+
/**
|
|
40
|
+
* Close the comm and tell the frontend. Its `onClose` handlers start
|
|
41
|
+
* right away, without being awaited. Does nothing once closed.
|
|
42
|
+
*
|
|
43
|
+
* @param data Data sent with the close message. Default `{}`.
|
|
44
|
+
* @param options Metadata and binary buffers.
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const comm = jupyter.comm.open("my.target");
|
|
48
|
+
* comm.close({ reason: "done" });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
19
51
|
close(data?: { [key: string]: unknown }, options?: CommSendOptions): void;
|
|
52
|
+
/**
|
|
53
|
+
* Handle messages from the frontend's end. A handler runs like a cell:
|
|
54
|
+
* its output goes to the notebook, and an exception shows on stderr.
|
|
55
|
+
*
|
|
56
|
+
* @param handler Called with each message; may be async.
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const comm = jupyter.comm.open("my.target");
|
|
60
|
+
* comm.onMsg(({ data, buffers }) => console.log(data, buffers.length));
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
20
63
|
onMsg(handler: (msg: CommMessage) => void | Promise<void>): void;
|
|
64
|
+
/**
|
|
65
|
+
* Handle the comm closing, from either end.
|
|
66
|
+
*
|
|
67
|
+
* @param handler Called with the close message; may be async.
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const comm = jupyter.comm.open("my.target");
|
|
71
|
+
* comm.onClose(() => console.log("closed"));
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
21
74
|
onClose(handler: (msg: CommMessage) => void | Promise<void>): void;
|
|
22
75
|
}
|
|
23
76
|
|
|
24
77
|
/** `jupyter.comm`: open comms, and accept comms the frontend opens. */
|
|
25
78
|
export interface CommApi {
|
|
79
|
+
/**
|
|
80
|
+
* Open a comm to a target the frontend registered.
|
|
81
|
+
*
|
|
82
|
+
* @param targetName The frontend's target name.
|
|
83
|
+
* @param data Data sent with the open message. Default `{}`.
|
|
84
|
+
* @param options Metadata and binary buffers.
|
|
85
|
+
* @returns The kernel's end of the comm.
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const comm = jupyter.comm.open("my.target", { hello: 1 });
|
|
89
|
+
* comm.onMsg((m) => console.log(m.data));
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
26
92
|
open(targetName: string, data?: { [key: string]: unknown }, options?: CommSendOptions): Comm;
|
|
93
|
+
/**
|
|
94
|
+
* Accept comms the frontend opens on `targetName`. Registering the same
|
|
95
|
+
* name again replaces the handler.
|
|
96
|
+
*
|
|
97
|
+
* @param targetName The target name the frontend opens comms on.
|
|
98
|
+
* @param handler Called with the new comm and its open message.
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* jupyter.comm.registerTarget("echo", (comm) => comm.onMsg((m) => comm.send(m.data)));
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
27
104
|
registerTarget(targetName: string, handler: (comm: Comm, msg: CommMessage) => void | Promise<void>): void;
|
|
105
|
+
/**
|
|
106
|
+
* Stop accepting new comms on `targetName`. Comms already open stay open.
|
|
107
|
+
*
|
|
108
|
+
* @param targetName The target to remove.
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* jupyter.comm.unregisterTarget("echo");
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
28
114
|
unregisterTarget(targetName: string): void;
|
|
29
115
|
}
|
|
30
116
|
|