@tlockney/cdp-kernel 0.6.0 → 0.7.1

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.
Files changed (53) hide show
  1. package/README.md +5 -3
  2. package/dist/browser/channels.d.ts +27 -0
  3. package/dist/browser/channels.js +90 -0
  4. package/dist/browser/kernel.js +29800 -0
  5. package/dist/browser/kernel.js.map +7 -0
  6. package/dist/browser/mod.d.ts +25 -0
  7. package/dist/browser/mod.js +41 -0
  8. package/dist/browser/target.d.ts +94 -0
  9. package/dist/browser/target.js +264 -0
  10. package/dist/browser/worker.js +1071 -0
  11. package/dist/browser/worker.js.map +7 -0
  12. package/dist/bun-target.d.ts +2 -0
  13. package/dist/bun-target.js +15 -2
  14. package/dist/cdp.d.ts +11 -0
  15. package/dist/cdp.js +34 -7
  16. package/dist/celld-target.js +3 -2
  17. package/dist/debugger.d.ts +2 -0
  18. package/dist/debugger.js +63 -11
  19. package/dist/kernel-core.d.ts +78 -0
  20. package/dist/kernel-core.js +824 -0
  21. package/dist/kernel.d.ts +6 -1
  22. package/dist/kernel.js +104 -723
  23. package/dist/main.js +11 -4
  24. package/dist/message.d.ts +34 -0
  25. package/dist/message.js +14 -0
  26. package/dist/package.d.ts +2 -2
  27. package/dist/package.js +2 -2
  28. package/dist/target-api.d.ts +92 -0
  29. package/dist/target-api.js +48 -0
  30. package/dist/target.d.ts +5 -76
  31. package/dist/target.js +62 -33
  32. package/dist/traceback.d.ts +75 -0
  33. package/dist/traceback.js +200 -0
  34. package/dist/transform.d.ts +1 -0
  35. package/dist/transform.js +8 -1
  36. package/dist/wire.d.ts +4 -24
  37. package/dist/wire.js +3 -12
  38. package/dist/workerd-target.d.ts +6 -1
  39. package/dist/workerd-target.js +56 -12
  40. package/dist/workers-project.d.ts +1 -1
  41. package/dist/workers-project.js +4 -3
  42. package/dist/zmq-channels.d.ts +14 -0
  43. package/dist/zmq-channels.js +59 -0
  44. package/package.json +9 -2
  45. package/runtime/bootstrap.mjs +2 -0
  46. package/runtime/browser-worker.mjs +202 -0
  47. package/runtime/cdn.d.mts +2 -0
  48. package/runtime/cdn.mjs +17 -0
  49. package/runtime/display.mjs +202 -1
  50. package/runtime/evaluator.mjs +2 -0
  51. package/runtime/format.d.mts +2 -0
  52. package/runtime/format.mjs +125 -0
  53. package/runtime/workerd-worker.mjs +2 -0
package/dist/main.js CHANGED
@@ -31,7 +31,17 @@ function kernelsDir() {
31
31
  return join(process.env.APPDATA ?? homedir(), "jupyter", "kernels");
32
32
  return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "jupyter", "kernels");
33
33
  }
34
+ // Jupyter's kernel names; anything else could point outside the kernels
35
+ // directory.
36
+ const KERNEL_NAME = /^[A-Za-z0-9._-]+$/;
37
+ function isKernelName(name) {
38
+ return KERNEL_NAME.test(name) && name !== "." && name !== "..";
39
+ }
34
40
  function install(runtime, denoCompat, typeStrip, languageService, name, display) {
41
+ if (!isKernelName(name)) {
42
+ console.error(`install: invalid kernelspec name ${JSON.stringify(name)}; use letters, digits, '.', '_' and '-'`);
43
+ process.exit(2);
44
+ }
35
45
  const dir = join(kernelsDir(), name);
36
46
  mkdirSync(dir, { recursive: true });
37
47
  const spec = {
@@ -97,9 +107,6 @@ function list() {
97
107
  console.log(`${s.name.padEnd(width)} ${s.runtime.padEnd(7)} ${(s.version ?? "-").padEnd(7)} ${s.dir}`);
98
108
  }
99
109
  }
100
- // Jupyter's kernel names; anything else could point outside the kernels
101
- // directory.
102
- const KERNEL_NAME = /^[A-Za-z0-9._-]+$/;
103
110
  function uninstall(names, all) {
104
111
  if (all)
105
112
  names = installedSpecs().map((s) => s.name);
@@ -110,7 +117,7 @@ function uninstall(names, all) {
110
117
  }
111
118
  let failed = false;
112
119
  for (const name of names) {
113
- const valid = KERNEL_NAME.test(name) && name !== "." && name !== "..";
120
+ const valid = isKernelName(name);
114
121
  const spec = valid ? readOwnSpec(name) : undefined;
115
122
  if (!spec) {
116
123
  const exists = valid && existsSync(join(kernelsDir(), name));
@@ -0,0 +1,34 @@
1
+ export declare const PROTOCOL_VERSION = "5.3";
2
+ export type JsonObject = {
3
+ [key: string]: unknown;
4
+ };
5
+ export interface Header extends JsonObject {
6
+ msg_id: string;
7
+ session: string;
8
+ date: string;
9
+ username: string;
10
+ msg_type: string;
11
+ version: string;
12
+ }
13
+ /** A header as received: the fields the kernel relies on, plus whatever else came. */
14
+ export interface ReceivedHeader extends JsonObject {
15
+ msg_id: string;
16
+ msg_type: string;
17
+ }
18
+ /** A message from a frontend, decoded (and on ZeroMQ, verified). */
19
+ export interface KernelMessage {
20
+ header: ReceivedHeader;
21
+ parentHeader: JsonObject;
22
+ metadata: JsonObject;
23
+ content: JsonObject;
24
+ buffers: Uint8Array[];
25
+ }
26
+ /** A message the kernel sends. */
27
+ export interface OutgoingMessage {
28
+ header: Header;
29
+ parentHeader: JsonObject;
30
+ metadata: JsonObject;
31
+ content: JsonObject;
32
+ buffers: Uint8Array[];
33
+ }
34
+ export declare function makeHeader(session: string, msgType: string): Header;
@@ -0,0 +1,14 @@
1
+ // Jupyter message shapes and headers, without the wire format: what the
2
+ // kernel's message handling (src/kernel-core.ts) builds and reads. The
3
+ // framing and HMAC signing that ZeroMQ peers need are in src/wire.ts.
4
+ export const PROTOCOL_VERSION = "5.3";
5
+ export function makeHeader(session, msgType) {
6
+ return {
7
+ msg_id: crypto.randomUUID(),
8
+ session,
9
+ date: new Date().toISOString(),
10
+ username: "kernel",
11
+ msg_type: msgType,
12
+ version: PROTOCOL_VERSION,
13
+ };
14
+ }
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.6.0";
2
+ export declare const VERSION = "0.7.1";
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-10-18";
8
+ export declare const RELEASE_DATE = "2026-10-19";
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.6.0";
17
+ export const VERSION = "0.7.1";
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-10-18";
23
+ export const RELEASE_DATE = "2026-10-19";
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
@@ -0,0 +1,92 @@
1
+ import type { CdpParams, EvaluateResult, RemoteObject } from "./cdp.ts";
2
+ import type { DebugChannel } from "./debugger.ts";
3
+ import type { CellTransformOptions } from "./transform.ts";
4
+ /** Targets driven over CDP. */
5
+ export type CdpRuntime = "node" | "deno";
6
+ /** Bun, workerd and celld have their own backends: src/bun-target.ts, src/workerd-target.ts, src/celld-target.ts. */
7
+ export type RuntimeName = CdpRuntime | "bun" | "workerd" | "celld";
8
+ /** Every target: the runtimes a kernelspec names, and the browser's Worker (src/browser/). */
9
+ export type TargetRuntime = RuntimeName | "browser";
10
+ /** What the kernel needs from a target runtime. */
11
+ export interface KernelTarget {
12
+ readonly runtime: TargetRuntime;
13
+ readonly versions: {
14
+ runtime: string;
15
+ engine: string;
16
+ };
17
+ /** Shown once, in the first cell: e.g. which project's bindings loaded. */
18
+ readonly notice?: string;
19
+ /** Turns a cell into the script evaluate() takes. */
20
+ transform(code: string, options: CellTransformOptions): string;
21
+ /** Runs a transformed cell. Rejects with Interrupted after interrupt(). */
22
+ evaluate(script: string): Promise<EvaluateResult>;
23
+ interrupt(): Promise<void>;
24
+ /** Resolves once all output and messages sent so far have been delivered. */
25
+ flush(): Promise<void>;
26
+ broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
27
+ /** A value's MIME bundle, as a cell's result would show it: for user_expressions (#340). */
28
+ format(value: RemoteObject): Promise<CdpParams>;
29
+ dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
30
+ handled: boolean;
31
+ } | {
32
+ error: {
33
+ name: string;
34
+ message: string;
35
+ stack: string;
36
+ };
37
+ }>;
38
+ describeError(exception: RemoteObject): Promise<{
39
+ name: string;
40
+ message: string;
41
+ stack: string;
42
+ }>;
43
+ propertyNames(expression: string): Promise<string[]>;
44
+ lexicalNames(): Promise<string[]>;
45
+ /** inspect_request (#10): text describing a dotted path's value, without running anything. */
46
+ inspect(path: string, detail: number): Promise<string | null>;
47
+ /** The Debugger domain, on targets driven over CDP (#18). */
48
+ readonly debug?: DebugChannel;
49
+ /** Answers the target's pending prompt; `interrupted` makes it throw instead (#121). */
50
+ replyInput(value: string | null, interrupted?: boolean): void;
51
+ /** What the host waits on, for a kernel that has stopped answering (#237). */
52
+ diagnose?(): Promise<CdpParams>;
53
+ /** Lets go of the last cell's result and error, once they are shown (#286). */
54
+ releaseResults?(): Promise<void>;
55
+ kill(): void;
56
+ }
57
+ /** Messages the bootstrap sends: message records on stdout, or Bun's channel. */
58
+ export type TargetMessage = {
59
+ kind: "iopub";
60
+ msg_type: string;
61
+ content: CdpParams;
62
+ metadata: CdpParams;
63
+ buffers: string[];
64
+ barrier: number;
65
+ } | {
66
+ kind: "input_request";
67
+ prompt: string;
68
+ password: boolean;
69
+ barrier: number;
70
+ };
71
+ /** A MIME bundle the target sent as JSON text; empty if it isn't one. */
72
+ export declare function parseBundle(text: unknown): CdpParams;
73
+ /** How long a target may take from launch to a usable session. */
74
+ export declare const START_TIMEOUT_MS = 60000;
75
+ /**
76
+ * `start`, or a rejection once `ms` pass without it settling. The caller
77
+ * stops whatever was starting: a runtime that never becomes ready would
78
+ * otherwise wedge the first cell (#52, #336).
79
+ */
80
+ export declare function withStartTimeout<T>(start: Promise<T>, what: string, ms?: number): Promise<T>;
81
+ /** The parsed value, or undefined for text that isn't JSON. */
82
+ export declare function parseJson(text: string): unknown;
83
+ export declare function isTargetMessage(value: unknown): value is TargetMessage;
84
+ export interface TargetEvents {
85
+ stream(name: "stdout" | "stderr", text: string): void;
86
+ message(msg: TargetMessage): void;
87
+ /** `note` says why, when the target was stopped on purpose. */
88
+ exit(code: number | null, note?: string): void;
89
+ }
90
+ export declare class Interrupted extends Error {
91
+ name: string;
92
+ }
@@ -0,0 +1,48 @@
1
+ // What the kernel needs from a target runtime, and what a target sends
2
+ // back: the part of src/target.ts with no Node imports, so the kernel's
3
+ // message handling (src/kernel-core.ts) also loads in a browser.
4
+ /** A MIME bundle the target sent as JSON text; empty if it isn't one. */
5
+ export function parseBundle(text) {
6
+ const bundle = typeof text === "string" ? parseJson(text) : undefined;
7
+ return typeof bundle === "object" && bundle !== null && !Array.isArray(bundle) ? bundle : {};
8
+ }
9
+ /** How long a target may take from launch to a usable session. */
10
+ export const START_TIMEOUT_MS = 60_000;
11
+ /**
12
+ * `start`, or a rejection once `ms` pass without it settling. The caller
13
+ * stops whatever was starting: a runtime that never becomes ready would
14
+ * otherwise wedge the first cell (#52, #336).
15
+ */
16
+ export async function withStartTimeout(start, what, ms = START_TIMEOUT_MS) {
17
+ // Its own rejection, once the deadline has passed, is the caller's to ignore.
18
+ start.catch(() => { });
19
+ let timer;
20
+ const stalled = new Promise((_, reject) => {
21
+ timer = setTimeout(() => reject(new Error(`${what} did not start within ${ms / 1000}s`)), ms);
22
+ });
23
+ try {
24
+ return await Promise.race([start, stalled]);
25
+ }
26
+ finally {
27
+ clearTimeout(timer);
28
+ }
29
+ }
30
+ /** The parsed value, or undefined for text that isn't JSON. */
31
+ export function parseJson(text) {
32
+ try {
33
+ return JSON.parse(text);
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ }
39
+ export function isTargetMessage(value) {
40
+ if (typeof value !== "object" || value === null)
41
+ return false;
42
+ const kind = value.kind;
43
+ return (kind === "iopub" || kind === "input_request") &&
44
+ typeof value.barrier === "number";
45
+ }
46
+ export class Interrupted extends Error {
47
+ name = "Interrupted";
48
+ }
package/dist/target.d.ts CHANGED
@@ -1,55 +1,9 @@
1
1
  import { type CdpParams, type EvaluateResult, type RemoteObject } from "./cdp.ts";
2
- import { type CellTransformOptions, transformCell } from "./transform.ts";
2
+ import { transformCell } from "./transform.ts";
3
3
  import type { DebugChannel } from "./debugger.ts";
4
- /** Targets driven over CDP. */
5
- export type CdpRuntime = "node" | "deno";
6
- /** Bun, workerd and celld have their own backends: src/bun-target.ts, src/workerd-target.ts, src/celld-target.ts. */
7
- export type RuntimeName = CdpRuntime | "bun" | "workerd" | "celld";
8
- /** What the kernel needs from a target runtime. */
9
- export interface KernelTarget {
10
- readonly runtime: RuntimeName;
11
- readonly versions: {
12
- runtime: string;
13
- engine: string;
14
- };
15
- /** Shown once, in the first cell: e.g. which project's bindings loaded. */
16
- readonly notice?: string;
17
- /** Turns a cell into the script evaluate() takes. */
18
- transform(code: string, options: CellTransformOptions): string;
19
- /** Runs a transformed cell. Rejects with Interrupted after interrupt(). */
20
- evaluate(script: string): Promise<EvaluateResult>;
21
- interrupt(): Promise<void>;
22
- /** Resolves once all output and messages sent so far have been delivered. */
23
- flush(): Promise<void>;
24
- broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
25
- dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
26
- handled: boolean;
27
- } | {
28
- error: {
29
- name: string;
30
- message: string;
31
- stack: string;
32
- };
33
- }>;
34
- describeError(exception: RemoteObject): Promise<{
35
- name: string;
36
- message: string;
37
- stack: string;
38
- }>;
39
- propertyNames(expression: string): Promise<string[]>;
40
- lexicalNames(): Promise<string[]>;
41
- /** inspect_request (#10): text describing a dotted path's value, without running anything. */
42
- inspect(path: string, detail: number): Promise<string | null>;
43
- /** The Debugger domain, on targets driven over CDP (#18). */
44
- readonly debug?: DebugChannel;
45
- /** Answers the target's pending prompt; `interrupted` makes it throw instead (#121). */
46
- replyInput(value: string | null, interrupted?: boolean): void;
47
- /** What the host waits on, for a kernel that has stopped answering (#237). */
48
- diagnose?(): Promise<CdpParams>;
49
- /** Lets go of the last cell's result and error, once they are shown (#286). */
50
- releaseResults?(): Promise<void>;
51
- kill(): void;
52
- }
4
+ import { type CdpRuntime, Interrupted, isTargetMessage, type KernelTarget, parseBundle, parseJson, type TargetEvents, type TargetMessage } from "./target-api.ts";
5
+ export { Interrupted, isTargetMessage, parseBundle, parseJson };
6
+ export type { CdpRuntime, KernelTarget, RuntimeName, TargetEvents, TargetMessage } from "./target-api.ts";
53
7
  export interface LaunchOptions {
54
8
  /** Runtime binary; defaults to `deno`, or `node` (this one when hosted by Node). */
55
9
  executable?: string;
@@ -68,23 +22,6 @@ export declare function launchCommand(runtime: CdpRuntime, bootstrap: string, {
68
22
  * install and in Deno's npm cache alike.
69
23
  */
70
24
  export declare function esbuildBinary(resolve?: (specifier: string) => string, platform?: string, arch?: string): string | null;
71
- /** Messages the bootstrap sends: message records on stdout, or Bun's channel. */
72
- export type TargetMessage = {
73
- kind: "iopub";
74
- msg_type: string;
75
- content: CdpParams;
76
- metadata: CdpParams;
77
- buffers: string[];
78
- barrier: number;
79
- } | {
80
- kind: "input_request";
81
- prompt: string;
82
- password: boolean;
83
- barrier: number;
84
- };
85
- /** The parsed value, or undefined for text that isn't JSON. */
86
- export declare function parseJson(text: string): unknown;
87
- export declare function isTargetMessage(value: unknown): value is TargetMessage;
88
25
  /**
89
26
  * Splits one output pipe into text and barrier markers, and releases them
90
27
  * in order. A gated marker (one that announces a message) pauses
@@ -134,15 +71,6 @@ export declare class Gates {
134
71
  open(n: number): void;
135
72
  openAll(): void;
136
73
  }
137
- export interface TargetEvents {
138
- stream(name: "stdout" | "stderr", text: string): void;
139
- message(msg: TargetMessage): void;
140
- /** `note` says why, when the target was stopped on purpose. */
141
- exit(code: number | null, note?: string): void;
142
- }
143
- export declare class Interrupted extends Error {
144
- name: string;
145
- }
146
74
  export declare class Target implements KernelTarget {
147
75
  #private;
148
76
  readonly runtime: CdpRuntime;
@@ -188,6 +116,7 @@ export declare class Target implements KernelTarget {
188
116
  callFunctionOn(functionDeclaration: string, args: CdpParams[]): Promise<CdpParams>;
189
117
  /** The broadcastResult step of Deno's execute flow (jupyter/mod.rs). */
190
118
  broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
119
+ format(value: RemoteObject): Promise<CdpParams>;
191
120
  /**
192
121
  * Hands a frontend comm message to the target's comms. Resolves to
193
122
  * whether a comm or registered target took it, or to the error a handler
package/dist/target.js CHANGED
@@ -11,6 +11,8 @@ import { asEvaluateResult, CdpAbandoned, CdpError, CdpSession } from "./cdp.js";
11
11
  import { SHIM, transformCell } from "./transform.js";
12
12
  import { hostRuntime, isInsideNodeModules, runtimeDir } from "./package.js";
13
13
  import { killOnExit } from "./children.js";
14
+ import { Interrupted, isTargetMessage, parseBundle, parseJson, withStartTimeout, } from "./target-api.js";
15
+ export { Interrupted, isTargetMessage, parseBundle, parseJson };
14
16
  /**
15
17
  * The inspector keeps every object it returns a remote reference to alive
16
18
  * until it is released, so a cell's result would never be collected (#286).
@@ -19,8 +21,6 @@ import { killOnExit } from "./children.js";
19
21
  */
20
22
  const CELL_GROUP = "cdpk-cell";
21
23
  const LOOKUP_GROUP = "cdpk-lookup";
22
- /** How long a target may take from spawn to a usable session. */
23
- const START_TIMEOUT_MS = 60_000;
24
24
  const MARKER_RE = /\x1b\[cdpk:(\d+)(m?)\]\x1b/;
25
25
  const MAX_MARKER_LEN = 24;
26
26
  // Lines the runtimes print to stderr on their own: inspector notices, and
@@ -84,22 +84,6 @@ export function esbuildBinary(resolve = import.meta.resolve, platform = process.
84
84
  return null;
85
85
  }
86
86
  }
87
- /** The parsed value, or undefined for text that isn't JSON. */
88
- export function parseJson(text) {
89
- try {
90
- return JSON.parse(text);
91
- }
92
- catch {
93
- return undefined;
94
- }
95
- }
96
- export function isTargetMessage(value) {
97
- if (typeof value !== "object" || value === null)
98
- return false;
99
- const kind = value.kind;
100
- return (kind === "iopub" || kind === "input_request") &&
101
- typeof value.barrier === "number";
102
- }
103
87
  const MARKER_PREFIX = "\x1b[cdpk:";
104
88
  // A message record: the message's JSON, which never holds a raw ESC, between
105
89
  // "\x1b[cdpk:<" and "\x1b]" (#271).
@@ -327,9 +311,6 @@ export class Gates {
327
311
  this.#map.clear();
328
312
  }
329
313
  }
330
- export class Interrupted extends Error {
331
- name = "Interrupted";
332
- }
333
314
  /** A catch handler: a call interrupt() abandoned becomes Interrupted. */
334
315
  function interrupted(what) {
335
316
  return (err) => {
@@ -351,6 +332,9 @@ export class Target {
351
332
  #undelivered = 0;
352
333
  #interrupted = false;
353
334
  #paused = false;
335
+ /** What the debugger asked to pause on (#307), and how many kernel calls hold it off. */
336
+ #pauseOnExceptions = "none";
337
+ #unpausable = 0;
354
338
  /** Set once the main context is gone: the runtime is exiting (#130). */
355
339
  #exiting = false;
356
340
  #exited;
@@ -400,13 +384,8 @@ export class Target {
400
384
  // A start that fails or stalls must not leave the runtime running: its
401
385
  // pipes and inspector sockets would keep this process alive (#52).
402
386
  const sessions = [];
403
- const timeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;
404
- let timer;
405
- const stalled = new Promise((_, reject) => {
406
- timer = setTimeout(() => reject(new Error(`${cmd} did not start within ${timeoutMs / 1000}s`)), timeoutMs);
407
- });
408
387
  try {
409
- return await Promise.race([Target.#start(runtime, events, cmd, child, sessions), stalled]);
388
+ return await withStartTimeout(Target.#start(runtime, events, cmd, child, sessions), cmd, options.startTimeoutMs);
410
389
  }
411
390
  catch (error) {
412
391
  for (const session of sessions)
@@ -414,9 +393,6 @@ export class Target {
414
393
  child.kill();
415
394
  throw error;
416
395
  }
417
- finally {
418
- clearTimeout(timer);
419
- }
420
396
  }
421
397
  static async #start(runtime, events, cmd, child, sessions) {
422
398
  child.stdout.setEncoding("utf8");
@@ -484,6 +460,15 @@ export class Target {
484
460
  if (params.executionContextId === contextId && target)
485
461
  target.#onExit();
486
462
  });
463
+ // A session that closes on its own (e.g. a frame that isn't JSON, #337)
464
+ // leaves a target nothing can drive: stop it, so the next cell starts
465
+ // a fresh one.
466
+ for (const session of [shell, control]) {
467
+ session.onClose(() => {
468
+ if (target)
469
+ target.#onExit();
470
+ });
471
+ }
487
472
  // Wait for the bootstrap's top-level await to finish installing the
488
473
  // shim. There is no cap here: launch's start timeout bounds the wait and
489
474
  // kills the child, so a slow bootstrap never yields a target without
@@ -511,12 +496,43 @@ export class Target {
511
496
  /** The Debugger domain on this target's session, for src/debugger.ts. */
512
497
  get debug() {
513
498
  return {
514
- send: (method, params = {}) => this.#shell.send(method, params),
499
+ send: (method, params = {}) => {
500
+ if (method === "Debugger.setPauseOnExceptions") {
501
+ this.#pauseOnExceptions = String(params.state ?? "none");
502
+ // Applied when the kernel's own call in flight finishes.
503
+ if (this.#unpausable > 0)
504
+ return Promise.resolve({});
505
+ }
506
+ if (method === "Debugger.disable")
507
+ this.#pauseOnExceptions = "none";
508
+ return this.#shell.send(method, params);
509
+ },
515
510
  on: (method, listener) => this.#shell.on(method, listener),
516
511
  contextId: this.#contextId,
517
512
  baselineGlobals: this.#baselineGlobals,
513
+ withoutPausing: (call) => this.#withoutPausing(call),
518
514
  };
519
515
  }
516
+ /**
517
+ * Runs the kernel's own inspector call with pausing on exceptions off. A
518
+ * pause inside a call the inspector dispatches holds that call (Node), or
519
+ * resumes by itself and leaves Deno's event loop stopped for good, so no
520
+ * later cell runs (#307). A cell that throws meanwhile doesn't pause.
521
+ */
522
+ async #withoutPausing(call) {
523
+ if (this.#pauseOnExceptions === "none" || this.#paused)
524
+ return call();
525
+ if (this.#unpausable++ === 0)
526
+ await this.#shell.send("Debugger.setPauseOnExceptions", { state: "none" });
527
+ try {
528
+ return await call();
529
+ }
530
+ finally {
531
+ if (--this.#unpausable === 0 && !this.#exiting) {
532
+ await this.#shell.send("Debugger.setPauseOnExceptions", { state: this.#pauseOnExceptions }).catch(() => { });
533
+ }
534
+ }
535
+ }
520
536
  // Messages wait until both pipes have caught up to their marker,
521
537
  // and are delivered strictly in order.
522
538
  #deliver(msg, events) {
@@ -665,12 +681,12 @@ export class Target {
665
681
  * even a plain return value in a promise it holds only weakly (#55).
666
682
  */
667
683
  callFunctionOn(functionDeclaration, args) {
668
- return this.#shell.send("Runtime.callFunctionOn", {
684
+ return this.#withoutPausing(() => this.#shell.send("Runtime.callFunctionOn", {
669
685
  functionDeclaration,
670
686
  arguments: args,
671
687
  executionContextId: this.#contextId,
672
688
  returnByValue: true,
673
- });
689
+ }));
674
690
  }
675
691
  /**
676
692
  * For functions that return a promise. The target holds it until after
@@ -699,6 +715,19 @@ export class Target {
699
715
  arg,
700
716
  ]).catch(interrupted("Execution"));
701
717
  }
718
+ async format(value) {
719
+ const arg = value.objectId
720
+ ? { objectId: value.objectId }
721
+ : value.unserializableValue !== undefined
722
+ ? { unserializableValue: value.unserializableValue }
723
+ : { value: value.value };
724
+ const res = await this.#callAsyncFunctionOn(`function (v) { return globalThis.${SHIM}.formatJson(v); }`, [arg])
725
+ .catch(interrupted("Execution"));
726
+ const thrown = res.exceptionDetails;
727
+ if (thrown)
728
+ throw new Error(thrown.exception?.description ?? thrown.text ?? "the value could not be formatted");
729
+ return parseBundle(res.result?.value);
730
+ }
702
731
  /**
703
732
  * Hands a frontend comm message to the target's comms. Resolves to
704
733
  * whether a comm or registered target took it, or to the error a handler
@@ -0,0 +1,75 @@
1
+ import type { LineMap } from "./sourcemap.ts";
2
+ export declare const TRACEBACK_MIME = "application/vnd.nteract.traceback+json";
3
+ /** The //# sourceURL a cell's script runs under while not debugging. */
4
+ export declare function cellName(code: string): string;
5
+ /** A cell that ran: its code, and where the notebook knows it from. */
6
+ export interface CellRecord {
7
+ code: string;
8
+ executionCount: number | null;
9
+ cellId: string | null;
10
+ }
11
+ export interface SourceLine extends Record<string, string | number | boolean> {
12
+ lineno: number;
13
+ source: string;
14
+ }
15
+ export interface TracebackFrame extends Record<string, unknown> {
16
+ filename: string;
17
+ lineno: number;
18
+ colno?: number;
19
+ name: string;
20
+ library: boolean;
21
+ lines: SourceLine[];
22
+ execution_count?: number;
23
+ cell_id?: string;
24
+ }
25
+ /** A frame of CDP's exceptionDetails.stackTrace (0-based positions). */
26
+ export interface CallFrame {
27
+ url: string;
28
+ lineNumber: number;
29
+ columnNumber: number;
30
+ }
31
+ /** A frame as parsed from one line of a stack. */
32
+ export interface StackFrame {
33
+ name: string;
34
+ file: string;
35
+ line: number | null;
36
+ column: number | null;
37
+ }
38
+ /** One line of a stack as a frame, or null for a line that isn't one (the message). */
39
+ export declare function parseFrame(text: string): StackFrame | null;
40
+ /**
41
+ * Frames from packages, runtimes and the kernel itself, which a frontend
42
+ * can dim or fold: a hint, not a boundary.
43
+ */
44
+ export declare function isLibraryFile(file: string, kernelFiles?: readonly string[]): boolean;
45
+ export interface TracebackSources {
46
+ /** The cell a script name belongs to, if it is one. */
47
+ cell(file: string): (CellRecord & {
48
+ lines: LineMap | null;
49
+ }) | undefined;
50
+ /** Another file's source, when it can be read. */
51
+ readFile?(file: string): string | undefined;
52
+ /** Path or URL prefixes of the kernel's own runtime files. */
53
+ kernelFiles?: readonly string[];
54
+ }
55
+ export interface Traceback {
56
+ /** The classic traceback lines, with cell frames pointing at the cell. */
57
+ lines: string[];
58
+ /** The structured payload. */
59
+ payload: Record<string, unknown>;
60
+ }
61
+ /**
62
+ * `stack` is the error's stack as the target describes it (src/target.ts's
63
+ * describeError), message lines included. `callFrames`, from CDP, name the
64
+ * scripts of frames the stack shows as "<anonymous>": Deno formats stacks
65
+ * itself and doesn't use a script's //# sourceURL.
66
+ */
67
+ export declare function buildTraceback(error: {
68
+ ename: string;
69
+ evalue: string;
70
+ stack: string;
71
+ callFrames?: readonly CallFrame[];
72
+ }, sources: TracebackSources, execution: {
73
+ executionCount: number | null;
74
+ cellId: string | null;
75
+ }): Traceback;