better-dsh 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +49 -178
  2. package/cordis.patch.yml +4 -35
  3. package/docs/adr/0015-independent-bundles-agent-remote.md +40 -0
  4. package/docs/remote-tools.md +56 -0
  5. package/docs/specs/omp-python-repl-port/runtime-guide.md +33 -0
  6. package/docs/specs/omp-python-repl-port/spec.md +61 -0
  7. package/docs/specs/remote-tool-enhancement/spec.md +203 -0
  8. package/dsh-docs/baseline.md +13 -0
  9. package/dsh-docs/subsystems/invariants.md +3 -0
  10. package/dsh-docs/subsystems/invariants.zh.md +3 -0
  11. package/eval-description.md +7 -3
  12. package/lib/{LaunchOptions-kGXcZOe0.js → LaunchOptions-5eg7E0ye.js} +1 -1
  13. package/lib/LaunchOptions-DxQQ0s-A.js +4 -0
  14. package/lib/{bidi-BalpKaJc.js → bidi-CwabFH07.js} +1 -1
  15. package/lib/client/index.js +30 -6
  16. package/lib/failover/index.js +1 -1
  17. package/lib/fs-aware/sandbox-plugin.js +3 -3
  18. package/lib/{index-browser-CysrjMyM.js → index-browser-B-9jm12n.js} +4 -4
  19. package/lib/index.d.ts +17 -41
  20. package/lib/index.js +473 -1678
  21. package/lib/kernel-env-BOF0fNHn.js +91 -0
  22. package/lib/kernel-env.d.ts +4 -58
  23. package/lib/kernel-env.js +2 -2
  24. package/lib/{lsp-server-registry-BXvUTBY7.js → lsp-server-registry-BcdkHROD.js} +1 -1
  25. package/lib/{lsp-server-registry-CH9WNFFt.js → lsp-server-registry-D4VLs1Sm.js} +1 -1
  26. package/lib/{main-CupOhMp5.js → main-BeCgUd20.js} +8 -6
  27. package/lib/{main-BQeFrBM1.js → main-Dv_UQqG2.js} +1 -1
  28. package/lib/mobile/plugin.d.ts +1 -1
  29. package/lib/omp-python/DILL-LICENSE +35 -0
  30. package/lib/omp-python/OMP-LICENSE +23 -0
  31. package/lib/omp-python/better_snapshot.py +391 -0
  32. package/lib/omp-python/better_support.py +425 -0
  33. package/lib/omp-python/dill-provenance.json +55 -0
  34. package/lib/omp-python/provenance.json +13 -0
  35. package/lib/omp-python/runner.py +2499 -0
  36. package/lib/omp-python/vendor/dill/__diff.py +234 -0
  37. package/lib/omp-python/vendor/dill/__info__.py +291 -0
  38. package/lib/omp-python/vendor/dill/__init__.py +119 -0
  39. package/lib/omp-python/vendor/dill/_dill.py +2265 -0
  40. package/lib/omp-python/vendor/dill/_objects.py +545 -0
  41. package/lib/omp-python/vendor/dill/_shims.py +193 -0
  42. package/lib/omp-python/vendor/dill/detect.py +287 -0
  43. package/lib/omp-python/vendor/dill/logger.py +285 -0
  44. package/lib/omp-python/vendor/dill/objtypes.py +24 -0
  45. package/lib/omp-python/vendor/dill/pointers.py +122 -0
  46. package/lib/omp-python/vendor/dill/session.py +612 -0
  47. package/lib/omp-python/vendor/dill/settings.py +25 -0
  48. package/lib/omp-python/vendor/dill/source.py +1025 -0
  49. package/lib/omp-python/vendor/dill/temp.py +252 -0
  50. package/lib/{puppeteer-core-CSk2PtSm.js → puppeteer-core-Dnd7eqel.js} +9 -9
  51. package/lib/remote/plugin.d.ts +459 -5
  52. package/lib/remote/plugin.js +2389 -210
  53. package/lib/url-schemes/index.d.ts +11 -11
  54. package/lib/url-schemes/index.js +12 -12
  55. package/lib/web-password.d.ts +8 -8
  56. package/lib/web-password.js +5 -1
  57. package/lib/{web-trust-Dej_RI2B.d.ts → web-trust-CwZrTBcl.d.ts} +11 -11
  58. package/lib/web-trust.d.ts +1 -2
  59. package/lib/welcome-notice.d.ts +5 -7
  60. package/lib/welcome-notice.js +9 -48
  61. package/lib/{wrap-T4I0nfPM.js → wrap-0VA0uFx8.js} +1 -1
  62. package/package.json +6 -23
  63. package/scripts/kernel-provision.mjs +4 -34
  64. package/lib/LaunchOptions-B874eBu2.js +0 -4
  65. package/lib/bundle-delivery.d.ts +0 -20
  66. package/lib/bundle-delivery.js +0 -209
  67. package/lib/hmr.d.ts +0 -32
  68. package/lib/hmr.js +0 -49
  69. package/lib/kernel-bridge.py +0 -172
  70. package/lib/kernel-env-D8UOWeji.js +0 -195
  71. package/lib/webserver.d.ts +0 -10
  72. package/lib/webserver.js +0 -19
  73. package/scripts/register-bundle.mjs +0 -80
@@ -1,209 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
- import { mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
4
- import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
7
-
8
- //#region src/bundle-delivery.ts
9
- const name = "dashr-bundle-delivery";
10
- const inject = ["profileContext", "appReady"];
11
- const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
12
- const json = async (path$1) => JSON.parse(await readFile(path$1, "utf8"));
13
- async function optionalJson(path$1) {
14
- try {
15
- return await json(path$1);
16
- } catch (error) {
17
- if (error.code === "ENOENT") return void 0;
18
- throw error;
19
- }
20
- }
21
- async function contained(root, path$1) {
22
- if (isAbsolute(path$1)) throw new Error("Pack delivery path must be relative");
23
- const base = await realpath(root), file = await realpath(resolve(base, path$1));
24
- const rel = relative(base, file);
25
- if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(rel)) throw new Error("Pack delivery path escapes its owner");
26
- return file;
27
- }
28
- /** A child process avoids inheriting a running HMR transaction's AsyncLocalStorage. */
29
- async function installNative(profile, archive, expected, signal) {
30
- signal?.throwIfAborted();
31
- const childScript = fileURLToPath(new URL("../scripts/register-bundle.mjs", import.meta.url));
32
- return new Promise((done, fail) => {
33
- const child = spawn(process.execPath, [childScript], {
34
- stdio: [
35
- "pipe",
36
- "pipe",
37
- "pipe"
38
- ],
39
- cwd: profile.dir
40
- });
41
- let output = "", diagnostic = "";
42
- const abort = () => {
43
- child.kill("SIGTERM");
44
- };
45
- const timeout = setTimeout(abort, 36e4);
46
- signal?.addEventListener("abort", abort, { once: true });
47
- child.stdout.setEncoding("utf8").on("data", (chunk) => {
48
- output = (output + chunk).slice(-32768);
49
- });
50
- child.stderr.setEncoding("utf8").on("data", (chunk) => {
51
- diagnostic = (diagnostic + chunk).slice(-4096);
52
- });
53
- child.on("error", fail);
54
- child.on("close", (code) => {
55
- clearTimeout(timeout);
56
- signal?.removeEventListener("abort", abort);
57
- if (signal?.aborted) {
58
- fail(signal.reason);
59
- return;
60
- }
61
- if (code !== 0) {
62
- fail(/* @__PURE__ */ new Error(`Native companion registration failed (${code}): ${diagnostic.trim() || output.trim()}`));
63
- return;
64
- }
65
- try {
66
- const result = JSON.parse(output);
67
- if (result.exitCode !== 0) fail(/* @__PURE__ */ new Error(`Native companion registration failed (${result.exitCode}); diagnostics: ${result.logPath ?? "unavailable"}`));
68
- else if (![
69
- "installed",
70
- "owner-changed",
71
- "companion-changed",
72
- "companion-removed"
73
- ].includes(result.disposition)) fail(/* @__PURE__ */ new Error("Native companion registration returned an invalid disposition"));
74
- else done(result.disposition);
75
- } catch (error) {
76
- fail(error);
77
- }
78
- });
79
- child.stdin.on("error", () => {});
80
- child.stdin.end(JSON.stringify({
81
- profile,
82
- archive,
83
- expected
84
- }));
85
- });
86
- }
87
- /** First registration enables a companion; native reconciliation preserves retained disablement. */
88
- async function reconcileBundledDelivery(profile, signal) {
89
- const directory = join(profile.home, "bundle-artifacts");
90
- await mkdir(directory, {
91
- recursive: true,
92
- mode: 448
93
- });
94
- const key = hash(profile.dir);
95
- return withFileLock(join(directory, key + ".delivery"), async () => {
96
- signal?.throwIfAborted();
97
- const receiptPath = join(directory, key + ".json");
98
- const receipts = await optionalJson(receiptPath) ?? {};
99
- const manifest = await json(join(profile.dir, "package.json"));
100
- const result = {
101
- installed: [],
102
- restartRequired: false
103
- };
104
- for (const owner of Object.keys(manifest.dependencies ?? {})) {
105
- const ownerDir = join(profile.dir, "node_modules", owner);
106
- const ownerManifest = await optionalJson(join(ownerDir, "package.json"));
107
- if (ownerManifest?.superd?.delivery === void 0) continue;
108
- const descriptorPath = await contained(ownerDir, ownerManifest.superd.delivery);
109
- const descriptor = await json(descriptorPath);
110
- if (!Array.isArray(descriptor.companions)) throw new Error("Invalid pack delivery descriptor");
111
- for (const item of descriptor.companions) {
112
- if (item.name !== "better-dsh" || item.version !== ownerManifest.dependencies?.[item.name] || !/^[a-f0-9]{64}$/.test(item.sha256)) throw new Error("Invalid companion identity/version/digest");
113
- const bytes = await readFile(await contained(dirname(descriptorPath), item.tarball));
114
- if (hash(bytes) !== item.sha256) throw new Error(`Pack companion SHA256 mismatch: ${item.name}`);
115
- const archive = join(directory, `${item.name}-${item.sha256}.tgz`);
116
- const cached = await optionalJson(join(profile.dir, "node_modules", item.name, "package.json"));
117
- const current = await json(join(profile.dir, "package.json"));
118
- const ownerSpec = current.dependencies?.[owner];
119
- if (ownerSpec === void 0) continue;
120
- const priorSpec = current.dependencies?.[item.name];
121
- const receiptKey = `${owner}:${item.name}`;
122
- if (priorSpec === void 0 && receipts[receiptKey] === item.sha256) continue;
123
- if (priorSpec === "file:" + archive && cached?.name === item.name && cached.version === item.version) {
124
- receipts[receiptKey] = item.sha256;
125
- await writeFileAtomic(receiptPath, JSON.stringify(receipts) + "\n", { mode: 384 });
126
- continue;
127
- }
128
- try {
129
- if (hash(await readFile(archive)) !== item.sha256) throw new Error(`Cached companion SHA256 mismatch: ${item.name}`);
130
- } catch (error) {
131
- if (error.code !== "ENOENT") throw error;
132
- const temporary = archive + "." + randomUUID() + ".tmp";
133
- try {
134
- await writeFile(temporary, bytes, {
135
- flag: "wx",
136
- mode: 384
137
- });
138
- await rename(temporary, archive);
139
- } finally {
140
- await rm(temporary, { force: true });
141
- }
142
- }
143
- signal?.throwIfAborted();
144
- const disposition = await installNative(profile, archive, {
145
- owner,
146
- ownerSpec,
147
- companion: item,
148
- ...priorSpec === void 0 ? {} : { priorSpec }
149
- }, signal);
150
- if (disposition === "owner-changed" || disposition === "companion-changed") continue;
151
- receipts[receiptKey] = item.sha256;
152
- await writeFileAtomic(receiptPath, JSON.stringify(receipts) + "\n", { mode: 384 });
153
- if (disposition === "companion-removed") continue;
154
- result.installed.push(`${item.name}@${item.version}`);
155
- result.restartRequired ||= priorSpec !== void 0;
156
- }
157
- }
158
- return result;
159
- }, { waitMs: 12e4 });
160
- }
161
- function apply(ctx) {
162
- const signal = new AbortController();
163
- let started = false, running = false, again = false;
164
- const reconcile = async () => {
165
- if (!started || signal.signal.aborted) return;
166
- if (running) {
167
- again = true;
168
- return;
169
- }
170
- running = true;
171
- try {
172
- const result = await reconcileBundledDelivery(ctx.profileContext, signal.signal);
173
- if (result.installed.length) {
174
- ctx.logger.info(`Registered ${result.installed.join(", ")} from its containing local pack${result.restartRequired ? "; restart DSH to use the updated code" : ""}`);
175
- ctx.emit("plugin-manager/changed", { reason: "install" });
176
- }
177
- } catch (error) {
178
- if (!signal.signal.aborted) ctx.logger.error("Local pack companion registration failed; retry by re-enabling the bundle", error);
179
- } finally {
180
- running = false;
181
- if (again && !signal.signal.aborted) {
182
- again = false;
183
- reconcile();
184
- }
185
- }
186
- };
187
- ctx.effect(() => {
188
- const offReady = ctx.appReady.onReady(() => {
189
- started = true;
190
- reconcile();
191
- });
192
- const offChange = ctx.on("plugin-manager/changed", () => {
193
- reconcile();
194
- });
195
- return () => {
196
- offReady();
197
- offChange();
198
- signal.abort();
199
- };
200
- }, "bundle-delivery: native registration");
201
- }
202
- var bundle_delivery_default = {
203
- name,
204
- inject,
205
- apply
206
- };
207
-
208
- //#endregion
209
- export { apply, bundle_delivery_default as default, inject, name, reconcileBundledDelivery };
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 };
@@ -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 };
@@ -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 };