@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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** The cdp-kernel project's DSN. It only lets a client send events. */
|
|
2
|
+
export declare const DSN = "https://91c2265acb5b0b0115914a43375be9ba@o4512380800598016.ingest.us.sentry.io/4512380976168960";
|
|
3
|
+
export interface Frame {
|
|
4
|
+
filename: string;
|
|
5
|
+
function?: string;
|
|
6
|
+
lineno?: number;
|
|
7
|
+
colno?: number;
|
|
8
|
+
in_app: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** `text` with home directories as `~`, URLs cut to their path, and at most MAX_MESSAGE characters. */
|
|
11
|
+
export declare function scrubText(text: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* A frame's file as cdp-kernel's own, `app:///src/kernel-core.ts`, or null
|
|
14
|
+
* when it isn't: its path from the package root (`src/`, `dist/` or
|
|
15
|
+
* `runtime/` and below), wherever the package is installed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function packageFile(file: string): string | null;
|
|
18
|
+
/** V8's stack lines as Sentry frames, oldest first. Files outside the package are dropped to `<other>`. */
|
|
19
|
+
export declare function parseStack(stack: string | undefined): Frame[];
|
|
20
|
+
export interface ReportContext {
|
|
21
|
+
/** Where it was caught, e.g. `shell handler`. */
|
|
22
|
+
where: string;
|
|
23
|
+
/** The target runtime: node, deno, bun, workerd or celld. */
|
|
24
|
+
target?: string;
|
|
25
|
+
}
|
|
26
|
+
/** The Sentry event for `err`. Pure but for the clock and the ids. */
|
|
27
|
+
export declare function buildEvent(err: unknown, context: ReportContext): Record<string, unknown>;
|
|
28
|
+
export interface Reporter {
|
|
29
|
+
/** Sends a report; resolves once sent or given up on. Never throws. */
|
|
30
|
+
report(err: unknown, where: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The reporter for this process: a real one when CDP_KERNEL_CRASH_REPORTS
|
|
34
|
+
* is `1`, otherwise one that does nothing. `send` is for tests.
|
|
35
|
+
*/
|
|
36
|
+
export declare function crashReporter(options?: {
|
|
37
|
+
env?: Record<string, string | undefined>;
|
|
38
|
+
target?: string;
|
|
39
|
+
send?: typeof fetch;
|
|
40
|
+
}): Reporter | null;
|
|
41
|
+
/**
|
|
42
|
+
* Reports an exception that would end the process, then ends it as Node
|
|
43
|
+
* would have: the stack on stderr and exit code 1. Only installed when
|
|
44
|
+
* reporting is on, so with it off a crash is exactly as before. An
|
|
45
|
+
* unhandled rejection arrives here too (Node's default `throw` mode).
|
|
46
|
+
*/
|
|
47
|
+
export declare function reportUncaught(reporter: Reporter): void;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Opt-in reports of faults in cdp-kernel's own code (#422,
|
|
2
|
+
// tlockney/ganymede#384), sent to Sentry's cdp-kernel project.
|
|
3
|
+
//
|
|
4
|
+
// Off unless the host sets CDP_KERNEL_CRASH_REPORTS=1, which Ganymede does
|
|
5
|
+
// when its user opted in. While off nothing here runs: no handler, no
|
|
6
|
+
// request. Errors from user cells never come through here: the target
|
|
7
|
+
// reports those to KernelCore as exception details, and KernelCore answers
|
|
8
|
+
// them as cell errors. What does: an exception that escapes a message
|
|
9
|
+
// handler, the target-message handler, or the process.
|
|
10
|
+
//
|
|
11
|
+
// No Sentry SDK: an event is a small JSON envelope, posted with fetch.
|
|
12
|
+
// That keeps the npm and JSR packages free of a dependency and its
|
|
13
|
+
// install-time cost for everyone who never opts in.
|
|
14
|
+
//
|
|
15
|
+
// Sent: the error's type, message (home directories as `~`, URLs cut to
|
|
16
|
+
// their path, 300 characters at most) and stack (frames in cdp-kernel's
|
|
17
|
+
// own files by their path in the package; others by name only), the
|
|
18
|
+
// version, host and target runtime, OS and architecture. Not sent: cell
|
|
19
|
+
// source, outputs, variables, file paths outside the package, env vars,
|
|
20
|
+
// host name.
|
|
21
|
+
import process from "node:process";
|
|
22
|
+
import { arch, platform, release } from "node:os";
|
|
23
|
+
import { hostRuntime, VERSION } from "./package.js";
|
|
24
|
+
/** The cdp-kernel project's DSN. It only lets a client send events. */
|
|
25
|
+
export const DSN = "https://91c2265acb5b0b0115914a43375be9ba@o4512380800598016.ingest.us.sentry.io/4512380976168960";
|
|
26
|
+
/** At most this many reports per process: a fault in a loop sends a few, not thousands. */
|
|
27
|
+
const MAX_REPORTS = 10;
|
|
28
|
+
const MAX_MESSAGE = 300;
|
|
29
|
+
const SEND_TIMEOUT_MS = 5000;
|
|
30
|
+
/** `text` with home directories as `~`, URLs cut to their path, and at most MAX_MESSAGE characters. */
|
|
31
|
+
export function scrubText(text) {
|
|
32
|
+
const out = text
|
|
33
|
+
.replace(/\b(?:https?|wss?|file):\/\/[^\s/'"`)]*([^\s?#'"`)]*)(?:[?#][^\s'"`)]*)?/g, "$1")
|
|
34
|
+
.replace(/(?:\/Users|\/home)\/[^/\s'"`)]+/g, "~")
|
|
35
|
+
.replace(/[A-Za-z]:\\Users\\[^\\\s'"`)]+/g, "~");
|
|
36
|
+
return out.length > MAX_MESSAGE ? `${out.slice(0, MAX_MESSAGE)}…` : out;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A frame's file as cdp-kernel's own, `app:///src/kernel-core.ts`, or null
|
|
40
|
+
* when it isn't: its path from the package root (`src/`, `dist/` or
|
|
41
|
+
* `runtime/` and below), wherever the package is installed.
|
|
42
|
+
*/
|
|
43
|
+
export function packageFile(file) {
|
|
44
|
+
const path = file.replace(/^file:\/\//, "").replaceAll("\\", "/");
|
|
45
|
+
// From npm, from JSR (jsr.io/@tlockney/cdp-kernel/<version>/src/…), or a checkout.
|
|
46
|
+
const match = /(?:^|\/)(?:@tlockney\/cdp-kernel(?:\/[^/]+)?|cdp-kernel)\/((?:src|dist|runtime)\/[^?#]+)$/.exec(path);
|
|
47
|
+
return match ? `app:///${match[1]}` : null;
|
|
48
|
+
}
|
|
49
|
+
const V8_FRAME = /^\s*at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?$/;
|
|
50
|
+
/** V8's stack lines as Sentry frames, oldest first. Files outside the package are dropped to `<other>`. */
|
|
51
|
+
export function parseStack(stack) {
|
|
52
|
+
const frames = [];
|
|
53
|
+
for (const line of (stack ?? "").split("\n")) {
|
|
54
|
+
const m = V8_FRAME.exec(line);
|
|
55
|
+
if (!m)
|
|
56
|
+
continue;
|
|
57
|
+
const [, fn, file, lineno, colno] = m;
|
|
58
|
+
const own = packageFile(file);
|
|
59
|
+
const internal = file.startsWith("node:") || file.startsWith("ext:");
|
|
60
|
+
frames.push({
|
|
61
|
+
filename: own ?? (internal ? file : "<other>"),
|
|
62
|
+
function: fn,
|
|
63
|
+
lineno: own || internal ? Number(lineno) : undefined,
|
|
64
|
+
colno: own || internal ? Number(colno) : undefined,
|
|
65
|
+
in_app: own !== null,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return frames.reverse();
|
|
69
|
+
}
|
|
70
|
+
/** The Sentry event for `err`. Pure but for the clock and the ids. */
|
|
71
|
+
export function buildEvent(err, context) {
|
|
72
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
73
|
+
return {
|
|
74
|
+
event_id: crypto.randomUUID().replaceAll("-", ""),
|
|
75
|
+
timestamp: Date.now() / 1000,
|
|
76
|
+
platform: "node",
|
|
77
|
+
level: "error",
|
|
78
|
+
release: `cdp-kernel@${VERSION}`,
|
|
79
|
+
environment: "production",
|
|
80
|
+
tags: { where: context.where, host: hostRuntime(), ...(context.target ? { target: context.target } : {}) },
|
|
81
|
+
contexts: {
|
|
82
|
+
os: { name: platform(), kernel_version: release() },
|
|
83
|
+
device: { arch: arch() },
|
|
84
|
+
runtime: { name: hostRuntime(), version: process.versions.deno ?? process.versions.node },
|
|
85
|
+
},
|
|
86
|
+
exception: {
|
|
87
|
+
values: [{
|
|
88
|
+
type: error.name,
|
|
89
|
+
value: scrubText(error.message),
|
|
90
|
+
stacktrace: { frames: parseStack(error.stack) },
|
|
91
|
+
mechanism: { type: context.where, handled: context.where !== "uncaught" },
|
|
92
|
+
}],
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function parseDsn(dsn) {
|
|
97
|
+
const url = new URL(dsn);
|
|
98
|
+
const project = url.pathname.replace(/^\//, "");
|
|
99
|
+
return { key: url.username, envelope: `${url.protocol}//${url.host}/api/${project}/envelope/` };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The reporter for this process: a real one when CDP_KERNEL_CRASH_REPORTS
|
|
103
|
+
* is `1`, otherwise one that does nothing. `send` is for tests.
|
|
104
|
+
*/
|
|
105
|
+
export function crashReporter(options = {}) {
|
|
106
|
+
const env = options.env ?? process.env;
|
|
107
|
+
if (env.CDP_KERNEL_CRASH_REPORTS !== "1")
|
|
108
|
+
return null;
|
|
109
|
+
const dsn = parseDsn(env.CDP_KERNEL_SENTRY_DSN || DSN);
|
|
110
|
+
const send = options.send ?? fetch;
|
|
111
|
+
let sent = 0;
|
|
112
|
+
return {
|
|
113
|
+
async report(err, where) {
|
|
114
|
+
if (sent >= MAX_REPORTS)
|
|
115
|
+
return;
|
|
116
|
+
sent++;
|
|
117
|
+
try {
|
|
118
|
+
const event = buildEvent(err, { where, target: options.target });
|
|
119
|
+
const body = [
|
|
120
|
+
JSON.stringify({ event_id: event.event_id, sent_at: new Date().toISOString() }),
|
|
121
|
+
JSON.stringify({ type: "event" }),
|
|
122
|
+
JSON.stringify(event),
|
|
123
|
+
].join("\n");
|
|
124
|
+
await send(dsn.envelope, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"content-type": "application/x-sentry-envelope",
|
|
128
|
+
"x-sentry-auth": `Sentry sentry_version=7, sentry_key=${dsn.key}, sentry_client=cdp-kernel/${VERSION}`,
|
|
129
|
+
},
|
|
130
|
+
body,
|
|
131
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Reporting must never take the kernel down.
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Reports an exception that would end the process, then ends it as Node
|
|
142
|
+
* would have: the stack on stderr and exit code 1. Only installed when
|
|
143
|
+
* reporting is on, so with it off a crash is exactly as before. An
|
|
144
|
+
* unhandled rejection arrives here too (Node's default `throw` mode).
|
|
145
|
+
*/
|
|
146
|
+
export function reportUncaught(reporter) {
|
|
147
|
+
let ending = false;
|
|
148
|
+
process.on("uncaughtException", (err) => {
|
|
149
|
+
if (ending)
|
|
150
|
+
return;
|
|
151
|
+
ending = true;
|
|
152
|
+
console.error(err instanceof Error ? err.stack ?? String(err) : err);
|
|
153
|
+
void reporter.report(err, "uncaught").finally(() => process.exit(1));
|
|
154
|
+
});
|
|
155
|
+
}
|
package/dist/globals.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { Displayable, DisplayOptions, JupyterNamespace, MediaBundle } from
|
|
|
16
16
|
export type { Displayable, DisplayOptions, JupyterNamespace, MediaBundle };
|
|
17
17
|
/** Options for the notebook's `prompt()`. */
|
|
18
18
|
export interface PromptOptions {
|
|
19
|
-
/** Ask the frontend to hide what is typed. */
|
|
19
|
+
/** Ask the frontend to hide what is typed, for secrets. Default `false`. */
|
|
20
20
|
password?: boolean;
|
|
21
21
|
}
|
|
22
22
|
declare global {
|
|
@@ -27,10 +27,41 @@ declare global {
|
|
|
27
27
|
*/
|
|
28
28
|
var jupyter: JupyterNamespace;
|
|
29
29
|
/**
|
|
30
|
-
* Ask
|
|
31
|
-
*
|
|
30
|
+
* Ask for a line of input in the notebook: the frontend shows an input
|
|
31
|
+
* box under the cell, and the cell waits for the answer.
|
|
32
|
+
*
|
|
33
|
+
* @param message The question. Default `"Prompt"`. A non-empty
|
|
34
|
+
* `defaultValue` is shown after it, as `message [default]`.
|
|
35
|
+
* @param defaultValue Returned when the answer is empty. Default `""`.
|
|
36
|
+
* @param options `password: true` hides what is typed.
|
|
37
|
+
* @returns The typed text, `defaultValue` for an empty answer, or `null`
|
|
38
|
+
* when nothing can answer (the frontend allows no input, as in
|
|
39
|
+
* `nbconvert --execute`).
|
|
40
|
+
* @availability On workerd and celld it always returns `null`.
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const name = prompt("What's your name?", "world");
|
|
44
|
+
* `Hello, ${name}!`;
|
|
45
|
+
* ```
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const apiKey = prompt("API key", "", { password: true });
|
|
49
|
+
* ```
|
|
50
|
+
* @see jupyter-api.md#asking-for-input-prompt-and-confirm
|
|
32
51
|
*/
|
|
33
52
|
function prompt(message?: string, defaultValue?: string, options?: PromptOptions): string | null;
|
|
34
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Ask a yes/no question in the notebook. `[y/N]` is added to the message.
|
|
55
|
+
*
|
|
56
|
+
* @param message The question. Default `"Confirm"`.
|
|
57
|
+
* @returns `true` only for `y` or `Y`; `false` otherwise, and when
|
|
58
|
+
* nothing can answer.
|
|
59
|
+
* @availability On workerd and celld it always returns `false`.
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* if (confirm("Delete the cache?")) console.log("deleting");
|
|
63
|
+
* ```
|
|
64
|
+
* @see jupyter-api.md#asking-for-input-prompt-and-confirm
|
|
65
|
+
*/
|
|
35
66
|
function confirm(message?: string): boolean;
|
|
36
67
|
}
|
package/dist/kernel-core.d.ts
CHANGED
|
@@ -52,11 +52,18 @@ export interface KernelHost {
|
|
|
52
52
|
readonly capabilities?: JsonObject;
|
|
53
53
|
/** JupyterLab's visual debugger, for targets driven over CDP (#18). */
|
|
54
54
|
createDebugger?(publish: (event: JsonObject) => void, transformOptions: () => CellTransformOptions): Debugger;
|
|
55
|
+
/** usage_reply's content: the host's and the running target's (null while none runs) memory and CPU (#409). */
|
|
56
|
+
usage?(target: KernelTarget | null): Promise<JsonObject>;
|
|
55
57
|
/** A file's source, for the lines around a traceback frame (#322). */
|
|
56
58
|
readSource?(file: string): string | undefined;
|
|
57
59
|
/** Path or URL prefixes of the kernel's own runtime files, whose frames a traceback marks as library code. */
|
|
58
60
|
readonly kernelFiles?: readonly string[];
|
|
59
61
|
log(text: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* A fault in the kernel's own code, caught where it would otherwise only
|
|
64
|
+
* be logged: for opt-in crash reports (#422). Never a cell's error.
|
|
65
|
+
*/
|
|
66
|
+
fault?(err: unknown, where: string): void;
|
|
60
67
|
/** After shutdown_reply has gone out. */
|
|
61
68
|
exit(): void;
|
|
62
69
|
}
|