@code-yeongyu/senpi-codemode 2026.10.8 → 2026.10.10-10
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/CHANGELOG.md +178 -0
- package/README.md +212 -14
- package/package.json +6 -5
- package/src/bridge/http-server.ts +9 -1
- package/src/bridge/protocol.ts +12 -3
- package/src/bridge/reserved.ts +2 -0
- package/src/bridges/eval-environment-schemas.ts +48 -0
- package/src/bridges/eval-virtual-schemas.ts +12 -4
- package/src/bridges/reserved-dispatch.ts +13 -1
- package/src/config/executable-settings.json +5 -0
- package/src/config/project-trust.ts +51 -0
- package/src/config/settings-overrides.ts +2 -0
- package/src/environments/js-environments.ts +208 -0
- package/src/environments/js-installer.ts +268 -0
- package/src/environments/js-revision-files.ts +91 -0
- package/src/environments/no-links.ts +132 -0
- package/src/environments/packages-install.ts +98 -0
- package/src/environments/project-resolves.ts +54 -0
- package/src/environments/py-environment.ts +7 -3
- package/src/environments/py-installer.ts +68 -4
- package/src/environments/python-environments.ts +85 -0
- package/src/environments/revision-store.ts +38 -7
- package/src/extension/bridge-tool-call.ts +7 -1
- package/src/extension/cell-kernel-tools.ts +28 -0
- package/src/extension/eval-notifier.ts +13 -2
- package/src/extension/kernel-registration.ts +7 -2
- package/src/extension/kernel-replacement.ts +20 -0
- package/src/extension/runtime-factory.ts +53 -3
- package/src/extension/runtime-info.ts +6 -2
- package/src/extension/session-manager-contract.ts +3 -0
- package/src/extension/session-manager-proxy.ts +6 -0
- package/src/extension/session-manager.ts +26 -4
- package/src/index.ts +4 -2
- package/src/interpreters/detect.ts +34 -1
- package/src/kernels/AGENTS.md +2 -0
- package/src/kernels/jl/prelude.jl +6 -1
- package/src/kernels/js/cell-dispatch.ts +44 -0
- package/src/kernels/js/cell-ownership.js +268 -0
- package/src/kernels/js/cell-run-context.js +89 -0
- package/src/kernels/js/context-manager.ts +48 -39
- package/src/kernels/js/control-frames.ts +24 -0
- package/src/kernels/js/host-entries.ts +62 -0
- package/src/kernels/js/kernel-contract.ts +16 -1
- package/src/kernels/js/kernel-memory-bridge.ts +1 -1
- package/src/kernels/js/kernel-memory.ts +79 -0
- package/src/kernels/js/kernel-tools-registry.js +1 -0
- package/src/kernels/js/local-module-loader.ts +21 -5
- package/src/kernels/js/process-drain-scan.d.ts +8 -0
- package/src/kernels/js/process-drain-scan.js +28 -0
- package/src/kernels/js/process-entry.js +466 -0
- package/src/kernels/js/process-memory-host.ts +83 -0
- package/src/kernels/js/process-worker.ts +298 -0
- package/src/kernels/js/rejection-reports.js +61 -0
- package/src/kernels/js/run-queue.ts +7 -0
- package/src/kernels/js/worker-core.js +53 -8
- package/src/kernels/js/worker-cwd.js +5 -2
- package/src/kernels/js/worker-host.ts +9 -2
- package/src/kernels/js/worker-indirect-eval.js +37 -13
- package/src/kernels/js/worker-package-resolve.js +153 -0
- package/src/kernels/js/worker-require.d.ts +19 -0
- package/src/kernels/js/worker-require.js +52 -0
- package/src/kernels/js/worker-runtime.js +45 -9
- package/src/kernels/js/worker-shadow-guard.js +2 -6
- package/src/kernels/js/worker-shell-capture.js +5 -0
- package/src/kernels/js/worker-slot.ts +9 -0
- package/src/kernels/js/worker-startup.ts +49 -1
- package/src/kernels/js/worker-webview.js +15 -1
- package/src/kernels/js/workpool.js +5 -2
- package/src/kernels/py/host-cell.ts +8 -31
- package/src/kernels/py/kernel.ts +8 -2
- package/src/kernels/py/prelude.py +145 -11
- package/src/kernels/py/process.ts +4 -0
- package/src/kernels/py/transport.ts +13 -1
- package/src/kernels/rb/workpool.rb +7 -1
- package/src/kernels/sandbox/sandbox-cell.ts +141 -0
- package/src/kernels/sandbox/vendor/pi-codemode/VENDORED.md +1 -0
- package/src/kernels/sandbox/vendor/pi-codemode/runtime/prelude-source.ts +14 -1
- package/src/kernels/sandbox/vendor/pi-codemode/runtime/worker.ts +4 -1
- package/src/kernels/sandbox/vendor/pi-codemode/types.ts +2 -0
- package/src/kernels/shared/cell-source-at-start.ts +18 -0
- package/src/kernels/shared/host-cell.ts +57 -0
- package/src/kernels/shared/subprocess-process.ts +14 -0
- package/src/prompt/eval-prompt.ts +8 -1
- package/src/tool/AGENTS.md +1 -0
- package/src/tool/cell-handler.ts +13 -12
- package/src/tool/cell-reserved-dispatch.ts +1 -0
- package/src/tool/cell-runtime.ts +4 -2
- package/src/tool/detached-cell-contract.ts +3 -0
- package/src/tool/detached-cell-manager.ts +4 -8
- package/src/tool/detached-cell-notification.ts +11 -63
- package/src/tool/detached-cell-snapshot.ts +17 -0
- package/src/tool/detached-eval-result.ts +13 -7
- package/src/tool/detached-notification-queue.ts +1 -2
- package/src/tool/eval-request.ts +23 -1
- package/src/tool/eval-tool-options.ts +6 -0
- package/src/tool/eval-tool.ts +17 -4
- package/src/tool/interrupt-note.ts +4 -5
- package/src/tool/live-headline.ts +60 -0
- package/src/tool/load-cell.ts +88 -0
- package/src/tool/magic-cell-host.ts +123 -0
- package/src/tool/magic-cells.ts +75 -0
- package/src/tool/managed-cell.ts +0 -4
- package/src/tool/render.ts +74 -15
- package/src/tool/run-eval-cell.ts +73 -1
- package/src/tool/runtime-label.ts +2 -1
- package/src/tool/types.ts +50 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isBuiltin } from "node:module";
|
|
3
|
+
import { dirname, join, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
// A cell's bare import runs `import()` from this worker's own module, so the runtime resolves it from Senpi's install
|
|
7
|
+
// tree, not the session's project or its managed package revision. This resolver walks `node_modules` from each root
|
|
8
|
+
// the cell names, in order, and resolves the package entry the way ESM does (the `exports` field with import
|
|
9
|
+
// conditions), so ESM-only packages load and the first root that has the package wins.
|
|
10
|
+
const RESOLVE_PACKAGE = Symbol.for("senpi.kernel.resolvePackage");
|
|
11
|
+
const CONDITIONS = typeof globalThis.Bun === "object" ? ["bun", "node", "import", "default"] : ["node", "import", "default"];
|
|
12
|
+
|
|
13
|
+
function isFile(path) {
|
|
14
|
+
try {
|
|
15
|
+
return statSync(path).isFile();
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isDirectory(path) {
|
|
22
|
+
try {
|
|
23
|
+
return statSync(path).isDirectory();
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function splitSpecifier(specifier) {
|
|
30
|
+
const parts = specifier.split("/");
|
|
31
|
+
const nameParts = specifier.startsWith("@") ? 2 : 1;
|
|
32
|
+
if (parts.length < nameParts || parts.slice(0, nameParts).some((part) => part === "" || part === "." || part === ".."))
|
|
33
|
+
return undefined;
|
|
34
|
+
const rest = parts.slice(nameParts).join("/");
|
|
35
|
+
return { name: parts.slice(0, nameParts).join("/"), subpath: rest === "" ? "." : `./${rest}` };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function conditionalTarget(target) {
|
|
39
|
+
if (typeof target === "string") return target;
|
|
40
|
+
if (Array.isArray(target)) {
|
|
41
|
+
for (const candidate of target) {
|
|
42
|
+
const resolved = conditionalTarget(candidate);
|
|
43
|
+
if (resolved !== undefined) return resolved;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (target === null || typeof target !== "object") return undefined;
|
|
48
|
+
for (const [condition, value] of Object.entries(target)) {
|
|
49
|
+
if (condition !== "default" && !CONDITIONS.includes(condition)) continue;
|
|
50
|
+
const resolved = conditionalTarget(value);
|
|
51
|
+
if (resolved !== undefined) return resolved;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function exportsTarget(exportsField, subpath) {
|
|
57
|
+
const subpathKeyed =
|
|
58
|
+
exportsField !== null &&
|
|
59
|
+
typeof exportsField === "object" &&
|
|
60
|
+
!Array.isArray(exportsField) &&
|
|
61
|
+
Object.keys(exportsField).some((key) => key.startsWith("."));
|
|
62
|
+
if (!subpathKeyed) return subpath === "." ? conditionalTarget(exportsField) : undefined;
|
|
63
|
+
if (Object.hasOwn(exportsField, subpath)) return conditionalTarget(exportsField[subpath]);
|
|
64
|
+
let best;
|
|
65
|
+
for (const key of Object.keys(exportsField)) {
|
|
66
|
+
const star = key.indexOf("*");
|
|
67
|
+
if (star < 0 || key.includes("*", star + 1)) continue;
|
|
68
|
+
const prefix = key.slice(0, star);
|
|
69
|
+
const suffix = key.slice(star + 1);
|
|
70
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix) || subpath.length < prefix.length + suffix.length)
|
|
71
|
+
continue;
|
|
72
|
+
if (best === undefined || prefix.length > best.prefix.length) {
|
|
73
|
+
best = { key, prefix, match: subpath.slice(prefix.length, subpath.length - suffix.length) };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (best === undefined || hasInvalidSegment(best.match)) return undefined;
|
|
77
|
+
const target = conditionalTarget(exportsField[best.key]);
|
|
78
|
+
return target === undefined ? undefined : target.replaceAll("*", best.match);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Node refuses a pattern match or target with "." / ".." / "node_modules" segments (ERR_INVALID_MODULE_SPECIFIER).
|
|
82
|
+
function hasInvalidSegment(path) {
|
|
83
|
+
return path.split(/[\\/]/).some((segment) => segment === "." || segment === ".." || segment === "node_modules");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function legacyFile(path) {
|
|
87
|
+
for (const candidate of [path, `${path}.js`, `${path}.mjs`, `${path}.cjs`, join(path, "index.js")]) {
|
|
88
|
+
if (isFile(candidate)) return candidate;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function packageEntry(packageDir, subpath, specifier) {
|
|
94
|
+
let manifest;
|
|
95
|
+
try {
|
|
96
|
+
manifest = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
|
|
97
|
+
} catch {
|
|
98
|
+
return legacyFile(join(packageDir, subpath === "." ? "index.js" : subpath));
|
|
99
|
+
}
|
|
100
|
+
if (manifest.exports !== undefined && manifest.exports !== null) {
|
|
101
|
+
const target = exportsTarget(manifest.exports, subpath);
|
|
102
|
+
if (typeof target !== "string" || !target.startsWith("./") || hasInvalidSegment(target.slice(2))) {
|
|
103
|
+
const error = new Error(`Package subpath '${subpath}' is not exported for import by '${specifier}'`);
|
|
104
|
+
error.code = "ERR_PACKAGE_PATH_NOT_EXPORTED";
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
return join(packageDir, target);
|
|
108
|
+
}
|
|
109
|
+
if (subpath !== ".") return legacyFile(join(packageDir, subpath));
|
|
110
|
+
const moduleEntry = typeof globalThis.Bun === "object" && typeof manifest.module === "string" ? manifest.module : undefined;
|
|
111
|
+
const main = moduleEntry ?? (typeof manifest.main === "string" ? manifest.main : "index.js");
|
|
112
|
+
return legacyFile(join(packageDir, main));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function findPackageDir(name, rootUrl) {
|
|
116
|
+
let directory = fileURLToPath(rootUrl);
|
|
117
|
+
for (;;) {
|
|
118
|
+
const candidate = join(directory, "node_modules", name);
|
|
119
|
+
if (isDirectory(candidate)) return candidate;
|
|
120
|
+
const parent = dirname(directory);
|
|
121
|
+
if (parent === directory) return undefined;
|
|
122
|
+
directory = parent;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The file URL the bare `specifier` resolves to from the first of `rootUrls` whose `node_modules` chain has its
|
|
128
|
+
* package, or undefined when it names a builtin or no root has the package (the caller then imports it natively).
|
|
129
|
+
*/
|
|
130
|
+
export function resolvePackage(specifier, rootUrls) {
|
|
131
|
+
if (specifier.includes(":") || isBuiltin(specifier)) return undefined;
|
|
132
|
+
const parts = splitSpecifier(specifier);
|
|
133
|
+
if (parts === undefined) return undefined;
|
|
134
|
+
for (const rootUrl of rootUrls) {
|
|
135
|
+
if (typeof rootUrl !== "string") continue;
|
|
136
|
+
const packageDir = findPackageDir(parts.name, rootUrl);
|
|
137
|
+
if (packageDir === undefined) continue;
|
|
138
|
+
const entry = packageEntry(packageDir, parts.subpath, specifier);
|
|
139
|
+
if (entry === undefined) continue;
|
|
140
|
+
let real = entry;
|
|
141
|
+
try {
|
|
142
|
+
real = realpathSync(entry);
|
|
143
|
+
} catch {
|
|
144
|
+
// A missing target fails in the import itself, with the runtime's own error.
|
|
145
|
+
}
|
|
146
|
+
return pathToFileURL(real.endsWith(sep) ? real.slice(0, -1) : real).href;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function installPackageResolver() {
|
|
152
|
+
globalThis[RESOLVE_PACKAGE] = resolvePackage;
|
|
153
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CellRequireContext {
|
|
2
|
+
readonly cwdUrl: string;
|
|
3
|
+
readonly packageRootUrl?: string | null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CellResolve {
|
|
7
|
+
(specifier: string, options?: { readonly paths?: readonly string[] }): string;
|
|
8
|
+
paths(specifier: string): string[] | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CellRequire {
|
|
12
|
+
(specifier: string): unknown;
|
|
13
|
+
readonly resolve: CellResolve;
|
|
14
|
+
readonly cache: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createCellRequire(context: () => CellRequireContext): CellRequire;
|
|
18
|
+
|
|
19
|
+
export { createRequire } from "node:module";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createRequire, isBuiltin } from "node:module";
|
|
2
|
+
|
|
3
|
+
// `require` in a cell resolves the way `import` does there: builtins natively, relative paths from the cell's
|
|
4
|
+
// directory, bare packages from the session's project first and then its managed package environment
|
|
5
|
+
// (%bun add / %npm add). It uses the runtime's own CommonJS loader, so JSON files and the `require` export conditions
|
|
6
|
+
// behave as in Node. Like Node's own `require`, it carries `resolve`, `resolve.paths` and `cache`.
|
|
7
|
+
export function createCellRequire(context) {
|
|
8
|
+
const requirers = () => {
|
|
9
|
+
const { cwdUrl, packageRootUrl } = context();
|
|
10
|
+
const fromCwd = createRequire(new URL("package.json", cwdUrl));
|
|
11
|
+
const fromPackages = packageRootUrl ? createRequire(new URL("package.json", packageRootUrl)) : undefined;
|
|
12
|
+
return { fromCwd, fromPackages };
|
|
13
|
+
};
|
|
14
|
+
const isPathLike = (name) => name.startsWith(".") || name.startsWith("/") || /^[A-Za-z]:[\\/]/.test(name);
|
|
15
|
+
// The one lookup both the call and `resolve` use, so they can never pick different copies of a package. Builtins
|
|
16
|
+
// (`fs`, `node:fs`, Bun's `bun:sqlite`) and paths resolve natively; a bare name the project does not have falls
|
|
17
|
+
// back to the managed environment, and a miss there reports the project's own not-found error.
|
|
18
|
+
const resolve = function resolve(specifier, options) {
|
|
19
|
+
const name = String(specifier);
|
|
20
|
+
const { fromCwd, fromPackages } = requirers();
|
|
21
|
+
if (isBuiltin(name) || isPathLike(name) || fromPackages === undefined || options?.paths !== undefined) {
|
|
22
|
+
return fromCwd.resolve(name, options);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return fromCwd.resolve(name, options);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error?.code !== "MODULE_NOT_FOUND") throw error;
|
|
28
|
+
try {
|
|
29
|
+
return fromPackages.resolve(name, options);
|
|
30
|
+
} catch (fallback) {
|
|
31
|
+
throw fallback?.code === "MODULE_NOT_FOUND" ? error : fallback;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
resolve.paths = (specifier) => {
|
|
36
|
+
const name = String(specifier);
|
|
37
|
+
const { fromCwd, fromPackages } = requirers();
|
|
38
|
+
const own = fromCwd.resolve.paths(name);
|
|
39
|
+
if (own === null || fromPackages === undefined || isPathLike(name)) return own;
|
|
40
|
+
return [...new Set([...own, ...(fromPackages.resolve.paths(name) ?? [])])];
|
|
41
|
+
};
|
|
42
|
+
// The call loads exactly what `resolve` names: a builtin id or an absolute file, through the runtime's loader.
|
|
43
|
+
const require = function require(specifier) {
|
|
44
|
+
return requirers().fromCwd(resolve(specifier));
|
|
45
|
+
};
|
|
46
|
+
require.resolve = resolve;
|
|
47
|
+
// One CommonJS cache per runtime: the cell's view is the loader's own, so deleting an entry forces a reload.
|
|
48
|
+
Object.defineProperty(require, "cache", { enumerable: true, get: () => createRequire(import.meta.url).cache });
|
|
49
|
+
return require;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { createRequire };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// allow: SIZE_OK — private runtime state and installed globals must stay in one worker module.
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { createCellRequire, createRequire } from "./worker-require.js";
|
|
2
4
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
5
|
import { dirname, isAbsolute, join, normalize, resolve, sep } from "node:path";
|
|
4
6
|
import { inspect } from "node:util";
|
|
@@ -31,6 +33,7 @@ export class JsWorkerRuntime {
|
|
|
31
33
|
#hooks = null;
|
|
32
34
|
#pendingDisplays = [];
|
|
33
35
|
#children = new Set();
|
|
36
|
+
#childrenStopping;
|
|
34
37
|
#shellWaits = new Set();
|
|
35
38
|
#onChildEvent;
|
|
36
39
|
#onShellWaitChange;
|
|
@@ -47,6 +50,7 @@ export class JsWorkerRuntime {
|
|
|
47
50
|
generation: options.kernelGeneration ?? 1,
|
|
48
51
|
hostToolNames: options.hostToolNames ?? [],
|
|
49
52
|
foreignLanguageNames: options.foreignLanguageNames ?? [],
|
|
53
|
+
disabled: options.kernelToolsDisabled === true,
|
|
50
54
|
});
|
|
51
55
|
this.#installGlobals();
|
|
52
56
|
}
|
|
@@ -65,30 +69,43 @@ export class JsWorkerRuntime {
|
|
|
65
69
|
try {
|
|
66
70
|
let prelude = "";
|
|
67
71
|
let cellCode = code;
|
|
72
|
+
let sourceName = cellId;
|
|
68
73
|
if (code.startsWith(PREPARED_CELL_PREFIX)) {
|
|
69
74
|
const prepared = JSON.parse(code.slice(PREPARED_CELL_PREFIX.length));
|
|
70
75
|
if (!isPlainObject(prepared) || typeof prepared.prelude !== "string" || typeof prepared.code !== "string") throw new Error("Invalid prepared JavaScript cell payload");
|
|
71
76
|
({ prelude, code: cellCode } = prepared);
|
|
77
|
+
if (typeof prepared.sourceFile === "string") sourceName = prepared.sourceFile;
|
|
72
78
|
}
|
|
73
79
|
if (prelude) indirectEval(prelude, PRELUDE_SOURCE_URL);
|
|
74
|
-
const
|
|
80
|
+
const shadowed = [];
|
|
81
|
+
const source = bindKernelBun(wrapUserCode(cellCode, shadowed));
|
|
82
|
+
if (shadowed.length > 0) this.#emitText("stderr", shadowNote(shadowed));
|
|
83
|
+
const value = await awaitMaybePromise(indirectEval(source, sourceName));
|
|
75
84
|
await this.#drainPendingDisplays();
|
|
76
85
|
return value;
|
|
77
86
|
} finally {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// spawned it is over, nothing will await it again, and it would be
|
|
81
|
-
// reparented to init. Retire it the way timeout and abort cleanup
|
|
82
|
-
// already do, unless the cell asked for a detached process.
|
|
83
|
-
await this.#terminateChildren();
|
|
84
|
-
this.#hooks = null;
|
|
87
|
+
// A released run finishing late must not clear the state of the cell running now.
|
|
88
|
+
if (this.#hooks === hooks) await this.release();
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
/** Ends the current run's ownership now: its displays, its hooks, and (unless detached) its child processes. */
|
|
93
|
+
async release() {
|
|
94
|
+
const hooks = this.#hooks;
|
|
95
|
+
this.#pendingDisplays = [];
|
|
96
|
+
// A child still running here has lost its only owner: the cell that
|
|
97
|
+
// spawned it is over, nothing will await it again, and it would be
|
|
98
|
+
// reparented to init. Retire it the way timeout and abort cleanup
|
|
99
|
+
// already do, unless the cell asked for a detached process.
|
|
100
|
+
await Promise.all([this.#childrenStopping, this.#terminateChildren()]);
|
|
101
|
+
this.#childrenStopping = undefined;
|
|
102
|
+
if (this.#hooks === hooks) this.#hooks = null;
|
|
103
|
+
}
|
|
104
|
+
|
|
88
105
|
interrupt() {
|
|
89
106
|
// The tree snapshot, SIGTERM, and SIGKILL escalation run on their own so
|
|
90
107
|
// the caller's interrupt latency stays that of the acknowledgement.
|
|
91
|
-
|
|
108
|
+
this.#childrenStopping = this.#terminateChildren();
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
#trackChild(child, spawnOptions) {
|
|
@@ -145,6 +162,17 @@ export class JsWorkerRuntime {
|
|
|
145
162
|
return isPlainObject(opts) && opts.handle === true ? handles.handle(value) : value;
|
|
146
163
|
};
|
|
147
164
|
globalThis.wait = async (list, options) => await handles.wait(list, options);
|
|
165
|
+
// The `%bun add` / `%npm add` installer as a call: same environment, receipt, cancellation and codes (senpi row 38).
|
|
166
|
+
globalThis.packages = Object.freeze({
|
|
167
|
+
install: async (manager, requirements, options = {}) => {
|
|
168
|
+
if (!isPlainObject(options)) throw new TypeError("packages.install(): options must be an object, e.g. { timeout: 120 }");
|
|
169
|
+
return await this.#callTool("__packages_install__", {
|
|
170
|
+
manager,
|
|
171
|
+
requirements,
|
|
172
|
+
...(options.timeout !== undefined ? { timeout: options.timeout } : {}),
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
});
|
|
148
176
|
globalThis.handle = value => handles.handle(value);
|
|
149
177
|
globalThis.tool = createToolNamespace(
|
|
150
178
|
(fn, metadata) => this.#tools.define(fn, metadata),
|
|
@@ -152,6 +180,8 @@ export class JsWorkerRuntime {
|
|
|
152
180
|
{ defined: () => this.#tools.defined(), undefine: (name) => this.#tools.undefine(name) },
|
|
153
181
|
);
|
|
154
182
|
globalThis.tools = globalThis.tool;
|
|
183
|
+
globalThis.require = createCellRequire(() => globalThis.__senpi_module_context__ ?? { cwdUrl: pathToFileURL(`${process.cwd()}/`).href });
|
|
184
|
+
globalThis.createRequire = createRequire;
|
|
155
185
|
const originalLog = console.log.bind(console);
|
|
156
186
|
const originalError = console.error.bind(console);
|
|
157
187
|
const originalStdoutWrite = process.stdout.write;
|
|
@@ -420,6 +450,12 @@ export class JsWorkerRuntime {
|
|
|
420
450
|
}
|
|
421
451
|
}
|
|
422
452
|
|
|
453
|
+
function shadowNote(names) {
|
|
454
|
+
const list = names.map((name) => `\`${name}\``).join(", ");
|
|
455
|
+
const restore = names.map((name) => `delete ${name}`).join("; ");
|
|
456
|
+
return `Note: ${list} ${names.length === 1 ? "shadows a kernel or platform global" : "shadow kernel or platform globals"} in your later cells; the kernel and imported libraries keep the original. \`${restore}\` restores ${names.length === 1 ? "it" : "them"}.\n`;
|
|
457
|
+
}
|
|
458
|
+
|
|
423
459
|
function isPlainObject(value) {
|
|
424
460
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
425
461
|
}
|
|
@@ -12,10 +12,6 @@ function protectedGlobals() {
|
|
|
12
12
|
return protectedGlobalNames;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
for (const name of bindings) {
|
|
18
|
-
if (names.has(name)) return name;
|
|
19
|
-
}
|
|
20
|
-
return undefined;
|
|
15
|
+
export function isKernelGlobal(name) {
|
|
16
|
+
return protectedGlobals().has(name);
|
|
21
17
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assertCellLive } from "./cell-run-context.js";
|
|
2
|
+
|
|
1
3
|
const SHELL_CONFIG_METHODS = ["env", "cwd", "nothrow", "throws"];
|
|
2
4
|
const SHELL_READ_METHODS = ["text", "json", "lines", "arrayBuffer", "bytes", "blob"];
|
|
3
5
|
// `true | ( … )` hands every command in the template an empty pipe as stdin. The worker thread shares
|
|
@@ -39,6 +41,7 @@ function isBunRuntime(bun) {
|
|
|
39
41
|
|
|
40
42
|
function capturedShell(originalShell, options) {
|
|
41
43
|
const shell = (strings, ...expressions) => {
|
|
44
|
+
assertCellLive();
|
|
42
45
|
if (!options.isActive()) return originalShell(strings, ...expressions);
|
|
43
46
|
const promise = originalShell(isolateStdin(strings), ...expressions);
|
|
44
47
|
return captureShellPromise(promise, options);
|
|
@@ -132,6 +135,7 @@ function outputText(value) {
|
|
|
132
135
|
// without an explicit env must get the worker's view pinned too (measured on Bun 1.4.0).
|
|
133
136
|
function capturedSpawnSync(originalSpawnSync, pinEnv) {
|
|
134
137
|
return (...args) => {
|
|
138
|
+
assertCellLive();
|
|
135
139
|
if (!pinEnv) return originalSpawnSync(...args);
|
|
136
140
|
const [first, second] = args;
|
|
137
141
|
if (Array.isArray(first)) {
|
|
@@ -148,6 +152,7 @@ function capturedSpawnSync(originalSpawnSync, pinEnv) {
|
|
|
148
152
|
|
|
149
153
|
function capturedSpawn(originalSpawn, options, pinEnv) {
|
|
150
154
|
return (...args) => {
|
|
155
|
+
assertCellLive();
|
|
151
156
|
if (!options.isActive()) return originalSpawn(...args);
|
|
152
157
|
const [first, second] = args;
|
|
153
158
|
let child;
|
|
@@ -4,6 +4,7 @@ import type { WorkerLike } from "./inline-worker.ts";
|
|
|
4
4
|
import { retireWorker, type WorkerRetirement } from "./interrupt-bounds.ts";
|
|
5
5
|
import type { JavaScriptKernelMode } from "./kernel-contract.ts";
|
|
6
6
|
import type { JavaScriptKernelOptions } from "./local-module-loader.ts";
|
|
7
|
+
import type { JavaScriptProcessWorker } from "./process-worker.ts";
|
|
7
8
|
import { KernelWebViewClients } from "./webview-host.ts";
|
|
8
9
|
import { WorkerStartupCancelledError } from "./worker-host.ts";
|
|
9
10
|
import { startWorkerWithInlineFallback } from "./worker-startup.ts";
|
|
@@ -19,6 +20,7 @@ export class WorkerSlot {
|
|
|
19
20
|
readonly #options: JavaScriptKernelOptions;
|
|
20
21
|
readonly #listeners: WorkerSlotListeners;
|
|
21
22
|
#worker: WorkerLike | null = null;
|
|
23
|
+
#lastProcessPid: number | undefined;
|
|
22
24
|
#webViews: KernelWebViewClients | null = null;
|
|
23
25
|
#mode: JavaScriptKernelMode = "worker";
|
|
24
26
|
#generation = 0;
|
|
@@ -34,6 +36,12 @@ export class WorkerSlot {
|
|
|
34
36
|
return this.#mode;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
get processPid(): number | undefined {
|
|
40
|
+
const worker = this.#worker;
|
|
41
|
+
if (worker?.mode === "process") return (worker as JavaScriptProcessWorker).pid;
|
|
42
|
+
return this.#lastProcessPid;
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
get present(): boolean {
|
|
38
46
|
return this.#worker !== null;
|
|
39
47
|
}
|
|
@@ -105,6 +113,7 @@ export class WorkerSlot {
|
|
|
105
113
|
#publish(worker: WorkerLike, generation: number): void {
|
|
106
114
|
if (!this.#listeners.isOpen() || generation !== this.#generation) throw new WorkerStartupCancelledError();
|
|
107
115
|
this.#worker = worker;
|
|
116
|
+
if (worker.mode === "process") this.#lastProcessPid = (worker as JavaScriptProcessWorker).pid;
|
|
108
117
|
const webViews = new KernelWebViewClients((message, transfer) => worker.postMessage(message, transfer));
|
|
109
118
|
this.#webViews = webViews;
|
|
110
119
|
worker.onMessage((message) => {
|
|
@@ -2,8 +2,10 @@ import { dirname, join } from "node:path";
|
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
3
|
import { type CodemodeRuntimeAssetEnvironment, requireCodemodeRuntimeAsset } from "../shared/runtime-asset.ts";
|
|
4
4
|
import { createInlineWorker, type WorkerLike } from "./inline-worker.ts";
|
|
5
|
+
import { retireWorker } from "./interrupt-bounds.ts";
|
|
5
6
|
import { resolveKernelToolNameSource } from "./kernel-contract.ts";
|
|
6
7
|
import { type JavaScriptKernelOptions, localBridgeConnection } from "./local-module-loader.ts";
|
|
8
|
+
import { resolveJsProcessEntryUrl, spawnProcessWorker } from "./process-worker.ts";
|
|
7
9
|
import { spawnNodeWorker, WorkerStartupCancelledError, waitForReady } from "./worker-host.ts";
|
|
8
10
|
|
|
9
11
|
export interface JavaScriptWorkerEntryUrlOptions extends CodemodeRuntimeAssetEnvironment {
|
|
@@ -15,6 +17,21 @@ export function resolveJsWorkerEntryUrl(options: JavaScriptWorkerEntryUrlOptions
|
|
|
15
17
|
return pathToFileURL(requireCodemodeRuntimeAsset(localPath, join("kernels", "js", "worker-entry.js"), options));
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* How long a process-mode kernel child gets to report ready. A child that a starved host never schedules would
|
|
22
|
+
* otherwise hold every queued cell forever: no cell's own timeout is armed until the kernel is up.
|
|
23
|
+
*/
|
|
24
|
+
export const PROCESS_STARTUP_DEADLINE_MS = 30_000;
|
|
25
|
+
|
|
26
|
+
export class ProcessKernelStartupTimeoutError extends Error {
|
|
27
|
+
constructor(deadlineMs: number) {
|
|
28
|
+
super(
|
|
29
|
+
`JavaScript kernel process did not become ready within ${deadlineMs / 1000}s; it was stopped, and the next cell starts a fresh one`,
|
|
30
|
+
);
|
|
31
|
+
this.name = "ProcessKernelStartupTimeoutError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
export interface WorkerStartupHooks {
|
|
19
36
|
readonly options: JavaScriptKernelOptions;
|
|
20
37
|
readonly kernelGeneration: number;
|
|
@@ -36,6 +53,13 @@ export async function startWorkerWithInlineFallback(hooks: WorkerStartupHooks, s
|
|
|
36
53
|
await worker.terminate();
|
|
37
54
|
throw new WorkerStartupCancelledError();
|
|
38
55
|
}
|
|
56
|
+
// Process mode never falls back: the user asked for isolation, so a failed start stays visible. The child that
|
|
57
|
+
// failed to start is stopped (bounded), so a stuck one is not left behind.
|
|
58
|
+
if (worker.mode === "process") {
|
|
59
|
+
hooks.retire(worker);
|
|
60
|
+
await retireWorker(worker);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
39
63
|
if (worker.mode === "inline") throw error;
|
|
40
64
|
hooks.retire(worker);
|
|
41
65
|
await worker.terminate();
|
|
@@ -47,6 +71,15 @@ export async function startWorkerWithInlineFallback(hooks: WorkerStartupHooks, s
|
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
function spawnWorker(options: JavaScriptKernelOptions): WorkerLike {
|
|
74
|
+
if (options.isolation === "process") {
|
|
75
|
+
const url = resolveJsProcessEntryUrl();
|
|
76
|
+
return spawnProcessWorker(url, {
|
|
77
|
+
cwd: options.cwd,
|
|
78
|
+
parallelPoolWidth: options.parallelPoolWidth,
|
|
79
|
+
...(options.processCommandPath === undefined ? {} : { searchPath: options.processCommandPath }),
|
|
80
|
+
...(options.processExecPath === undefined ? {} : { execPath: options.processExecPath }),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
50
83
|
try {
|
|
51
84
|
const url = options.workerEntryUrl ?? resolveJsWorkerEntryUrl();
|
|
52
85
|
return spawnNodeWorker(url, options.cwd, options.parallelPoolWidth);
|
|
@@ -57,7 +90,10 @@ function spawnWorker(options: JavaScriptKernelOptions): WorkerLike {
|
|
|
57
90
|
}
|
|
58
91
|
|
|
59
92
|
async function initializeWorker(worker: WorkerLike, hooks: WorkerStartupHooks, signal: AbortSignal): Promise<void> {
|
|
60
|
-
const ready =
|
|
93
|
+
const ready =
|
|
94
|
+
worker.mode === "process"
|
|
95
|
+
? withStartupDeadline(waitForReady(worker, signal), hooks.options)
|
|
96
|
+
: waitForReady(worker, signal);
|
|
61
97
|
const options = hooks.options;
|
|
62
98
|
worker.postMessage({
|
|
63
99
|
type: "init",
|
|
@@ -68,6 +104,18 @@ async function initializeWorker(worker: WorkerLike, hooks: WorkerStartupHooks, s
|
|
|
68
104
|
foreignLanguageNames: resolveKernelToolNameSource(options.foreignLanguageNames),
|
|
69
105
|
...(options.sessionEnv === undefined ? {} : { sessionEnv: options.sessionEnv }),
|
|
70
106
|
...(options.memory === undefined ? {} : { memory: options.memory }),
|
|
107
|
+
...(options.kernelToolsEnabled === false ? { kernelToolsDisabled: true } : {}),
|
|
71
108
|
});
|
|
72
109
|
await ready;
|
|
73
110
|
}
|
|
111
|
+
|
|
112
|
+
function withStartupDeadline(ready: Promise<void>, options: JavaScriptKernelOptions): Promise<void> {
|
|
113
|
+
const deadlineMs = options.processStartupDeadlineMs ?? PROCESS_STARTUP_DEADLINE_MS;
|
|
114
|
+
let timer: NodeJS.Timeout | undefined;
|
|
115
|
+
const expired = new Promise<never>((_resolve, reject) => {
|
|
116
|
+
timer = setTimeout(() => reject(new ProcessKernelStartupTimeoutError(deadlineMs)), deadlineMs);
|
|
117
|
+
});
|
|
118
|
+
// When the deadline wins, the stopped child's readiness promise rejects later; that outcome is already reported.
|
|
119
|
+
ready.catch(() => {});
|
|
120
|
+
return Promise.race([ready, expired]).finally(() => clearTimeout(timer));
|
|
121
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { onCellRelease } from "./cell-run-context.js";
|
|
1
2
|
import { DELIVER_EVENT, WebViewPortClient, webViewError } from "./worker-webview-client.js";
|
|
2
3
|
|
|
3
4
|
// Bun constructs "chrome"-backend WebViews only on the process main thread, and this kernel runs in
|
|
@@ -5,6 +6,7 @@ import { DELIVER_EVENT, WebViewPortClient, webViewError } from "./worker-webview
|
|
|
5
6
|
// `Bun` parameter bound to a stand-in whose `WebView` forwards Chrome-backed views to the
|
|
6
7
|
// main-thread service over a private port; every other `Bun.*` member is the real one.
|
|
7
8
|
const KERNEL_BUN = Symbol.for("senpi.kernel.bun");
|
|
9
|
+
const KERNEL_PROCESS_MODE = Symbol.for("senpi.kernel.processMode");
|
|
8
10
|
|
|
9
11
|
export function bindKernelBun(source) {
|
|
10
12
|
if (!globalThis[KERNEL_BUN]) return source;
|
|
@@ -17,6 +19,10 @@ export function installKernelWebView(requestPort) {
|
|
|
17
19
|
globalThis[KERNEL_BUN] = shadowBun(bun, createKernelWebView(bun.WebView, requestPort));
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
export function markKernelProcessMode() {
|
|
23
|
+
globalThis[KERNEL_PROCESS_MODE] = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
function shadowBun(real, WebView) {
|
|
21
27
|
return new Proxy(
|
|
22
28
|
{},
|
|
@@ -35,7 +41,10 @@ function shadowBun(real, WebView) {
|
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
// The macOS default (WebKit) stays a native worker view: its host process serves any thread.
|
|
44
|
+
// A process-mode kernel already runs its cells on the child's main thread, so the native
|
|
45
|
+
// WebView constructs there directly and no MessagePort proxy is ever needed.
|
|
38
46
|
function needsMainThread(options) {
|
|
47
|
+
if (globalThis[KERNEL_PROCESS_MODE]) return false;
|
|
39
48
|
const backend = options?.backend;
|
|
40
49
|
if (backend === undefined) return process.platform !== "darwin";
|
|
41
50
|
return backend === "chrome" || (typeof backend === "object" && backend !== null && backend.type === "chrome");
|
|
@@ -82,13 +91,16 @@ function createKernelWebView(NativeWebView, requestPort) {
|
|
|
82
91
|
#console;
|
|
83
92
|
#ready;
|
|
84
93
|
#closed = false;
|
|
94
|
+
#forgetRelease;
|
|
85
95
|
onNavigated = null;
|
|
86
96
|
onNavigationFailed = null;
|
|
87
97
|
|
|
88
98
|
constructor(options = {}) {
|
|
89
99
|
if (!needsMainThread(options)) {
|
|
90
100
|
const view = new NativeWebView(options);
|
|
91
|
-
|
|
101
|
+
const ref = new WeakRef(view);
|
|
102
|
+
natives.add(ref);
|
|
103
|
+
onCellRelease(() => ref.deref()?.close());
|
|
92
104
|
return view;
|
|
93
105
|
}
|
|
94
106
|
super();
|
|
@@ -96,6 +108,7 @@ function createKernelWebView(NativeWebView, requestPort) {
|
|
|
96
108
|
this.#console = capture;
|
|
97
109
|
proxied.add(this);
|
|
98
110
|
live.add(this);
|
|
111
|
+
this.#forgetRelease = onCellRelease(() => this.close());
|
|
99
112
|
const viewId = this.#viewId;
|
|
100
113
|
this.#ready = connect().then(async client => {
|
|
101
114
|
client.register(viewId, this);
|
|
@@ -149,6 +162,7 @@ function createKernelWebView(NativeWebView, requestPort) {
|
|
|
149
162
|
close() {
|
|
150
163
|
if (this.#closed) return;
|
|
151
164
|
this.#closed = true;
|
|
165
|
+
this.#forgetRelease?.();
|
|
152
166
|
live.delete(this);
|
|
153
167
|
const viewId = this.#viewId;
|
|
154
168
|
this.#ready.then(
|
|
@@ -4,8 +4,11 @@ import { kernelToolError } from "./kernel-tools-errors.js";
|
|
|
4
4
|
// Only host-tool sugar: no admission, worker, or queue state belongs to the kernel.
|
|
5
5
|
export async function createWorkpool(callTool, agent, name, options = {}) {
|
|
6
6
|
if (inKernelToolInvoke()) throw kernelToolError("kernel_tool_recursion", "kernel tools may not invoke workpool()");
|
|
7
|
-
if (options === null || typeof options !== "object" || Array.isArray(options) || Object.keys(options).some(key => key !== "mode")) {
|
|
8
|
-
throw new TypeError("workpool() options only accept mode");
|
|
7
|
+
if (options === null || typeof options !== "object" || Array.isArray(options) || Object.keys(options).some(key => key !== "mode" && key !== "tools")) {
|
|
8
|
+
throw new TypeError("workpool() options only accept mode and tools");
|
|
9
|
+
}
|
|
10
|
+
if (options.tools !== undefined && (!Array.isArray(options.tools) || !options.tools.every(name => typeof name === "string"))) {
|
|
11
|
+
throw workpoolError("invalid_tools", `workpool() tools takes an array of tool names; got ${Array.isArray(options.tools) ? "an array with a non-string" : typeof options.tools}`);
|
|
9
12
|
}
|
|
10
13
|
const call = async args => {
|
|
11
14
|
try {
|
|
@@ -1,42 +1,19 @@
|
|
|
1
1
|
import type { KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
2
|
import type { HostCellExecutor } from "../../tool/types.ts";
|
|
3
|
-
|
|
4
|
-
type HostCellOutcome = Awaited<ReturnType<HostCellExecutor>>;
|
|
5
|
-
|
|
3
|
+
import { runHostCell as runSharedHostCell } from "../shared/host-cell.ts";
|
|
6
4
|
import type { PendingRun, ResultMessage } from "./kernel-contract.ts";
|
|
7
|
-
import { failedPythonResult } from "./transport.ts";
|
|
8
5
|
|
|
9
6
|
export function runHostCell(
|
|
10
7
|
pending: PendingRun,
|
|
11
8
|
host: HostCellExecutor,
|
|
12
9
|
io: { readonly emit: (message: KernelToHostMessage) => void; readonly settle: (result: ResultMessage) => void },
|
|
13
10
|
): void {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const emit = (message: KernelToHostMessage): void => {
|
|
20
|
-
if (!finished && !abort.signal.aborted) io.emit(message);
|
|
21
|
-
};
|
|
22
|
-
// Calling the executor inside a promise keeps a synchronous throw in this cell instead of failing the queue.
|
|
23
|
-
const done = new Promise<HostCellOutcome>((resolve) => resolve(host({ signal: abort.signal, emit }))).then(
|
|
24
|
-
(outcome): ResultMessage =>
|
|
25
|
-
outcome.ok
|
|
26
|
-
? {
|
|
27
|
-
type: "result",
|
|
28
|
-
cellId,
|
|
29
|
-
ok: true,
|
|
30
|
-
durationMs: 0,
|
|
31
|
-
...(outcome.valueRepr === undefined ? {} : { valueRepr: outcome.valueRepr }),
|
|
32
|
-
}
|
|
33
|
-
: { type: "result", cellId, ok: false, error: outcome.error, durationMs: 0 },
|
|
34
|
-
(error: unknown): ResultMessage =>
|
|
35
|
-
failedPythonResult(cellId, error instanceof Error ? error.message : String(error)),
|
|
36
|
-
);
|
|
37
|
-
pending.hostDone = done;
|
|
38
|
-
void done.then((result) => {
|
|
39
|
-
finished = true;
|
|
40
|
-
if (!abort.signal.aborted) io.settle(result);
|
|
11
|
+
const run = runSharedHostCell(pending.input.cellId, host, {
|
|
12
|
+
emit: io.emit,
|
|
13
|
+
settle: io.settle,
|
|
14
|
+
durationMs: () => 0,
|
|
15
|
+
settleAfterAbort: false,
|
|
41
16
|
});
|
|
17
|
+
pending.hostAbort = run.abort;
|
|
18
|
+
pending.hostDone = run.done;
|
|
42
19
|
}
|
package/src/kernels/py/kernel.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
KernelToolsInvokeOptions,
|
|
6
6
|
KernelToolsInvokeRequest,
|
|
7
7
|
} from "../js/kernel-tools-types.ts";
|
|
8
|
+
import { inputAtStart } from "../shared/cell-source-at-start.ts";
|
|
8
9
|
import { describeExit } from "../shared/kernel-death.ts";
|
|
9
10
|
import { KernelMemoryHost } from "../shared/kernel-memory-host.ts";
|
|
10
11
|
import { KernelPreludeTracker } from "../shared/kernel-prelude-plan.ts";
|
|
@@ -243,10 +244,15 @@ export class PythonKernel {
|
|
|
243
244
|
});
|
|
244
245
|
return;
|
|
245
246
|
}
|
|
247
|
+
const input = inputAtStart(pending.input);
|
|
248
|
+
if ("refused" in input) {
|
|
249
|
+
this.#settleRun(pending, failedPythonResult(pending.input.cellId, input.refused));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
246
252
|
try {
|
|
247
253
|
this.#transport?.run({
|
|
248
|
-
...
|
|
249
|
-
preludePlan: this.#preludes.plan(
|
|
254
|
+
...input,
|
|
255
|
+
preludePlan: this.#preludes.plan(input.kernelPreludes ?? []),
|
|
250
256
|
});
|
|
251
257
|
} catch (error) {
|
|
252
258
|
const failure = error instanceof Error ? error : new Error(String(error));
|