@nimbus-sh/worker 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/facets/compose.d.ts +3 -0
- package/dist/facets/compose.d.ts.map +1 -1
- package/dist/facets/compose.js +1 -1
- package/dist/facets/manager.d.ts +26 -15
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +154 -112
- package/dist/facets/real-vite-hmr.d.ts +3 -1
- package/dist/facets/real-vite-hmr.d.ts.map +1 -1
- package/dist/facets/real-vite-hmr.js +18 -8
- package/dist/facets/ws-terminal.d.ts +9 -31
- package/dist/facets/ws-terminal.d.ts.map +1 -1
- package/dist/facets/ws-terminal.js +58 -51
- package/dist/hosted/commands.d.ts +12 -0
- package/dist/hosted/commands.d.ts.map +1 -0
- package/dist/hosted/commands.js +1647 -0
- package/dist/hosted/runtime.d.ts +135 -0
- package/dist/hosted/runtime.d.ts.map +1 -0
- package/dist/hosted/runtime.js +344 -0
- package/dist/hosted/services.d.ts +44 -0
- package/dist/hosted/services.d.ts.map +1 -0
- package/dist/hosted/services.js +522 -0
- package/dist/loaders/generated-workers.d.ts +1 -1
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +2 -2
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/runtime/bash-repl.d.ts +6 -4
- package/dist/runtime/bash-repl.d.ts.map +1 -1
- package/dist/runtime/bash-repl.js +46 -7
- package/dist/runtime/bun-runner.js +1 -1
- package/dist/runtime/facet-loader-host.d.ts.map +1 -1
- package/dist/runtime/facet-loader-host.js +1 -3
- package/dist/runtime/node-runner.d.ts +1 -1
- package/dist/runtime/node-runner.d.ts.map +1 -1
- package/dist/runtime/node-runner.js +2 -2
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +14 -1
- package/dist/runtime/opencode-facet-runner.js +14 -14
- package/dist/runtime/package-manager.d.ts +35 -122
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +61 -596
- package/dist/runtime/process-logs-api.d.ts +1 -1
- package/dist/runtime/process-logs-api.d.ts.map +1 -1
- package/dist/runtime/process-logs-api.js +1 -1
- package/dist/runtime/python-repl.d.ts +6 -4
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +92 -67
- package/dist/runtime/repl-session.d.ts +9 -47
- package/dist/runtime/repl-session.d.ts.map +1 -1
- package/dist/runtime/repl-session.js +105 -105
- package/dist/runtime/ruby-repl.d.ts +3 -2
- package/dist/runtime/ruby-repl.d.ts.map +1 -1
- package/dist/runtime/ruby-repl.js +7 -6
- package/dist/runtime/runtime-catalog.d.ts +13 -0
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +88 -0
- package/dist/runtime/wasi/preamble.d.ts.map +1 -1
- package/dist/runtime/wasi/preamble.js +74 -56
- package/dist/runtime-catalog.generated.js +1 -1
- package/dist/session/agent.d.ts +1 -21
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/ai.d.ts +5 -1
- package/dist/session/ai.d.ts.map +1 -1
- package/dist/session/ai.js +7 -0
- package/dist/session/hibernation.d.ts +2 -1
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +7 -4
- package/dist/session/init.d.ts +1 -36
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +18 -1773
- package/dist/session/loopback.d.ts +2 -29
- package/dist/session/loopback.d.ts.map +1 -1
- package/dist/session/loopback.js +3 -27
- package/dist/session/nimbus-session.d.ts +28 -34
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +78 -518
- package/dist/session/npm-install-port.d.ts +3 -1
- package/dist/session/npm-install-port.d.ts.map +1 -1
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +11 -1
- package/dist/session/programmatic.d.ts +5 -38
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +17 -49
- package/dist/session/rpc.d.ts +3 -43
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +8 -67
- package/dist/session/supervisor-op.d.ts +10 -5
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +21 -48
- package/dist/session/supervisor-rpc.d.ts +33 -16
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +49 -4
- package/dist/vfs/facet-resident-store.d.ts.map +1 -1
- package/dist/vfs/facet-resident-store.js +4 -2
- package/dist/workspace-host.d.ts +7 -0
- package/dist/workspace-host.d.ts.map +1 -0
- package/dist/workspace-host.js +5 -0
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +1 -0
- package/package.json +17 -12
- package/public/_assets/runtime/{node-shims-8f8c64edd2a1f0f5.js → node-shims-acaa037e6d26ec0d.js} +14 -1
- package/scripts/bundle-runtime.mjs +35 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-logs-api.d.ts","sourceRoot":"","sources":["../../src/runtime/process-logs-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uDAAuD,CAAC;AAEtG;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,wBAAwB,CAAC;IACpC;;;;OAIG;IACH,GAAG,EAAE;QACH,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"process-logs-api.d.ts","sourceRoot":"","sources":["../../src/runtime/process-logs-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uDAAuD,CAAC;AAEtG;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,wBAAwB,CAAC;IACpC;;;;OAIG;IACH,GAAG,EAAE;QACH,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,SAAS,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GAAG,IAAI,EAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAON;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,iBAAiB,GACtB,QAAQ,CA8EV;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,wBAAwB,EACnC,GAAG,EAAE;IAAE,aAAa,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,CAAA;CAAE,GACjD,IAAI,CA8CN;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,wBAAwB,GAClC,QAAQ,CAsCV;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK7D"}
|
|
@@ -30,7 +30,7 @@ export function notifyTerminalEvent(terminal, event) {
|
|
|
30
30
|
if (!terminal)
|
|
31
31
|
return;
|
|
32
32
|
try {
|
|
33
|
-
terminal.ws
|
|
33
|
+
terminal.ws?.send(JSON.stringify(event));
|
|
34
34
|
}
|
|
35
35
|
catch {
|
|
36
36
|
/* socket closed or congested — dropping is the right behavior here */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Shell } from '@nimbus-sh/core/substrate/lifo/shell/Shell.js';
|
|
1
2
|
/**
|
|
2
3
|
* python-repl.ts — the interactive `python` prompt.
|
|
3
4
|
*
|
|
@@ -19,13 +20,14 @@
|
|
|
19
20
|
* so `1 + 1` prints `2` and `x = 1` prints nothing, with no wrapper of ours
|
|
20
21
|
* deciding what counts as a result.
|
|
21
22
|
*/
|
|
22
|
-
import type { SqliteVFS } from '@nimbus-sh/core/vfs/sqlite-vfs.js';
|
|
23
23
|
import type { FacetManager } from '../facets/manager.js';
|
|
24
24
|
import type { WebSocketTerminal } from '../facets/ws-terminal.js';
|
|
25
25
|
import type { RuntimeManifest } from '@nimbus-sh/core/runtime/runtime-manifest.js';
|
|
26
|
+
import { type NimbusFilesystemAuthority } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
26
27
|
export interface PythonReplDeps {
|
|
27
28
|
facetMgr: FacetManager;
|
|
28
|
-
|
|
29
|
+
/** Owns the installed interpreter blobs the prompt is booted from. */
|
|
30
|
+
authority: NimbusFilesystemAuthority;
|
|
29
31
|
terminal: WebSocketTerminal;
|
|
30
32
|
/** Per-user-VFS install dir, e.g. 'home/user/.nimbus/runtimes/cpython/3.13.14'. */
|
|
31
33
|
installRoot: string;
|
|
@@ -40,7 +42,7 @@ export interface PythonReplDeps {
|
|
|
40
42
|
* ReplSession lets it drain that queue on attach, which is the difference
|
|
41
43
|
* between the pasted tail arriving and the prompt hanging.
|
|
42
44
|
*/
|
|
43
|
-
shell?:
|
|
45
|
+
shell?: Pick<Shell, 'takeQueuedInput'>;
|
|
44
46
|
/**
|
|
45
47
|
* The invoking process's pid.
|
|
46
48
|
*
|
|
@@ -57,5 +59,5 @@ export declare function runPythonRepl(deps: PythonReplDeps): Promise<number>;
|
|
|
57
59
|
* Pay the interpreter's boot before the user asks for a prompt. Pushing empty
|
|
58
60
|
* source compiles to a no-op, so the only thing it does is bring the facet up.
|
|
59
61
|
*/
|
|
60
|
-
export declare function warmPythonRepl(deps: Pick<PythonReplDeps, 'facetMgr' | '
|
|
62
|
+
export declare function warmPythonRepl(deps: Pick<PythonReplDeps, 'facetMgr' | 'authority' | 'installRoot' | 'manifest'>): Promise<void>;
|
|
61
63
|
//# sourceMappingURL=python-repl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"python-repl.d.ts","sourceRoot":"","sources":["../../src/runtime/python-repl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"python-repl.d.ts","sourceRoot":"","sources":["../../src/runtime/python-repl.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+CAA+C,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAMnF,OAAO,EAAe,KAAK,yBAAyB,EAAE,MAAM,yCAAyC,CAAC;AActG,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,YAAY,CAAC;IACvB,sEAAsE;IACtE,SAAS,EAAE,yBAAyB,CAAC;IACrC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA2SD,wBAAsB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAIzE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC,GAChF,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -1,24 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* it, so definitions, imports and open files survive between prompts. That is
|
|
6
|
-
* the whole reason the interpreter is built as a WASI reactor: a command
|
|
7
|
-
* module's _start runs once.
|
|
8
|
-
*
|
|
9
|
-
* Incompleteness is decided by `codeop.compile_command`, which is what the real
|
|
10
|
-
* Python REPL uses — it returns None for source that is syntactically fine so
|
|
11
|
-
* far but unfinished (an open bracket, a `def` with no body yet), raises
|
|
12
|
-
* SyntaxError for source that can never complete, and otherwise hands back a
|
|
13
|
-
* code object. That distinction is not something to re-derive from error
|
|
14
|
-
* strings; the previous Pyodide implementation asked PyodideConsole for it,
|
|
15
|
-
* which is the same idea reached through a Pyodide-only object.
|
|
16
|
-
*
|
|
17
|
-
* Compiling in 'single' mode also gets the echo right for free: an expression
|
|
18
|
-
* statement goes through sys.displayhook exactly as it does at a real prompt,
|
|
19
|
-
* so `1 + 1` prints `2` and `x = 1` prints nothing, with no wrapper of ours
|
|
20
|
-
* deciding what counts as a result.
|
|
21
|
-
*/
|
|
1
|
+
import { IsolatePool } from '@nimbus-sh/fabric/isolate-pool.js';
|
|
2
|
+
import { manifestVfs } from '@nimbus-sh/core/runtime/vfs-manifest.js';
|
|
3
|
+
import { withHostFilesystem } from '@nimbus-sh/core/shell/execution-fs.js';
|
|
4
|
+
import { z } from 'zod/v4';
|
|
22
5
|
import { ReplSession } from './repl-session.js';
|
|
23
6
|
import { sessionUsesSciVariant } from '@nimbus-sh/core/runtime/python-pip.js';
|
|
24
7
|
import { buildCPythonPreamble } from '@nimbus-sh/core/runtime/cpython-runner.js';
|
|
@@ -35,6 +18,8 @@ const INCOMPLETE_MARKER = '__NIMBUS_PY_INCOMPLETE__';
|
|
|
35
18
|
* ordinary output and never leaves.
|
|
36
19
|
*/
|
|
37
20
|
const EXIT_MARKER = '__NIMBUS_PY_EXIT__';
|
|
21
|
+
const PythonFacetResult = z.object({ stdout: z.string(), stderr: z.string(), exitCode: z.number().int(), error: z.string().optional() });
|
|
22
|
+
const PythonFacetFailure = z.object({ __nimbusFacetError: z.string() });
|
|
38
23
|
/** Where cpython-runner's catalog spec stages the interpreter. */
|
|
39
24
|
const CPYTHON_WASM_REL = 'share/cpython/python.wasm';
|
|
40
25
|
const CPYTHON_SCI_WASM_REL = 'share/cpython/python-sci.wasm';
|
|
@@ -102,13 +87,25 @@ class PythonReplAdapter {
|
|
|
102
87
|
return ('Python 3.13.14 (CPython, wasm32-wasi, Nimbus runtime)\r\n' +
|
|
103
88
|
'Type "exit()" or press Ctrl-D to exit.\r\n');
|
|
104
89
|
}
|
|
105
|
-
|
|
90
|
+
push(source) {
|
|
91
|
+
const controller = new AbortController();
|
|
92
|
+
const done = this.evaluate(source, controller.signal);
|
|
93
|
+
const active = { controller, done };
|
|
94
|
+
this.active = active;
|
|
95
|
+
const clear = () => { if (this.active === active)
|
|
96
|
+
this.active = null; };
|
|
97
|
+
void done.then(clear, clear);
|
|
98
|
+
return done;
|
|
99
|
+
}
|
|
100
|
+
async evaluate(source, signal) {
|
|
106
101
|
const trimmed = source.trim();
|
|
107
102
|
if (trimmed === 'exit' || trimmed === 'quit') {
|
|
108
103
|
return { kind: 'output', stdout: '', stderr: 'Use exit() or Ctrl-D to exit\n' };
|
|
109
104
|
}
|
|
110
105
|
try {
|
|
106
|
+
signal.throwIfAborted();
|
|
111
107
|
await this.ensurePool();
|
|
108
|
+
signal.throwIfAborted();
|
|
112
109
|
}
|
|
113
110
|
catch (e) {
|
|
114
111
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -116,7 +113,7 @@ class PythonReplAdapter {
|
|
|
116
113
|
}
|
|
117
114
|
let result;
|
|
118
115
|
try {
|
|
119
|
-
result = await this.submit(buildReplDriver(source));
|
|
116
|
+
result = await this.submit(buildReplDriver(source), signal);
|
|
120
117
|
}
|
|
121
118
|
catch (e) {
|
|
122
119
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -149,48 +146,53 @@ class PythonReplAdapter {
|
|
|
149
146
|
}
|
|
150
147
|
return { kind: 'output', stdout: result.stdout, stderr: result.stderr };
|
|
151
148
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
close() { return this.interrupt(); }
|
|
150
|
+
resetPool() {
|
|
151
|
+
const pool = this.pool;
|
|
152
|
+
this.pool = null;
|
|
153
|
+
this.wasmBytes = null;
|
|
154
|
+
this.fsSnapshot = null;
|
|
155
|
+
pool?.dispose();
|
|
156
|
+
}
|
|
157
|
+
async interrupt() {
|
|
158
|
+
const active = this.active;
|
|
159
|
+
active?.controller.abort();
|
|
160
|
+
try {
|
|
161
|
+
await active?.done;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
this.resetPool();
|
|
159
165
|
}
|
|
160
166
|
}
|
|
161
167
|
async ensurePool() {
|
|
162
|
-
|
|
168
|
+
await withHostFilesystem(this.deps.authority, CRED_KERNEL, (vfs) => this.ensurePoolFrom(vfs));
|
|
169
|
+
}
|
|
170
|
+
async ensurePoolFrom(vfs) {
|
|
163
171
|
const sciPath = `${this.deps.installRoot}/${CPYTHON_SCI_WASM_REL}`;
|
|
164
|
-
const wantsSci = sessionUsesSciVariant(
|
|
172
|
+
const wantsSci = (await sessionUsesSciVariant(vfs)) && (await vfs.exists(sciPath));
|
|
165
173
|
// A prompt that was open before `pip install numpy` is holding the
|
|
166
174
|
// interpreter that does not have it. Dropping the pool rebuilds on the next
|
|
167
175
|
// statement, which is the facet restart EXTENSIONS.md says this costs.
|
|
168
176
|
if (this.pool && this.poolUsesSci !== wantsSci) {
|
|
169
|
-
|
|
170
|
-
this.pool.dispose?.();
|
|
171
|
-
}
|
|
172
|
-
catch { /* fail-soft: it is being replaced */ }
|
|
173
|
-
this.pool = null;
|
|
177
|
+
this.resetPool();
|
|
174
178
|
}
|
|
175
179
|
if (this.pool)
|
|
176
180
|
return;
|
|
177
181
|
this.poolUsesSci = wantsSci;
|
|
178
182
|
const { installRoot, facetMgr } = this.deps;
|
|
179
|
-
const vfs = vfsForVariant;
|
|
180
183
|
const wasmPath = wantsSci ? sciPath : `${installRoot}/${CPYTHON_WASM_REL}`;
|
|
181
184
|
const stdlibPath = `${installRoot}/${CPYTHON_STDLIB_REL}`;
|
|
182
|
-
if (!vfs.exists(wasmPath)) {
|
|
185
|
+
if (!(await vfs.exists(wasmPath))) {
|
|
183
186
|
throw new Error(`python.wasm missing at ${wasmPath} (run 'nimbus install python')`);
|
|
184
187
|
}
|
|
185
|
-
if (!vfs.exists(stdlibPath)) {
|
|
188
|
+
if (!(await vfs.exists(stdlibPath))) {
|
|
186
189
|
throw new Error(`python313.zip missing at ${stdlibPath} (run 'nimbus install python')`);
|
|
187
190
|
}
|
|
188
|
-
this.wasmBytes = toArrayBuffer(vfs.readFile(wasmPath));
|
|
189
|
-
const { manifestVfs } = await import('@nimbus-sh/core/runtime/vfs-manifest.js');
|
|
191
|
+
this.wasmBytes = toArrayBuffer(await vfs.readFile(wasmPath));
|
|
190
192
|
// The install root is the Python prefix, so the manifest covers lib/ and
|
|
191
193
|
// etc/ as they are — nothing is aliased into a path the supervisor could
|
|
192
194
|
// not serve.
|
|
193
|
-
const built = manifestVfs(vfs, 'home/user', { extraRoots: [installRoot.replace(/^\/+/, '')] });
|
|
195
|
+
const built = await manifestVfs(vfs.authority, CRED_KERNEL, 'home/user', { extraRoots: [installRoot.replace(/^\/+/, '')] });
|
|
194
196
|
if ('error' in built)
|
|
195
197
|
throw new Error(built.error);
|
|
196
198
|
// Same workaround as cpython-runner, and it belongs to the same open
|
|
@@ -198,7 +200,7 @@ class PythonReplAdapter {
|
|
|
198
200
|
// demand-load, though the transport delivers it byte-identically. Seeding
|
|
199
201
|
// it by value is what makes the interpreter start. Remove both together.
|
|
200
202
|
const snapshot = built.snapshot;
|
|
201
|
-
const zipBytes = vfs.readFile(stdlibPath);
|
|
203
|
+
const zipBytes = await vfs.readFile(stdlibPath);
|
|
202
204
|
let bin = '';
|
|
203
205
|
const CH = 32768;
|
|
204
206
|
for (let i = 0; i < zipBytes.length; i += CH) {
|
|
@@ -207,7 +209,6 @@ class PythonReplAdapter {
|
|
|
207
209
|
snapshot.files[stdlibPath.replace(/^\/+/, '')] = btoa(bin);
|
|
208
210
|
this.fsSnapshot = snapshot;
|
|
209
211
|
this.pythonHome = `/${installRoot.replace(/^\/+/, '')}`;
|
|
210
|
-
const { IsolatePool } = await import('@nimbus-sh/fabric/isolate-pool.js');
|
|
211
212
|
const host = getFacetManagerLoaderHost(facetMgr);
|
|
212
213
|
// A prompt where `open(path, "w")` silently does nothing is worse than one
|
|
213
214
|
// that refuses to start, so the pid decides which pool this is. Written as
|
|
@@ -219,43 +220,65 @@ class PythonReplAdapter {
|
|
|
219
220
|
// Distinct from cpython-runner's tag: a REPL facet holds a live
|
|
220
221
|
// interpreter and must never be handed a one-shot invocation.
|
|
221
222
|
tag: wantsSci ? 'python-repl:sci' : 'python-repl',
|
|
223
|
+
scope: crypto.randomUUID(),
|
|
222
224
|
concurrency: 1,
|
|
223
225
|
preamble: buildCPythonPreamble(),
|
|
224
226
|
wasmModules: { 'python.wasm': this.wasmBytes },
|
|
225
227
|
};
|
|
226
228
|
const pid = this.deps.pid;
|
|
227
|
-
this.pool =
|
|
229
|
+
this.pool = typeof pid === 'number' && pid > 0
|
|
228
230
|
? new IsolatePool(host.env, host.ctx, { ...base, supervisorPid: pid })
|
|
229
231
|
// The install-time warm-up has no invoking process. It boots the
|
|
230
232
|
// interpreter and never touches a file, so it asks for no supervisor
|
|
231
233
|
// rather than binding one it cannot authenticate to.
|
|
232
|
-
: new IsolatePool(host.env, host.ctx, { ...base, omitSupervisor: true })
|
|
234
|
+
: new IsolatePool(host.env, host.ctx, { ...base, omitSupervisor: true });
|
|
233
235
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
236
|
+
active = null;
|
|
237
|
+
async submit(userCode, signal) {
|
|
238
|
+
const pool = this.pool;
|
|
239
|
+
if (!pool || !this.fsSnapshot)
|
|
240
|
+
throw new Error('Python REPL is not initialized');
|
|
241
|
+
const response = await pool.submitRequest(pythonReplStepRequestFn, new Request('https://facet.internal/python-repl-step', {
|
|
242
|
+
method: 'POST',
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
userCode,
|
|
245
|
+
pythonHome: this.pythonHome,
|
|
246
|
+
pyArgv: ['python'],
|
|
247
|
+
userEnv: { HOME: '/home/user', PYTHONUNBUFFERED: '1' },
|
|
248
|
+
progName: 'python',
|
|
249
|
+
cwd: '/home/user',
|
|
250
|
+
fsSnapshot: this.fsSnapshot,
|
|
251
|
+
}),
|
|
252
|
+
signal,
|
|
253
|
+
}), { timeoutMs: 60_000 });
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
const failure = PythonFacetFailure.parse(await response.json());
|
|
256
|
+
throw new Error(failure.__nimbusFacetError);
|
|
257
|
+
}
|
|
258
|
+
return PythonFacetResult.parse(await response.json());
|
|
245
259
|
}
|
|
246
260
|
}
|
|
247
261
|
/**
|
|
248
|
-
* Facet-side
|
|
249
|
-
*
|
|
250
|
-
*
|
|
262
|
+
* Facet-side, request-shaped: serialized with fn.toString() into the
|
|
263
|
+
* pool's fetch entrypoint, so it captures nothing and names no import —
|
|
264
|
+
* __cpythonReplRun is put on globalThis by the preamble, and unlike
|
|
265
|
+
* __cpythonRun it keeps its interpreter between calls. The request body
|
|
266
|
+
* is the step payload the adapter JSON-encodes; the response is the
|
|
267
|
+
* step result. Request transport because it is the pool's only
|
|
268
|
+
* cancellable dispatch: Ctrl-C aborts the request, workerd stops the
|
|
269
|
+
* interpreter at its suspension point.
|
|
251
270
|
*/
|
|
252
|
-
async function
|
|
271
|
+
async function pythonReplStepRequestFn(request, facetEnv) {
|
|
272
|
+
const args = await request.json();
|
|
273
|
+
if (typeof args !== 'object' || args === null || !('userCode' in args) || typeof args.userCode !== 'string') {
|
|
274
|
+
throw new Error('Python REPL request must contain userCode');
|
|
275
|
+
}
|
|
253
276
|
const run = Reflect.get(globalThis, '__cpythonReplRun');
|
|
254
277
|
if (typeof run !== 'function') {
|
|
255
|
-
return {
|
|
278
|
+
return Response.json({
|
|
256
279
|
stdout: '', stderr: '', exitCode: 127,
|
|
257
280
|
error: 'cpython preamble missing: __cpythonReplRun not in scope',
|
|
258
|
-
};
|
|
281
|
+
});
|
|
259
282
|
}
|
|
260
283
|
const adopt = Reflect.get(globalThis, '__wasiAdoptSupervisor');
|
|
261
284
|
const drain = Reflect.get(globalThis, '__wasiDrainPersist');
|
|
@@ -266,13 +289,15 @@ async function pythonReplStepFacetFn(args, facetEnv) {
|
|
|
266
289
|
// filesystem it could read.
|
|
267
290
|
if (supervisor)
|
|
268
291
|
Reflect.set(globalThis, '__nimbusPySupervisor', supervisor);
|
|
269
|
-
adopt
|
|
292
|
+
if (typeof adopt === 'function')
|
|
293
|
+
Reflect.apply(adopt, undefined, [supervisor ?? null]);
|
|
270
294
|
try {
|
|
271
|
-
return await run(args);
|
|
295
|
+
return Response.json(await run(args));
|
|
272
296
|
}
|
|
273
297
|
finally {
|
|
274
298
|
// A line that wrote a file and then raised still wrote the file.
|
|
275
|
-
|
|
299
|
+
if (typeof drain === 'function')
|
|
300
|
+
await drain();
|
|
276
301
|
}
|
|
277
302
|
}
|
|
278
303
|
export async function runPythonRepl(deps) {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* interface declared below.
|
|
13
13
|
*/
|
|
14
14
|
import type { WebSocketTerminal } from '../facets/ws-terminal.js';
|
|
15
|
+
import type { Shell } from '@nimbus-sh/core/substrate/lifo/shell/Shell.js';
|
|
15
16
|
/**
|
|
16
17
|
* Per-runtime adapter contract. The shell-side ReplSession orchestrates
|
|
17
18
|
* input handling; the adapter wraps the actual runtime invocation.
|
|
@@ -30,6 +31,8 @@ export interface ReplAdapter {
|
|
|
30
31
|
/** Called once on session close (exit() / Ctrl-D / shell teardown).
|
|
31
32
|
* Should free any cached pool / isolate. Idempotent. */
|
|
32
33
|
close(): Promise<void>;
|
|
34
|
+
/** Abort and settle the active push, resetting its interpreter before resolving. */
|
|
35
|
+
interrupt?(): void | Promise<void>;
|
|
33
36
|
/** Primary prompt (typically '>>> '). */
|
|
34
37
|
ps1: string;
|
|
35
38
|
/** Continuation prompt (typically '... '). */
|
|
@@ -50,45 +53,10 @@ export type ReplPushResult = {
|
|
|
50
53
|
kind: 'error';
|
|
51
54
|
stderr: string;
|
|
52
55
|
};
|
|
53
|
-
/**
|
|
54
|
-
* Manages an interactive REPL session: stdin buffering with minimal
|
|
55
|
-
* readline UX (line-mode), output routing to the WS terminal, prompt
|
|
56
|
-
* rendering, and adapter dispatch.
|
|
57
|
-
*
|
|
58
|
-
* The shell creates a ReplSession when a runtime's `startRepl` hook
|
|
59
|
-
* fires; the session installs a `replCallback` on the WebSocketTerminal
|
|
60
|
-
* (via attachRepl()) and runs until the adapter signals 'exit' or the
|
|
61
|
-
* user presses Ctrl-D on an empty line.
|
|
62
|
-
*
|
|
63
|
-
* No setTimeout in the read loop — the session is driven entirely by
|
|
64
|
-
* keystroke arrival on the WS, with awaits gating the adapter's push().
|
|
65
|
-
*/
|
|
66
56
|
export declare class ReplSession {
|
|
67
57
|
private adapter;
|
|
58
|
+
private detachRepl;
|
|
68
59
|
private terminal;
|
|
69
|
-
private detachReplCb;
|
|
70
|
-
/**
|
|
71
|
-
* REPL-R7-1 (2026-05-12): optional reference to the Nimbus shell.
|
|
72
|
-
*
|
|
73
|
-
* Required when the REPL is launched from a multi-line WS frame
|
|
74
|
-
* (e.g. user pastes `python\nexit(7)`). The shell input handler
|
|
75
|
-
* splits the frame on \r\n and pushes lines AFTER
|
|
76
|
-
* the first into `shell.pasteQueue`, which is drained ONLY when
|
|
77
|
-
* the shell becomes idle (after executeLine returns). While our
|
|
78
|
-
* REPL is running, those pasteQueue lines sit there waiting and
|
|
79
|
-
* the REPL itself receives no input → user sees a hung `>>> `
|
|
80
|
-
* prompt that never responds.
|
|
81
|
-
*
|
|
82
|
-
* If `shell` is provided, ReplSession will, immediately after
|
|
83
|
-
* attaching its replCallback, drain shell.pasteQueue and feed the
|
|
84
|
-
* lines into the callback (suffixed with \r each — matches what
|
|
85
|
-
* the WS frame originally would have looked like). This makes the
|
|
86
|
-
* paste path work transparently for REPL launches.
|
|
87
|
-
*
|
|
88
|
-
* The shell reference is optional so existing adapters (bun, node,
|
|
89
|
-
* ruby) that haven't been updated keep working with their
|
|
90
|
-
* pre-fix behavior.
|
|
91
|
-
*/
|
|
92
60
|
private shellRef;
|
|
93
61
|
/** Current line buffer (chars typed since the last enter). */
|
|
94
62
|
private lineBuf;
|
|
@@ -101,9 +69,11 @@ export declare class ReplSession {
|
|
|
101
69
|
/** Per-session history ring (most recent first). Capped at 100. */
|
|
102
70
|
private history;
|
|
103
71
|
private historyIdx;
|
|
104
|
-
/** Resolves when close() has been called and the session ended. */
|
|
105
72
|
private closedResolve;
|
|
106
73
|
private closedPromise;
|
|
74
|
+
private pendingInterrupt;
|
|
75
|
+
private activePush;
|
|
76
|
+
private ending;
|
|
107
77
|
/** Exit code captured from adapter's last 'exit' return. */
|
|
108
78
|
private exitCode;
|
|
109
79
|
/**
|
|
@@ -124,23 +94,16 @@ export declare class ReplSession {
|
|
|
124
94
|
*/
|
|
125
95
|
private inputQueue;
|
|
126
96
|
private draining;
|
|
127
|
-
constructor(adapter: ReplAdapter, terminal: WebSocketTerminal, shell?:
|
|
97
|
+
constructor(adapter: ReplAdapter, terminal: WebSocketTerminal, shell?: Pick<Shell, 'takeQueuedInput'>);
|
|
128
98
|
/** Run the session: prints banner, installs the input hook, returns
|
|
129
99
|
* a promise that resolves with the exit code when the session ends. */
|
|
130
100
|
run(): Promise<number>;
|
|
131
|
-
/**
|
|
132
|
-
* REPL-A1b: drain the input queue serially. Pulls data off
|
|
133
|
-
* inputQueue, runs handleInput, and reads any data that arrived
|
|
134
|
-
* during the await. Exits when queue is empty. Only ONE drainInput
|
|
135
|
-
* runs at a time (guarded by draining flag set in attachRepl
|
|
136
|
-
* callback).
|
|
137
|
-
*/
|
|
138
101
|
private drainInput;
|
|
139
102
|
/** Process an input chunk. May contain multiple characters (paste
|
|
140
103
|
* or rapid typing) — we iterate char-by-char to handle each
|
|
141
104
|
* control byte individually. */
|
|
142
105
|
private handleInput;
|
|
143
|
-
|
|
106
|
+
private interruptBusy;
|
|
144
107
|
private submitLine;
|
|
145
108
|
/** Map up-arrow → previous history entry. */
|
|
146
109
|
private historyUp;
|
|
@@ -148,7 +111,6 @@ export declare class ReplSession {
|
|
|
148
111
|
private historyDown;
|
|
149
112
|
/** Erase the current displayed line and replace with `text`. */
|
|
150
113
|
private replaceCurrentLine;
|
|
151
|
-
/** Close the session: detach input hook, free adapter, resolve. */
|
|
152
114
|
private endSession;
|
|
153
115
|
}
|
|
154
116
|
//# sourceMappingURL=repl-session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl-session.d.ts","sourceRoot":"","sources":["../../src/runtime/repl-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"repl-session.d.ts","sourceRoot":"","sources":["../../src/runtime/repl-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+CAA+C,CAAC;AAI3E;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B;mCAC+B;IAC/B,MAAM,IAAI,MAAM,CAAC;IACjB;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC5C;6DACyD;IACzD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,oFAAoF;IACpF,SAAS,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAiBtC,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,QAAQ,CAA+C;IAE/D,8DAA8D;IAC9D,OAAO,CAAC,OAAO,CAAc;IAC7B,sDAAsD;IACtD,OAAO,CAAC,SAAS,CAAa;IAC9B,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAgB;IAChC,8EAA8E;IAC9E,OAAO,CAAC,IAAI,CAAkB;IAC9B,mEAAmE;IACnE,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,gBAAgB,CAA0C;IAClE,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,MAAM,CAA8B;IAE5C,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAa;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,QAAQ,CAAkB;gBAEtB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAUrG;4EACwE;IAClE,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;YAuCd,UAAU;IAiBxB;;qCAEiC;YACnB,WAAW;IA0EzB,OAAO,CAAC,aAAa;YAiBP,UAAU;IA0FxB,6CAA6C;IAC7C,OAAO,CAAC,SAAS;IAMjB,mDAAmD;IACnD,OAAO,CAAC,WAAW;IAUnB,gEAAgE;IAChE,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,UAAU;CAwBnB"}
|