@tlockney/cdp-kernel 0.6.0 → 0.7.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.
@@ -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.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-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.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-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,80 @@
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
+ dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
28
+ handled: boolean;
29
+ } | {
30
+ error: {
31
+ name: string;
32
+ message: string;
33
+ stack: string;
34
+ };
35
+ }>;
36
+ describeError(exception: RemoteObject): Promise<{
37
+ name: string;
38
+ message: string;
39
+ stack: string;
40
+ }>;
41
+ propertyNames(expression: string): Promise<string[]>;
42
+ lexicalNames(): Promise<string[]>;
43
+ /** inspect_request (#10): text describing a dotted path's value, without running anything. */
44
+ inspect(path: string, detail: number): Promise<string | null>;
45
+ /** The Debugger domain, on targets driven over CDP (#18). */
46
+ readonly debug?: DebugChannel;
47
+ /** Answers the target's pending prompt; `interrupted` makes it throw instead (#121). */
48
+ replyInput(value: string | null, interrupted?: boolean): void;
49
+ /** What the host waits on, for a kernel that has stopped answering (#237). */
50
+ diagnose?(): Promise<CdpParams>;
51
+ /** Lets go of the last cell's result and error, once they are shown (#286). */
52
+ releaseResults?(): Promise<void>;
53
+ kill(): void;
54
+ }
55
+ /** Messages the bootstrap sends: message records on stdout, or Bun's channel. */
56
+ export type TargetMessage = {
57
+ kind: "iopub";
58
+ msg_type: string;
59
+ content: CdpParams;
60
+ metadata: CdpParams;
61
+ buffers: string[];
62
+ barrier: number;
63
+ } | {
64
+ kind: "input_request";
65
+ prompt: string;
66
+ password: boolean;
67
+ barrier: number;
68
+ };
69
+ /** The parsed value, or undefined for text that isn't JSON. */
70
+ export declare function parseJson(text: string): unknown;
71
+ export declare function isTargetMessage(value: unknown): value is TargetMessage;
72
+ export interface TargetEvents {
73
+ stream(name: "stdout" | "stderr", text: string): void;
74
+ message(msg: TargetMessage): void;
75
+ /** `note` says why, when the target was stopped on purpose. */
76
+ exit(code: number | null, note?: string): void;
77
+ }
78
+ export declare class Interrupted extends Error {
79
+ name: string;
80
+ }
@@ -0,0 +1,22 @@
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
+ /** The parsed value, or undefined for text that isn't JSON. */
5
+ export function parseJson(text) {
6
+ try {
7
+ return JSON.parse(text);
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ }
13
+ export function isTargetMessage(value) {
14
+ if (typeof value !== "object" || value === null)
15
+ return false;
16
+ const kind = value.kind;
17
+ return (kind === "iopub" || kind === "input_request") &&
18
+ typeof value.barrier === "number";
19
+ }
20
+ export class Interrupted extends Error {
21
+ name = "Interrupted";
22
+ }
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, parseJson, type TargetEvents, type TargetMessage } from "./target-api.ts";
5
+ export { Interrupted, isTargetMessage, 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;
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, parseJson } from "./target-api.js";
15
+ export { Interrupted, isTargetMessage, 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).
@@ -84,22 +86,6 @@ export function esbuildBinary(resolve = import.meta.resolve, platform = process.
84
86
  return null;
85
87
  }
86
88
  }
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
89
  const MARKER_PREFIX = "\x1b[cdpk:";
104
90
  // A message record: the message's JSON, which never holds a raw ESC, between
105
91
  // "\x1b[cdpk:<" and "\x1b]" (#271).
@@ -327,9 +313,6 @@ export class Gates {
327
313
  this.#map.clear();
328
314
  }
329
315
  }
330
- export class Interrupted extends Error {
331
- name = "Interrupted";
332
- }
333
316
  /** A catch handler: a call interrupt() abandoned becomes Interrupted. */
334
317
  function interrupted(what) {
335
318
  return (err) => {
@@ -351,6 +334,9 @@ export class Target {
351
334
  #undelivered = 0;
352
335
  #interrupted = false;
353
336
  #paused = false;
337
+ /** What the debugger asked to pause on (#307), and how many kernel calls hold it off. */
338
+ #pauseOnExceptions = "none";
339
+ #unpausable = 0;
354
340
  /** Set once the main context is gone: the runtime is exiting (#130). */
355
341
  #exiting = false;
356
342
  #exited;
@@ -511,12 +497,43 @@ export class Target {
511
497
  /** The Debugger domain on this target's session, for src/debugger.ts. */
512
498
  get debug() {
513
499
  return {
514
- send: (method, params = {}) => this.#shell.send(method, params),
500
+ send: (method, params = {}) => {
501
+ if (method === "Debugger.setPauseOnExceptions") {
502
+ this.#pauseOnExceptions = String(params.state ?? "none");
503
+ // Applied when the kernel's own call in flight finishes.
504
+ if (this.#unpausable > 0)
505
+ return Promise.resolve({});
506
+ }
507
+ if (method === "Debugger.disable")
508
+ this.#pauseOnExceptions = "none";
509
+ return this.#shell.send(method, params);
510
+ },
515
511
  on: (method, listener) => this.#shell.on(method, listener),
516
512
  contextId: this.#contextId,
517
513
  baselineGlobals: this.#baselineGlobals,
514
+ withoutPausing: (call) => this.#withoutPausing(call),
518
515
  };
519
516
  }
517
+ /**
518
+ * Runs the kernel's own inspector call with pausing on exceptions off. A
519
+ * pause inside a call the inspector dispatches holds that call (Node), or
520
+ * resumes by itself and leaves Deno's event loop stopped for good, so no
521
+ * later cell runs (#307). A cell that throws meanwhile doesn't pause.
522
+ */
523
+ async #withoutPausing(call) {
524
+ if (this.#pauseOnExceptions === "none" || this.#paused)
525
+ return call();
526
+ if (this.#unpausable++ === 0)
527
+ await this.#shell.send("Debugger.setPauseOnExceptions", { state: "none" });
528
+ try {
529
+ return await call();
530
+ }
531
+ finally {
532
+ if (--this.#unpausable === 0 && !this.#exiting) {
533
+ await this.#shell.send("Debugger.setPauseOnExceptions", { state: this.#pauseOnExceptions }).catch(() => { });
534
+ }
535
+ }
536
+ }
520
537
  // Messages wait until both pipes have caught up to their marker,
521
538
  // and are delivered strictly in order.
522
539
  #deliver(msg, events) {
@@ -665,12 +682,12 @@ export class Target {
665
682
  * even a plain return value in a promise it holds only weakly (#55).
666
683
  */
667
684
  callFunctionOn(functionDeclaration, args) {
668
- return this.#shell.send("Runtime.callFunctionOn", {
685
+ return this.#withoutPausing(() => this.#shell.send("Runtime.callFunctionOn", {
669
686
  functionDeclaration,
670
687
  arguments: args,
671
688
  executionContextId: this.#contextId,
672
689
  returnByValue: true,
673
- });
690
+ }));
674
691
  }
675
692
  /**
676
693
  * For functions that return a promise. The target holds it until after
@@ -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;
@@ -0,0 +1,200 @@
1
+ // Structured tracebacks (#322): an error's stack as frames a frontend can
2
+ // render, link to cells and fold, alongside the classic `traceback` lines.
3
+ // The payload follows nteract's application/vnd.nteract.traceback+json
4
+ // (runtimed/runt, docs/adr/traceback-output-protocol.md): ename, evalue,
5
+ // frames with a source window and a `library` flag, and a plain `text`
6
+ // rendering. Building it must never cost the user their traceback: callers
7
+ // fall back to the classic lines if it throws.
8
+ //
9
+ // Cells run under a //# sourceURL naming them (cellName), so a frame in a
10
+ // cell, including one in a function an earlier cell defined, says which
11
+ // cell. Its line goes through the cell's source map, as for the debugger
12
+ // (src/sourcemap.ts), so it is the line the user wrote, not the line of the
13
+ // stripped JavaScript. Only lines are mapped: a cell frame has no column.
14
+ //
15
+ // Nothing here imports Node; reading other files' source is the caller's.
16
+ import { murmur2 } from "./murmur2.js";
17
+ export const TRACEBACK_MIME = "application/vnd.nteract.traceback+json";
18
+ /** Lines of source shown around a frame's line. */
19
+ const CONTEXT_BEFORE = 2;
20
+ const CONTEXT_AFTER = 2;
21
+ /** Longer stacks keep their first and last frames. */
22
+ const HEAD_FRAMES = 20;
23
+ const TAIL_FRAMES = 10;
24
+ /** The //# sourceURL a cell's script runs under while not debugging. */
25
+ export function cellName(code) {
26
+ return `$cdpk$cell-${murmur2(code, 0).toString(36)}.ts`;
27
+ }
28
+ // V8: " at fn (file:1:2)", " at file:1:2", " at async fn (file:1:2)",
29
+ // " at new Promise (<anonymous>)".
30
+ const V8_FRAME = /^\s*at (?:(.*?) \((.*?)(?::(\d+):(\d+))?\)|(.*?)(?::(\d+):(\d+))?)$/;
31
+ // JavaScriptCore and SpiderMonkey: "fn@file:1:2", "@file:1:2", "fn@[native code]".
32
+ const AT_FRAME = /^([^\s@]*)@(.+?)(?::(\d+):(\d+))?$/;
33
+ /** One line of a stack as a frame, or null for a line that isn't one (the message). */
34
+ export function parseFrame(text) {
35
+ const v8 = /^\s*at /.test(text) ? V8_FRAME.exec(text) : null;
36
+ if (v8) {
37
+ const named = v8[2] !== undefined;
38
+ const line = named ? v8[3] : v8[6];
39
+ const column = named ? v8[4] : v8[7];
40
+ return {
41
+ name: named ? v8[1] : "",
42
+ file: named ? v8[2] : v8[5],
43
+ line: line === undefined ? null : Number(line),
44
+ column: column === undefined ? null : Number(column),
45
+ };
46
+ }
47
+ const at = !/^\s/.test(text) && text.includes("@") ? AT_FRAME.exec(text) : null;
48
+ if (at) {
49
+ return {
50
+ name: at[1],
51
+ file: at[2],
52
+ line: at[3] === undefined ? null : Number(at[3]),
53
+ column: at[4] === undefined ? null : Number(at[4]),
54
+ };
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Frames from packages, runtimes and the kernel itself, which a frontend
60
+ * can dim or fold: a hint, not a boundary.
61
+ */
62
+ export function isLibraryFile(file, kernelFiles = []) {
63
+ if (["", "<anonymous>", "native", "[native code]", "unknown"].includes(file))
64
+ return true;
65
+ if (/^(?:node|ext|deno|bun|jsr|npm|internal):/.test(file))
66
+ return true;
67
+ if (/^https?:\/\/(?:jsr\.io|esm\.sh|cdn\.jsdelivr\.net|unpkg\.com|deno\.land)\//.test(file))
68
+ return true;
69
+ const path = file.replaceAll("\\", "/");
70
+ if (path.includes("/node_modules/") || path.includes("/npm/registry.npmjs.org/"))
71
+ return true;
72
+ return kernelFiles.some((prefix) => path.startsWith(prefix));
73
+ }
74
+ function window(source, line) {
75
+ const all = source.split("\n");
76
+ if (all.length > 1 && all[all.length - 1] === "")
77
+ all.pop();
78
+ const out = [];
79
+ for (let n = Math.max(1, line - CONTEXT_BEFORE); n <= Math.min(all.length, line + CONTEXT_AFTER); n++) {
80
+ const entry = { lineno: n, source: all[n - 1].replace(/\r$/, "") };
81
+ if (n === line)
82
+ entry.highlight = true;
83
+ out.push(entry);
84
+ }
85
+ return out;
86
+ }
87
+ /** How a cell frame's location reads in the classic traceback. */
88
+ function cellLabel(cell) {
89
+ return cell.executionCount === null ? "cell" : `In[${cell.executionCount}]`;
90
+ }
91
+ /**
92
+ * `stack` is the error's stack as the target describes it (src/target.ts's
93
+ * describeError), message lines included. `callFrames`, from CDP, name the
94
+ * scripts of frames the stack shows as "<anonymous>": Deno formats stacks
95
+ * itself and doesn't use a script's //# sourceURL.
96
+ */
97
+ export function buildTraceback(error, sources, execution) {
98
+ const raw = error.stack || `${error.ename}: ${error.evalue}`;
99
+ const lines = [];
100
+ const frames = [];
101
+ const callFrames = error.callFrames ?? [];
102
+ let next = 0;
103
+ for (const text of raw.split("\n")) {
104
+ const parsed = parseFrame(text);
105
+ if (parsed === null) {
106
+ lines.push(text);
107
+ continue;
108
+ }
109
+ let file = parsed.file;
110
+ if (parsed.line !== null) {
111
+ // CDP lists only frames with a position, in the same order.
112
+ const at = callFrames.findIndex((frame, i) => i >= next && frame.lineNumber + 1 === parsed.line && frame.columnNumber + 1 === parsed.column);
113
+ if (at !== -1) {
114
+ next = at + 1;
115
+ if (file === "<anonymous>" && callFrames[at].url)
116
+ file = callFrames[at].url;
117
+ }
118
+ }
119
+ const cell = sources.cell(file);
120
+ if (cell !== undefined && parsed.line !== null) {
121
+ // Script lines are 1-based; the map's are 0-based.
122
+ const lineno = cell.lines ? cell.lines.toOriginal(parsed.line - 1) + 1 : parsed.line;
123
+ const frame = {
124
+ filename: cellLabel(cell),
125
+ lineno,
126
+ name: parsed.name,
127
+ library: false,
128
+ lines: window(cell.code, lineno),
129
+ };
130
+ if (cell.executionCount !== null)
131
+ frame.execution_count = cell.executionCount;
132
+ if (cell.cellId !== null)
133
+ frame.cell_id = cell.cellId;
134
+ frame.source_ref = {
135
+ kind: "notebook_execution",
136
+ ...(cell.executionCount !== null ? { execution_count: cell.executionCount } : {}),
137
+ ...(cell.cellId !== null ? { cell_id: cell.cellId } : {}),
138
+ };
139
+ frames.push(frame);
140
+ const location = `${cellLabel(cell)}:${lineno}`;
141
+ lines.push(rewriteLocation(text, parsed, location));
142
+ continue;
143
+ }
144
+ lines.push(text);
145
+ const library = isLibraryFile(file, sources.kernelFiles);
146
+ const frame = {
147
+ filename: file,
148
+ lineno: parsed.line ?? 0,
149
+ name: parsed.name,
150
+ library,
151
+ lines: [],
152
+ };
153
+ if (parsed.column !== null)
154
+ frame.colno = parsed.column;
155
+ if (!library && parsed.line !== null) {
156
+ const source = sources.readFile?.(file);
157
+ if (source !== undefined)
158
+ frame.lines = window(source, parsed.line);
159
+ }
160
+ frames.push(frame);
161
+ }
162
+ // What ran the cell (the kernel's evaluator, the runtime's own frames)
163
+ // comes after the cell's last frame and is never the user's.
164
+ const lastCell = frames.findLastIndex((frame) => frame.source_ref !== undefined);
165
+ let end = frames.length;
166
+ while (end > lastCell + 1 && lastCell !== -1 && frames[end - 1].library)
167
+ end--;
168
+ const kept = clip(frames.slice(0, end));
169
+ const payload = {
170
+ ename: error.ename,
171
+ evalue: error.evalue,
172
+ language: "typescript",
173
+ frames: kept,
174
+ syntax: null,
175
+ text: lines.join("\n"),
176
+ raw_text: raw,
177
+ };
178
+ if (execution.executionCount !== null || execution.cellId !== null) {
179
+ payload.execution = {
180
+ ...(execution.executionCount !== null ? { execution_count: execution.executionCount } : {}),
181
+ ...(execution.cellId !== null ? { cell_id: execution.cellId } : {}),
182
+ };
183
+ }
184
+ return { lines, payload };
185
+ }
186
+ function rewriteLocation(text, frame, location) {
187
+ const original = `${frame.file}:${frame.line}${frame.column === null ? "" : `:${frame.column}`}`;
188
+ const at = text.lastIndexOf(original);
189
+ return at === -1 ? text : text.slice(0, at) + location + text.slice(at + original.length);
190
+ }
191
+ function clip(frames) {
192
+ if (frames.length <= HEAD_FRAMES + TAIL_FRAMES)
193
+ return frames;
194
+ const omitted = frames.length - HEAD_FRAMES - TAIL_FRAMES;
195
+ return [
196
+ ...frames.slice(0, HEAD_FRAMES),
197
+ { filename: "", lineno: 0, name: `… ${omitted} frames omitted …`, library: true, lines: [] },
198
+ ...frames.slice(-TAIL_FRAMES),
199
+ ];
200
+ }
@@ -1,5 +1,6 @@
1
1
  import { LineMap } from "./sourcemap.ts";
2
2
  export declare const SHIM = "__cdpk";
3
+ export declare const YIELD = "await __cdpk.tick();\n";
3
4
  /**
4
5
  * Each replMode cell also ends by yielding one macrotask, so the script
5
6
  * settles with no other microtasks queued ahead of the inspector's