better-dsh 0.3.0 → 0.3.1-a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -177
- package/cordis.patch.yml +5 -36
- package/docs/20_specs/kernel-provisioning/spec.md +2 -0
- package/docs/adr/0015-independent-bundles-agent-remote.md +40 -0
- package/docs/remote-tools.md +56 -0
- package/docs/specs/kernel-provisioning/spec.md +12 -40
- package/docs/specs/omp-python-repl-port/runtime-guide.md +35 -0
- package/docs/specs/omp-python-repl-port/spec.md +68 -0
- package/docs/specs/remote-tool-enhancement/spec.md +203 -0
- package/dsh-docs/baseline.md +13 -0
- package/dsh-docs/subsystems/invariants.md +3 -0
- package/dsh-docs/subsystems/invariants.zh.md +3 -0
- package/eval-description.md +7 -3
- package/lib/LaunchOptions-0Mr6meJc.js +4 -0
- package/lib/{LaunchOptions-kGXcZOe0.js → LaunchOptions-iE9M-STS.js} +1 -1
- package/lib/{bidi-BalpKaJc.js → bidi-CaQeBZHY.js} +1 -1
- package/lib/client/index.js +30 -6
- package/lib/failover/index.js +1 -1
- package/lib/fs-aware/sandbox-plugin.js +3 -3
- package/lib/{index-browser-CysrjMyM.js → index-browser-CJzxT9Vi.js} +4 -4
- package/lib/index.d.ts +16 -50
- package/lib/index.js +482 -1720
- package/lib/kernel-env-x_WkG6Fr.js +76 -0
- package/lib/kernel-env.d.ts +5 -69
- package/lib/kernel-env.js +2 -2
- package/lib/{lsp-server-registry-BXvUTBY7.js → lsp-server-registry-BcdkHROD.js} +1 -1
- package/lib/{lsp-server-registry-CH9WNFFt.js → lsp-server-registry-D4VLs1Sm.js} +1 -1
- package/lib/{main-BQeFrBM1.js → main-taSMz1KX.js} +1 -1
- package/lib/{main-CupOhMp5.js → main-uqGqYYnA.js} +7 -5
- package/lib/mobile/plugin.d.ts +1 -1
- package/lib/omp-python/DILL-LICENSE +35 -0
- package/lib/omp-python/OMP-LICENSE +23 -0
- package/lib/omp-python/better_snapshot.py +391 -0
- package/lib/omp-python/better_support.py +425 -0
- package/lib/omp-python/dill-provenance.json +55 -0
- package/lib/omp-python/provenance.json +13 -0
- package/lib/omp-python/runner.py +2499 -0
- package/lib/omp-python/vendor/dill/__diff.py +234 -0
- package/lib/omp-python/vendor/dill/__info__.py +291 -0
- package/lib/omp-python/vendor/dill/__init__.py +119 -0
- package/lib/omp-python/vendor/dill/_dill.py +2265 -0
- package/lib/omp-python/vendor/dill/_objects.py +545 -0
- package/lib/omp-python/vendor/dill/_shims.py +193 -0
- package/lib/omp-python/vendor/dill/detect.py +287 -0
- package/lib/omp-python/vendor/dill/logger.py +285 -0
- package/lib/omp-python/vendor/dill/objtypes.py +24 -0
- package/lib/omp-python/vendor/dill/pointers.py +122 -0
- package/lib/omp-python/vendor/dill/session.py +612 -0
- package/lib/omp-python/vendor/dill/settings.py +25 -0
- package/lib/omp-python/vendor/dill/source.py +1025 -0
- package/lib/omp-python/vendor/dill/temp.py +252 -0
- package/lib/{puppeteer-core-CSk2PtSm.js → puppeteer-core-C-4NyOpY.js} +8 -8
- package/lib/remote/plugin.d.ts +459 -5
- package/lib/remote/plugin.js +2389 -210
- package/lib/url-schemes/index.d.ts +11 -11
- package/lib/url-schemes/index.js +11 -11
- package/lib/web-password.d.ts +8 -8
- package/lib/web-password.js +5 -1
- package/lib/{web-trust-Dej_RI2B.d.ts → web-trust-C_Y6o8Z2.d.ts} +11 -11
- package/lib/web-trust.d.ts +1 -2
- package/lib/welcome-notice.d.ts +5 -7
- package/lib/welcome-notice.js +9 -48
- package/lib/{wrap-T4I0nfPM.js → wrap-0VA0uFx8.js} +1 -1
- package/package.json +7 -24
- package/scripts/kernel-check.mjs +5 -0
- package/lib/LaunchOptions-B874eBu2.js +0 -4
- package/lib/bundle-delivery.d.ts +0 -20
- package/lib/bundle-delivery.js +0 -209
- package/lib/hmr.d.ts +0 -32
- package/lib/hmr.js +0 -49
- package/lib/kernel-bridge.py +0 -172
- package/lib/kernel-env-D8UOWeji.js +0 -195
- package/lib/webserver.d.ts +0 -10
- package/lib/webserver.js +0 -19
- package/scripts/kernel-provision.mjs +0 -35
- package/scripts/register-bundle.mjs +0 -80
package/lib/hmr.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import Hmr, { HmrConfig } from "@deepseek-ai/dsh-hmr";
|
|
2
|
-
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
-
|
|
4
|
-
//#region src/hmr.d.ts
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Native runExclusive keeps a boolean in ALS. HTTP servers created while
|
|
8
|
-
* replacing webServer inherit that boolean after the transaction finishes,
|
|
9
|
-
* so subsequent native Plugin Manager requests are refused as nested.
|
|
10
|
-
*
|
|
11
|
-
* The public runExclusive/watchConfig APIs expose no selective context reset.
|
|
12
|
-
* AsyncLocalStorage.snapshot() also resets AgentRegistry initiator attribution
|
|
13
|
-
* and its teardown lineage, so it cannot safely repair this host transaction.
|
|
14
|
-
* This compatibility bridge reads the native TS-private `executing` storage
|
|
15
|
-
* after checking its runtime shape. It never replaces that storage or changes
|
|
16
|
-
* upstream files: only exit() clears its inherited value while the unchanged
|
|
17
|
-
* native method establishes the next transaction. Native queue, closing,
|
|
18
|
-
* exact watchers, module reload and disposal retain their original semantics.
|
|
19
|
-
*
|
|
20
|
-
* The mutable token rejects nesting only while its operation is still active;
|
|
21
|
-
* resources created by that operation can initiate later transactions.
|
|
22
|
-
* A native implementation change fails initialization explicitly instead of
|
|
23
|
-
* silently dropping queue or lifecycle guarantees.
|
|
24
|
-
*/
|
|
25
|
-
declare class BetterHmr extends Hmr {
|
|
26
|
-
private readonly nativeTransactions;
|
|
27
|
-
private readonly liveTransactions;
|
|
28
|
-
constructor(ctx: Context, config: HmrConfig);
|
|
29
|
-
runExclusive<T>(operation: () => Promise<T>): Promise<T>;
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
export { BetterHmr as default };
|
package/lib/hmr.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import Hmr from "@deepseek-ai/dsh-hmr";
|
|
3
|
-
|
|
4
|
-
//#region src/hmr.ts
|
|
5
|
-
/**
|
|
6
|
-
* Native runExclusive keeps a boolean in ALS. HTTP servers created while
|
|
7
|
-
* replacing webServer inherit that boolean after the transaction finishes,
|
|
8
|
-
* so subsequent native Plugin Manager requests are refused as nested.
|
|
9
|
-
*
|
|
10
|
-
* The public runExclusive/watchConfig APIs expose no selective context reset.
|
|
11
|
-
* AsyncLocalStorage.snapshot() also resets AgentRegistry initiator attribution
|
|
12
|
-
* and its teardown lineage, so it cannot safely repair this host transaction.
|
|
13
|
-
* This compatibility bridge reads the native TS-private `executing` storage
|
|
14
|
-
* after checking its runtime shape. It never replaces that storage or changes
|
|
15
|
-
* upstream files: only exit() clears its inherited value while the unchanged
|
|
16
|
-
* native method establishes the next transaction. Native queue, closing,
|
|
17
|
-
* exact watchers, module reload and disposal retain their original semantics.
|
|
18
|
-
*
|
|
19
|
-
* The mutable token rejects nesting only while its operation is still active;
|
|
20
|
-
* resources created by that operation can initiate later transactions.
|
|
21
|
-
* A native implementation change fails initialization explicitly instead of
|
|
22
|
-
* silently dropping queue or lifecycle guarantees.
|
|
23
|
-
*/
|
|
24
|
-
var BetterHmr = class extends Hmr {
|
|
25
|
-
nativeTransactions;
|
|
26
|
-
liveTransactions = new AsyncLocalStorage();
|
|
27
|
-
constructor(ctx, config) {
|
|
28
|
-
super(ctx, config);
|
|
29
|
-
const storage = this.executing;
|
|
30
|
-
if (!(storage instanceof AsyncLocalStorage) || typeof storage.getStore !== "function" || typeof storage.exit !== "function" || typeof storage.run !== "function") throw new Error("Better Dash HMR cannot reset this native HMR transaction context: expected executing AsyncLocalStorage");
|
|
31
|
-
this.nativeTransactions = storage;
|
|
32
|
-
}
|
|
33
|
-
runExclusive(operation) {
|
|
34
|
-
if (this.liveTransactions.getStore()?.active) return Promise.reject(/* @__PURE__ */ new Error("HMR transactions cannot be nested"));
|
|
35
|
-
return this.nativeTransactions.exit(() => super.runExclusive(() => {
|
|
36
|
-
const token = { active: true };
|
|
37
|
-
return this.liveTransactions.run(token, async () => {
|
|
38
|
-
try {
|
|
39
|
-
return await operation();
|
|
40
|
-
} finally {
|
|
41
|
-
token.active = false;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
export { BetterHmr as default };
|
package/lib/kernel-bridge.py
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""stdio <-> ZeroMQ bridge for the DASHR REPL.
|
|
3
|
-
|
|
4
|
-
The host (Node/TS) owns the kernel lifecycle but must not link a native ZMQ
|
|
5
|
-
binding: `zeromq` was a hard npm dependency whose install script compiles C++
|
|
6
|
-
inside the consumer's install transaction (and whose top-level import killed
|
|
7
|
-
the whole plugin when the binding was missing). This process is the only ZMQ
|
|
8
|
-
speaker on the host side; it speaks JSON lines on stdio to Node.
|
|
9
|
-
|
|
10
|
-
Node ──stdio JSON lines──► kernel-bridge.py ──pyzmq/jupyter_client──► ipykernel
|
|
11
|
-
|
|
12
|
-
Requirements come from the kernel's own environment: `jupyter_client` and
|
|
13
|
-
`pyzmq` are transitive dependencies of the `ipykernel` we provision (see
|
|
14
|
-
`kernel-env.ts`), so nothing extra is fetched from any package manager.
|
|
15
|
-
|
|
16
|
-
Protocol
|
|
17
|
-
--------
|
|
18
|
-
stdin (one JSON object per line)::
|
|
19
|
-
|
|
20
|
-
{"op":"send","channel":"shell"|"control","header":{...},"content":{...}}
|
|
21
|
-
{"op":"interrupt"}
|
|
22
|
-
{"op":"shutdown"}
|
|
23
|
-
|
|
24
|
-
stdout (one JSON object per line)::
|
|
25
|
-
|
|
26
|
-
{"ev":"ready"}
|
|
27
|
-
{"ev":"msg","channel":"shell"|"iopub"|"control"|"stdin","msg":{...}}
|
|
28
|
-
{"ev":"fatal","message":"..."}
|
|
29
|
-
|
|
30
|
-
The caller-supplied `header` is passed to `Session.msg` verbatim, so the
|
|
31
|
-
message id the host generated is the id on the wire — parent-matching and
|
|
32
|
-
request/response correlation stay exactly as they were on the raw-socket path.
|
|
33
|
-
"""
|
|
34
|
-
from __future__ import annotations
|
|
35
|
-
|
|
36
|
-
import json
|
|
37
|
-
import queue
|
|
38
|
-
import sys
|
|
39
|
-
import threading
|
|
40
|
-
|
|
41
|
-
CHANNELS = ("iopub", "shell", "control", "stdin")
|
|
42
|
-
POLL_SECONDS = 0.2
|
|
43
|
-
|
|
44
|
-
# Protocol output goes to the real stdout; anything a library prints must never
|
|
45
|
-
# corrupt the byte stream the host parses, so sys.stdout is repointed to stderr.
|
|
46
|
-
_PROTOCOL_OUT = sys.stdout
|
|
47
|
-
sys.stdout = sys.stderr
|
|
48
|
-
|
|
49
|
-
_write_lock = threading.Lock()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def emit(payload: dict) -> None:
|
|
53
|
-
"""Write one protocol line, atomically across the pump threads."""
|
|
54
|
-
# `default=str`: jupyter_client hands back parsed values (a header `date`
|
|
55
|
-
# arrives as `datetime`, for instance) that are not JSON-native; the host
|
|
56
|
-
# only reads msg_id/msg_type/content, so stringifying the rest is lossless
|
|
57
|
-
# for its purposes and keeps one pump thread from dying on a header field.
|
|
58
|
-
line = json.dumps(payload, ensure_ascii=False, default=str)
|
|
59
|
-
with _write_lock:
|
|
60
|
-
_PROTOCOL_OUT.write(line + "\n")
|
|
61
|
-
_PROTOCOL_OUT.flush()
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _clean(value: object) -> object:
|
|
65
|
-
"""Strip binary buffers: this protocol carries JSON only."""
|
|
66
|
-
if isinstance(value, dict):
|
|
67
|
-
return {key: _clean(item) for key, item in value.items() if key != "buffers"}
|
|
68
|
-
if isinstance(value, (list, tuple)):
|
|
69
|
-
return [_clean(item) for item in value]
|
|
70
|
-
if isinstance(value, bytes):
|
|
71
|
-
return value.decode("utf-8", "replace")
|
|
72
|
-
return value
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def pump(client, name: str, stop: threading.Event) -> None:
|
|
76
|
-
"""Relay one client channel onto stdout until `stop` is set."""
|
|
77
|
-
channel = getattr(client, f"{name}_channel")
|
|
78
|
-
while not stop.is_set():
|
|
79
|
-
try:
|
|
80
|
-
message = channel.get_msg(timeout=POLL_SECONDS)
|
|
81
|
-
except queue.Empty:
|
|
82
|
-
continue
|
|
83
|
-
except Exception as error: # channel torn down, or the socket died
|
|
84
|
-
if not stop.is_set():
|
|
85
|
-
emit({"ev": "fatal", "channel": name, "message": f"{type(error).__name__}: {error}"})
|
|
86
|
-
try:
|
|
87
|
-
emit({
|
|
88
|
-
"ev": "msg",
|
|
89
|
-
"channel": name,
|
|
90
|
-
"msg": {
|
|
91
|
-
"header": message.get("header") or {},
|
|
92
|
-
"parent_header": message.get("parent_header") or {},
|
|
93
|
-
"content": _clean(message.get("content") or {}),
|
|
94
|
-
"metadata": _clean(message.get("metadata") or {}),
|
|
95
|
-
},
|
|
96
|
-
})
|
|
97
|
-
except Exception as error: # never let one message kill a channel pump
|
|
98
|
-
sys.stderr.write(f"kernel-bridge: cannot relay {name} message: {type(error).__name__}: {error}\n")
|
|
99
|
-
sys.stderr.flush()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def main() -> int:
|
|
103
|
-
if len(sys.argv) != 2:
|
|
104
|
-
emit({"ev": "fatal", "message": "usage: kernel-bridge.py <connection-file>"})
|
|
105
|
-
return 2
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
from jupyter_client import BlockingKernelClient
|
|
109
|
-
except Exception as error: # the kernel environment is incomplete
|
|
110
|
-
emit({"ev": "fatal", "message": f"jupyter_client unavailable: {type(error).__name__}: {error}"})
|
|
111
|
-
return 3
|
|
112
|
-
|
|
113
|
-
try:
|
|
114
|
-
client = BlockingKernelClient(connection_file=sys.argv[1])
|
|
115
|
-
client.load_connection_file()
|
|
116
|
-
client.start_channels()
|
|
117
|
-
except Exception as error:
|
|
118
|
-
emit({"ev": "fatal", "message": f"cannot connect to the kernel: {type(error).__name__}: {error}"})
|
|
119
|
-
return 4
|
|
120
|
-
|
|
121
|
-
stop = threading.Event()
|
|
122
|
-
for name in CHANNELS:
|
|
123
|
-
threading.Thread(target=pump, args=(client, name, stop), name=f"pump-{name}", daemon=True).start()
|
|
124
|
-
|
|
125
|
-
emit({"ev": "ready"})
|
|
126
|
-
|
|
127
|
-
try:
|
|
128
|
-
for line in sys.stdin:
|
|
129
|
-
line = line.strip()
|
|
130
|
-
if not line:
|
|
131
|
-
continue
|
|
132
|
-
try:
|
|
133
|
-
request = json.loads(line)
|
|
134
|
-
except Exception:
|
|
135
|
-
continue
|
|
136
|
-
op = request.get("op")
|
|
137
|
-
if op == "send":
|
|
138
|
-
channel_name = request.get("channel")
|
|
139
|
-
if channel_name not in ("shell", "control"):
|
|
140
|
-
emit({"ev": "fatal", "message": f"unknown send channel {channel_name!r}"})
|
|
141
|
-
continue
|
|
142
|
-
channel = getattr(client, f"{channel_name}_channel")
|
|
143
|
-
try:
|
|
144
|
-
header = request.get("header") or None
|
|
145
|
-
message = client.session.msg(
|
|
146
|
-
header["msg_type"] if header else "comm_msg",
|
|
147
|
-
content=request.get("content") or {},
|
|
148
|
-
header=header,
|
|
149
|
-
)
|
|
150
|
-
channel.send(message)
|
|
151
|
-
except Exception as error:
|
|
152
|
-
emit({"ev": "fatal", "channel": channel_name, "message": f"send failed: {type(error).__name__}: {error}"})
|
|
153
|
-
elif op == "interrupt":
|
|
154
|
-
try:
|
|
155
|
-
client.interrupt_kernel()
|
|
156
|
-
except Exception as error:
|
|
157
|
-
emit({"ev": "fatal", "message": f"interrupt failed: {type(error).__name__}: {error}"})
|
|
158
|
-
elif op == "shutdown":
|
|
159
|
-
break
|
|
160
|
-
except (KeyboardInterrupt, BrokenPipeError):
|
|
161
|
-
pass
|
|
162
|
-
finally:
|
|
163
|
-
stop.set()
|
|
164
|
-
try:
|
|
165
|
-
client.stop_channels()
|
|
166
|
-
except Exception:
|
|
167
|
-
pass
|
|
168
|
-
return 0
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if __name__ == "__main__":
|
|
172
|
-
sys.exit(main())
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
|
|
7
|
-
//#region src/kernel-env.ts
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
/** The installed package root (where `lib/` and `src/` live): the default home of the managed venv. */
|
|
10
|
-
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
-
/** The config value meaning "no explicit interpreter — auto-manage a venv". */
|
|
12
|
-
const AUTO_PYTHON_SENTINEL = "python3";
|
|
13
|
-
/** Preferred CPython version for a managed venv. */
|
|
14
|
-
const DEFAULT_KERNEL_PYTHON_VERSION = "3.11";
|
|
15
|
-
/** Pinned, tested `ipykernel` version (2026-09-02 tested value; upgrade = explicit change + regression). */
|
|
16
|
-
const IPYKERNEL_VERSION = "7.3.0";
|
|
17
|
-
/** Pinned, tested `dill` version. */
|
|
18
|
-
const DILL_VERSION = "0.4.1";
|
|
19
|
-
/** A probe that never imports user modules and reports exactly what we need. */
|
|
20
|
-
const PROBE_CODE = [
|
|
21
|
-
"import json, sys",
|
|
22
|
-
"r = {\"version\": sys.version.split()[0], \"ipykernel\": False, \"dill\": False}",
|
|
23
|
-
"try:",
|
|
24
|
-
" import ipykernel # noqa: F401",
|
|
25
|
-
" r[\"ipykernel\"] = True",
|
|
26
|
-
"except Exception:",
|
|
27
|
-
" pass",
|
|
28
|
-
"try:",
|
|
29
|
-
" import dill # noqa: F401",
|
|
30
|
-
" r[\"dill\"] = True",
|
|
31
|
-
"except Exception:",
|
|
32
|
-
" pass",
|
|
33
|
-
"print(json.dumps(r))"
|
|
34
|
-
].join("\n");
|
|
35
|
-
async function probePython(python) {
|
|
36
|
-
try {
|
|
37
|
-
const { stdout } = await execFileAsync(python, ["-c", PROBE_CODE], { timeout: 15e3 });
|
|
38
|
-
const parsed = JSON.parse(stdout.trim());
|
|
39
|
-
return {
|
|
40
|
-
version: typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown",
|
|
41
|
-
ipykernel: parsed.ipykernel === true,
|
|
42
|
-
dill: parsed.dill === true
|
|
43
|
-
};
|
|
44
|
-
} catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** The venv interpreter path for this platform. */
|
|
49
|
-
function venvPythonPath(venvDir) {
|
|
50
|
-
return process.platform === "win32" ? join(venvDir, "Scripts", "python.exe") : join(venvDir, "bin", "python");
|
|
51
|
-
}
|
|
52
|
-
async function commandExists(cmd) {
|
|
53
|
-
try {
|
|
54
|
-
await execFileAsync(cmd, ["--version"], { timeout: 1e4 });
|
|
55
|
-
return true;
|
|
56
|
-
} catch {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/** Tool-cache env for uv: caches live NEXT TO the venv (plugin territory), so a read-only home can never block provisioning. */
|
|
61
|
-
function uvToolEnv(venvDir) {
|
|
62
|
-
const cacheDir = join(dirname(venvDir), ".uv-cache");
|
|
63
|
-
return {
|
|
64
|
-
...process.env,
|
|
65
|
-
UV_CACHE_DIR: cacheDir,
|
|
66
|
-
UV_PYTHON_INSTALL_DIR: join(cacheDir, "python"),
|
|
67
|
-
UV_LINK_MODE: "copy"
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/** uv venv arguments (pure; unit-tested). */
|
|
71
|
-
function uvVenvArgs(venvDir, pythonVersion) {
|
|
72
|
-
return [
|
|
73
|
-
"venv",
|
|
74
|
-
venvDir,
|
|
75
|
-
"--python",
|
|
76
|
-
pythonVersion
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
/** python3 -m venv fallback arguments (pure; cache-free by construction). */
|
|
80
|
-
function venvFallbackArgs(venvDir) {
|
|
81
|
-
return [
|
|
82
|
-
"-m",
|
|
83
|
-
"venv",
|
|
84
|
-
venvDir
|
|
85
|
-
];
|
|
86
|
-
}
|
|
87
|
-
/** uv pip install arguments with pinned versions (pure; unit-tested). */
|
|
88
|
-
function uvInstallArgs(python) {
|
|
89
|
-
return [
|
|
90
|
-
"pip",
|
|
91
|
-
"install",
|
|
92
|
-
"--python",
|
|
93
|
-
python,
|
|
94
|
-
"--quiet",
|
|
95
|
-
`ipykernel==${IPYKERNEL_VERSION}`,
|
|
96
|
-
`dill==${DILL_VERSION}`
|
|
97
|
-
];
|
|
98
|
-
}
|
|
99
|
-
/** pip install arguments with pinned versions for the venv interpreter (pure). */
|
|
100
|
-
function pipInstallArgs() {
|
|
101
|
-
return [
|
|
102
|
-
"-m",
|
|
103
|
-
"pip",
|
|
104
|
-
"install",
|
|
105
|
-
"--quiet",
|
|
106
|
-
"--disable-pip-version-check",
|
|
107
|
-
`ipykernel==${IPYKERNEL_VERSION}`,
|
|
108
|
-
`dill==${DILL_VERSION}`
|
|
109
|
-
];
|
|
110
|
-
}
|
|
111
|
-
/** Create the venv (when absent) and return its interpreter path. */
|
|
112
|
-
async function createVenv(venvDir, pythonVersion) {
|
|
113
|
-
const python = venvPythonPath(venvDir);
|
|
114
|
-
if (existsSync(python)) return python;
|
|
115
|
-
if (await commandExists("uv")) {
|
|
116
|
-
mkdirSync(join(dirname(venvDir), ".uv-cache"), { recursive: true });
|
|
117
|
-
await execFileAsync("uv", uvVenvArgs(venvDir, pythonVersion), {
|
|
118
|
-
timeout: 12e4,
|
|
119
|
-
env: uvToolEnv(venvDir)
|
|
120
|
-
});
|
|
121
|
-
} else await execFileAsync("python3", venvFallbackArgs(venvDir), { timeout: 12e4 });
|
|
122
|
-
return python;
|
|
123
|
-
}
|
|
124
|
-
/** Install pinned `ipykernel` + `dill` into the venv interpreter. */
|
|
125
|
-
async function installDeps(python, venvDir) {
|
|
126
|
-
if (await commandExists("uv")) await execFileAsync("uv", uvInstallArgs(python), {
|
|
127
|
-
timeout: 18e4,
|
|
128
|
-
env: uvToolEnv(venvDir)
|
|
129
|
-
});
|
|
130
|
-
else await execFileAsync(python, pipInstallArgs(), { timeout: 18e4 });
|
|
131
|
-
}
|
|
132
|
-
/** Provision (or repair) the managed venv so it has `ipykernel` + `dill`. */
|
|
133
|
-
async function ensureVenv(venvDir, pythonVersion, log) {
|
|
134
|
-
const python = venvPythonPath(venvDir);
|
|
135
|
-
const existing = existsSync(python) ? await probePython(python) : null;
|
|
136
|
-
if (existing?.ipykernel === true && existing.dill === true) return python;
|
|
137
|
-
await createVenv(venvDir, pythonVersion);
|
|
138
|
-
if (!existsSync(python)) throw new Error(`dashr-repl: managed kernel venv was not created at ${venvDir} (no interpreter at ${python})`);
|
|
139
|
-
log?.("info", `dashr-repl: provisioning kernel venv at ${venvDir} (ipykernel + dill)`);
|
|
140
|
-
await installDeps(python, venvDir);
|
|
141
|
-
return python;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Resolve the kernel interpreter, provisioning a managed venv when no
|
|
145
|
-
* explicit interpreter is configured. Throws when the resolved interpreter
|
|
146
|
-
* cannot run `ipykernel`; a missing `dill` only disables snapshots (warned).
|
|
147
|
-
*/
|
|
148
|
-
async function resolveKernelEnv(options = {}) {
|
|
149
|
-
const log = options.log;
|
|
150
|
-
const explicit = options.python !== void 0 && options.python !== "" && options.python !== AUTO_PYTHON_SENTINEL ? options.python : void 0;
|
|
151
|
-
if (explicit !== void 0) {
|
|
152
|
-
const probe$1 = await probePython(explicit);
|
|
153
|
-
if (probe$1 === null) throw new Error(`dashr-repl: kernel python ${JSON.stringify(explicit)} could not be executed — check the path`);
|
|
154
|
-
if (!probe$1.ipykernel) throw new Error(`dashr-repl: kernel python ${JSON.stringify(explicit)} has no ipykernel — install it (pip install ipykernel) or set python to a managed venv`);
|
|
155
|
-
if (!probe$1.dill) log?.("warn", `dashr-repl: kernel python ${JSON.stringify(explicit)} has no dill — namespace snapshots are DISABLED (install it: pip install dill)`);
|
|
156
|
-
return {
|
|
157
|
-
python: explicit,
|
|
158
|
-
managed: false,
|
|
159
|
-
venvDir: void 0,
|
|
160
|
-
dill: probe$1.dill,
|
|
161
|
-
ipykernel: true,
|
|
162
|
-
version: probe$1.version
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const venvDir = options.venvDir ?? join(PACKAGE_ROOT, ".venv-kernel");
|
|
166
|
-
const python = venvPythonPath(venvDir);
|
|
167
|
-
if (options.autoInstall === false) {
|
|
168
|
-
const probe$1 = await probePython(python);
|
|
169
|
-
if (probe$1 === null || !probe$1.ipykernel) throw new Error(`dashr-repl: managed kernel venv is missing or incomplete at ${venvDir} — run 'npm run kernel:venv' in the dashr package, or set kernelAutoInstall: true (or python to a prepared interpreter)`);
|
|
170
|
-
if (!probe$1.dill) log?.("warn", `dashr-repl: kernel venv at ${venvDir} has no dill — namespace snapshots are DISABLED (run 'npm run kernel:venv' to reinstall)`);
|
|
171
|
-
return {
|
|
172
|
-
python,
|
|
173
|
-
managed: true,
|
|
174
|
-
venvDir,
|
|
175
|
-
dill: probe$1.dill,
|
|
176
|
-
ipykernel: true,
|
|
177
|
-
version: probe$1.version
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
const resolved = await ensureVenv(venvDir, options.pythonVersion ?? DEFAULT_KERNEL_PYTHON_VERSION, log);
|
|
181
|
-
const probe = await probePython(resolved);
|
|
182
|
-
if (probe === null || !probe.ipykernel) throw new Error(`dashr-repl: managed kernel venv at ${venvDir} still has no ipykernel after provisioning — check uv/python3-venv availability and retry`);
|
|
183
|
-
if (!probe.dill) log?.("warn", `dashr-repl: managed kernel venv at ${venvDir} has no dill after provisioning — namespace snapshots are DISABLED`);
|
|
184
|
-
return {
|
|
185
|
-
python: resolved,
|
|
186
|
-
managed: true,
|
|
187
|
-
venvDir,
|
|
188
|
-
dill: probe.dill,
|
|
189
|
-
ipykernel: true,
|
|
190
|
-
version: probe.version
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
//#endregion
|
|
195
|
-
export { PACKAGE_ROOT as a, uvInstallArgs as c, venvFallbackArgs as d, IPYKERNEL_VERSION as i, uvToolEnv as l, DEFAULT_KERNEL_PYTHON_VERSION as n, pipInstallArgs as o, DILL_VERSION as r, resolveKernelEnv as s, AUTO_PYTHON_SENTINEL as t, uvVenvArgs as u };
|
package/lib/webserver.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
-
import { WebServer } from "@deepseek-ai/dsh-host-webserver";
|
|
3
|
-
|
|
4
|
-
//#region src/webserver.d.ts
|
|
5
|
-
declare class BetterWebServer extends WebServer {
|
|
6
|
-
static inject: string[];
|
|
7
|
-
[Service.init](): Promise<void>;
|
|
8
|
-
}
|
|
9
|
-
//#endregion
|
|
10
|
-
export { BetterWebServer as default };
|
package/lib/webserver.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
-
import { WebServer } from "@deepseek-ai/dsh-host-webserver";
|
|
3
|
-
|
|
4
|
-
//#region src/webserver.ts
|
|
5
|
-
var BetterWebServer = class extends WebServer {
|
|
6
|
-
static inject = ["loader"];
|
|
7
|
-
async [Service.init]() {
|
|
8
|
-
const welcome = [...this.ctx.loader.entries()].find((entry) => entry.options.id === "dashr-welcome-notice");
|
|
9
|
-
if (welcome !== void 0 && !welcome.disabled) {
|
|
10
|
-
if (welcome.fiber === void 0) await welcome.init();
|
|
11
|
-
await welcome.fiber?.await();
|
|
12
|
-
}
|
|
13
|
-
this.ctx.fiber.assertActive();
|
|
14
|
-
await super[Service.init]();
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
//#endregion
|
|
19
|
-
export { BetterWebServer as default };
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Best-effort kernel provisioning entry — the single code path behind BOTH
|
|
4
|
-
* `postinstall` and `npm run kernel:venv`.
|
|
5
|
-
*
|
|
6
|
-
* Fail-open by design (2026-09-03 ruling): any failure logs a remedy hint and
|
|
7
|
-
* exits 0. The installation must NEVER fail because the kernel could not be
|
|
8
|
-
* provisioned here — the daemon spin-up check (primary path) and first-use
|
|
9
|
-
* lazy provisioning (final fallback) complete the three-trigger ladder.
|
|
10
|
-
*/
|
|
11
|
-
import { access } from 'node:fs/promises'
|
|
12
|
-
|
|
13
|
-
const libUrl = new URL('../lib/kernel-env.js', import.meta.url)
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
await access(libUrl)
|
|
17
|
-
} catch {
|
|
18
|
-
// Dev checkout before the first build, or a pruned install: nothing to call.
|
|
19
|
-
console.log('[dashr] kernel-env build output not present yet — skipping install-time pre-provision (the daemon spin-up check will provision on start)')
|
|
20
|
-
process.exit(0)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const { resolveKernelEnv } = await import(libUrl)
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const env = await resolveKernelEnv({
|
|
27
|
-
autoInstall: true,
|
|
28
|
-
log: (level, message) => console[level === 'warn' ? 'warn' : 'log'](`[dashr] ${message}`),
|
|
29
|
-
})
|
|
30
|
-
console.log(`[dashr] kernel ready: python ${env.version} at ${env.python}${env.dill ? '' : ' (dill missing — snapshots disabled)'}`)
|
|
31
|
-
} catch (error) {
|
|
32
|
-
console.warn(`[dashr] kernel pre-provision failed (non-blocking): ${error instanceof Error ? error.message : String(error)}`)
|
|
33
|
-
console.warn('[dashr] remedies: run "npm run kernel:venv" in the dashr package; or rely on the daemon spin-up check / first-use auto-install; or set python to a prepared interpreter')
|
|
34
|
-
process.exit(0)
|
|
35
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// Separate-process entry for the SAME native package operations used by dsh plugin.
|
|
2
|
-
// No shell interpolation, package scripts, upstream edits, or companion registry lookup.
|
|
3
|
-
import { createRequire } from 'node:module'
|
|
4
|
-
import { createHash } from 'node:crypto'
|
|
5
|
-
import { readFile, realpath } from 'node:fs/promises'
|
|
6
|
-
import { dirname, join, relative, resolve, isAbsolute, sep } from 'node:path'
|
|
7
|
-
import { pathToFileURL } from 'node:url'
|
|
8
|
-
let input = ''
|
|
9
|
-
for await (const bytes of process.stdin) input += bytes
|
|
10
|
-
const {profile,archive,expected} = JSON.parse(input)
|
|
11
|
-
const require = createRequire(profile.installAnchor)
|
|
12
|
-
const {runProfilePnpm} = await import(pathToFileURL(require.resolve('@deepseek-ai/dsh-plugin-manager/operations')).href)
|
|
13
|
-
const {withFileLock} = await import(pathToFileURL(require.resolve('@deepseek-ai/dsh-atomic-write')).href)
|
|
14
|
-
const abort = new AbortController()
|
|
15
|
-
process.once('SIGTERM', () => abort.abort())
|
|
16
|
-
const json = async path => JSON.parse(await readFile(path, 'utf8'))
|
|
17
|
-
const hash = bytes => createHash('sha256').update(bytes).digest('hex')
|
|
18
|
-
async function contained(root, path) {
|
|
19
|
-
if (typeof path !== 'string' || isAbsolute(path)) throw new Error('Pack delivery path must be relative')
|
|
20
|
-
const base = await realpath(root), file = await realpath(resolve(base, path)), rel = relative(base, file)
|
|
21
|
-
if (rel === '..' || rel.startsWith('..' + sep) || isAbsolute(rel)) throw new Error('Pack delivery path escapes its owner')
|
|
22
|
-
return file
|
|
23
|
-
}
|
|
24
|
-
const skipped = disposition => ({ exitCode: 0, disposition })
|
|
25
|
-
// Complete each native acquisition/release before observing cancellation;
|
|
26
|
-
// exiting during its async lock-file write can strand incomplete lock metadata.
|
|
27
|
-
async function withCancellableProfileLock(operation) {
|
|
28
|
-
const path = join(profile.dir, 'package.json'), deadline = Date.now() + 120000
|
|
29
|
-
for (;;) {
|
|
30
|
-
abort.signal.throwIfAborted()
|
|
31
|
-
let acquired = false
|
|
32
|
-
try {
|
|
33
|
-
return await withFileLock(path, async () => {
|
|
34
|
-
acquired = true
|
|
35
|
-
abort.signal.throwIfAborted()
|
|
36
|
-
return operation()
|
|
37
|
-
}, { waitMs: Math.min(250, Math.max(0, deadline - Date.now())) })
|
|
38
|
-
} catch (error) {
|
|
39
|
-
abort.signal.throwIfAborted()
|
|
40
|
-
if (acquired || Date.now() >= deadline
|
|
41
|
-
|| error.message !== `atomic-write: timed out waiting for the writer lock at ${path}.lock`) throw error
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const result = await withCancellableProfileLock(async () => {
|
|
46
|
-
abort.signal.throwIfAborted()
|
|
47
|
-
const current = await json(join(profile.dir, 'package.json'))
|
|
48
|
-
if (typeof expected.ownerSpec !== 'string' || current.dependencies?.[expected.owner] !== expected.ownerSpec) return skipped('owner-changed')
|
|
49
|
-
const ownerDir = join(profile.dir, 'node_modules', expected.owner)
|
|
50
|
-
let owner, descriptorPath, descriptor
|
|
51
|
-
try {
|
|
52
|
-
owner = await json(join(ownerDir, 'package.json'))
|
|
53
|
-
if (owner.dependencies?.[expected.companion.name] !== expected.companion.version || owner.superd?.delivery === undefined) return skipped('owner-changed')
|
|
54
|
-
descriptorPath = await contained(ownerDir, owner.superd.delivery)
|
|
55
|
-
descriptor = await json(descriptorPath)
|
|
56
|
-
} catch (error) {
|
|
57
|
-
if (error.code === 'ENOENT') return skipped('owner-changed')
|
|
58
|
-
throw error
|
|
59
|
-
}
|
|
60
|
-
const item = descriptor.companions?.find(item => item.name === expected.companion.name)
|
|
61
|
-
if (item?.version !== expected.companion.version || item.sha256 !== expected.companion.sha256
|
|
62
|
-
|| hash(await readFile(await contained(dirname(descriptorPath), item.tarball))) !== expected.companion.sha256) return skipped('owner-changed')
|
|
63
|
-
const prior = current.dependencies?.[item.name]
|
|
64
|
-
if (prior !== expected.priorSpec) return skipped(prior === undefined && expected.priorSpec !== undefined ? 'companion-removed' : 'companion-changed')
|
|
65
|
-
if (hash(await readFile(archive)) !== item.sha256) throw new Error('Cached companion SHA256 mismatch')
|
|
66
|
-
abort.signal.throwIfAborted()
|
|
67
|
-
// Same public operation and writer lock used by the native Plugin Manager;
|
|
68
|
-
// intent is checked after acquisition, before a queued install can undo removal.
|
|
69
|
-
const operation = await runProfilePnpm({profile:profile.name,dir:profile.dir,installAnchor:profile.installAnchor,cwd:profile.dir,home:profile.home}, ['add','file:'+archive,'--ignore-scripts'], {
|
|
70
|
-
...profile.packageManager, execution:'service', signal:abort.signal, outputBytes:16384,
|
|
71
|
-
lookupTimeoutMs:120000, idleTimeoutMs:120000,
|
|
72
|
-
})
|
|
73
|
-
if (operation.exitCode === 0) {
|
|
74
|
-
const installed = await json(join(profile.dir, 'node_modules', item.name, 'package.json'))
|
|
75
|
-
const after = await json(join(profile.dir, 'package.json'))
|
|
76
|
-
if (installed.name !== item.name || installed.version !== item.version || after.dependencies?.[item.name] !== 'file:' + archive) throw new Error('Native companion registration did not install the exact local artifact')
|
|
77
|
-
}
|
|
78
|
-
return { exitCode: operation.exitCode, logPath: operation.logPath, disposition: 'installed' }
|
|
79
|
-
})
|
|
80
|
-
process.stdout.write(JSON.stringify(result)+'\n')
|