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.
Files changed (76) hide show
  1. package/README.md +49 -177
  2. package/cordis.patch.yml +5 -36
  3. package/docs/20_specs/kernel-provisioning/spec.md +2 -0
  4. package/docs/adr/0015-independent-bundles-agent-remote.md +40 -0
  5. package/docs/remote-tools.md +56 -0
  6. package/docs/specs/kernel-provisioning/spec.md +12 -40
  7. package/docs/specs/omp-python-repl-port/runtime-guide.md +35 -0
  8. package/docs/specs/omp-python-repl-port/spec.md +68 -0
  9. package/docs/specs/remote-tool-enhancement/spec.md +203 -0
  10. package/dsh-docs/baseline.md +13 -0
  11. package/dsh-docs/subsystems/invariants.md +3 -0
  12. package/dsh-docs/subsystems/invariants.zh.md +3 -0
  13. package/eval-description.md +7 -3
  14. package/lib/LaunchOptions-0Mr6meJc.js +4 -0
  15. package/lib/{LaunchOptions-kGXcZOe0.js → LaunchOptions-iE9M-STS.js} +1 -1
  16. package/lib/{bidi-BalpKaJc.js → bidi-CaQeBZHY.js} +1 -1
  17. package/lib/client/index.js +30 -6
  18. package/lib/failover/index.js +1 -1
  19. package/lib/fs-aware/sandbox-plugin.js +3 -3
  20. package/lib/{index-browser-CysrjMyM.js → index-browser-CJzxT9Vi.js} +4 -4
  21. package/lib/index.d.ts +16 -50
  22. package/lib/index.js +482 -1720
  23. package/lib/kernel-env-x_WkG6Fr.js +76 -0
  24. package/lib/kernel-env.d.ts +5 -69
  25. package/lib/kernel-env.js +2 -2
  26. package/lib/{lsp-server-registry-BXvUTBY7.js → lsp-server-registry-BcdkHROD.js} +1 -1
  27. package/lib/{lsp-server-registry-CH9WNFFt.js → lsp-server-registry-D4VLs1Sm.js} +1 -1
  28. package/lib/{main-BQeFrBM1.js → main-taSMz1KX.js} +1 -1
  29. package/lib/{main-CupOhMp5.js → main-uqGqYYnA.js} +7 -5
  30. package/lib/mobile/plugin.d.ts +1 -1
  31. package/lib/omp-python/DILL-LICENSE +35 -0
  32. package/lib/omp-python/OMP-LICENSE +23 -0
  33. package/lib/omp-python/better_snapshot.py +391 -0
  34. package/lib/omp-python/better_support.py +425 -0
  35. package/lib/omp-python/dill-provenance.json +55 -0
  36. package/lib/omp-python/provenance.json +13 -0
  37. package/lib/omp-python/runner.py +2499 -0
  38. package/lib/omp-python/vendor/dill/__diff.py +234 -0
  39. package/lib/omp-python/vendor/dill/__info__.py +291 -0
  40. package/lib/omp-python/vendor/dill/__init__.py +119 -0
  41. package/lib/omp-python/vendor/dill/_dill.py +2265 -0
  42. package/lib/omp-python/vendor/dill/_objects.py +545 -0
  43. package/lib/omp-python/vendor/dill/_shims.py +193 -0
  44. package/lib/omp-python/vendor/dill/detect.py +287 -0
  45. package/lib/omp-python/vendor/dill/logger.py +285 -0
  46. package/lib/omp-python/vendor/dill/objtypes.py +24 -0
  47. package/lib/omp-python/vendor/dill/pointers.py +122 -0
  48. package/lib/omp-python/vendor/dill/session.py +612 -0
  49. package/lib/omp-python/vendor/dill/settings.py +25 -0
  50. package/lib/omp-python/vendor/dill/source.py +1025 -0
  51. package/lib/omp-python/vendor/dill/temp.py +252 -0
  52. package/lib/{puppeteer-core-CSk2PtSm.js → puppeteer-core-C-4NyOpY.js} +8 -8
  53. package/lib/remote/plugin.d.ts +459 -5
  54. package/lib/remote/plugin.js +2389 -210
  55. package/lib/url-schemes/index.d.ts +11 -11
  56. package/lib/url-schemes/index.js +11 -11
  57. package/lib/web-password.d.ts +8 -8
  58. package/lib/web-password.js +5 -1
  59. package/lib/{web-trust-Dej_RI2B.d.ts → web-trust-C_Y6o8Z2.d.ts} +11 -11
  60. package/lib/web-trust.d.ts +1 -2
  61. package/lib/welcome-notice.d.ts +5 -7
  62. package/lib/welcome-notice.js +9 -48
  63. package/lib/{wrap-T4I0nfPM.js → wrap-0VA0uFx8.js} +1 -1
  64. package/package.json +7 -24
  65. package/scripts/kernel-check.mjs +5 -0
  66. package/lib/LaunchOptions-B874eBu2.js +0 -4
  67. package/lib/bundle-delivery.d.ts +0 -20
  68. package/lib/bundle-delivery.js +0 -209
  69. package/lib/hmr.d.ts +0 -32
  70. package/lib/hmr.js +0 -49
  71. package/lib/kernel-bridge.py +0 -172
  72. package/lib/kernel-env-D8UOWeji.js +0 -195
  73. package/lib/webserver.d.ts +0 -10
  74. package/lib/webserver.js +0 -19
  75. package/scripts/kernel-provision.mjs +0 -35
  76. package/scripts/register-bundle.mjs +0 -80
package/README.md CHANGED
@@ -1,196 +1,71 @@
1
- > Part of [Super D](../README.md). This is the independently installable `better-dsh` package. Super Dash embeds the same local build and registers it as a separate native Plugin Manager row, with its own enable/disable switch; both versions advance together toward 0.3.0. Active source is this directory, with shared specifications and references under root `docs/`.
2
-
3
- # Better Dsh — Dashr
4
-
5
- DASHR is a persistent-kernel REPL for the DeepSeek Harness (dsh): a
6
- **stateful `ctx.replRuntime` provider** (one persistent IPython kernel
7
- subprocess **per session**, the run's `principal`, held in a map inside the
8
- one service instance per mount). Each `run()` is one cell on the calling
9
- session's kernel; variables, imports, and definitions assigned in run N
10
- survive into run N+1 — *state codification* (blueprint §1.1 channel ②),
11
- deliberately NOT the per-run isolation a one-shot execution backend
12
- provides — and two sessions sharing one service instance never see each
13
- other's variables.
14
-
15
- Naming (v0.1.5 layer model): the runtime class is **`DashrRuntime`** — the
16
- standing-mount-layer component (one instance per mount holding the
17
- cross-session kernel map), and therefore the de facto daemon while the
18
- profile-level `DashrDaemon` concept stays an empty shell; the session layer
19
- is the ipykernel subprocess itself, a pure interpreter with no harness
20
- awareness.
21
-
22
- The presentation half — the `eval` transport tool, the Python SDK renderer,
23
- and the tool→binding bridge — lives in the SAME package (merged in v0.1.8;
24
- the pre-merge `dsh-rlm-mode`/`dashr-presentation` sibling split is gone).
25
- The runtime registers the service key `replRuntime` through its own vendored
26
- Service Definition (see `src/vendored/repl-runtime.ts`), so it carries **zero
27
- dsh runtime package dependencies**: only `@deepseek-ai/cordis` (peer),
28
- `schemastery`, and `zeromq`.
29
-
30
- ## Package positioning
31
-
32
- - npm name: `better-dsh` (unscoped; formerly `@pgmi-builds/better-dsh`, which
33
- now stays published only as a deprecated pointer to this name).
34
- - A standard Cordis plugin (`Context` + schemastery `Config`, every tunable
35
- configurable from `cordis.yml`, no hardcoded tunables).
36
- - "Registrations are effects": the kernel lifecycle (lazy spawn on a key's
37
- first `run()`, teardown on that session's `agent/disposed`, optional
38
- snapshot at either teardown) is effect-owned, so plugin disposal tears
39
- every subprocess down.
40
- - Published surface: `lib/` only (`files: ["lib"]`, `main`/`types`/`exports`
41
- pointing at `lib/index.js` / `lib/index.d.ts`) — the build emits beside the
42
- manifest (`outDir: 'lib'`; the tsdown default `dist/` left the exports map
43
- dangling, fixed in M2B). The root also re-exports the vendored Service
44
- Definition's public contract (`ReplRuntime` plus the `CodeRun*` /
45
- `CodeBinding*` / `CodeJsonValue` types) so consumers depend on the
46
- published shape instead of reaching into sources. The declaration keeps
47
- dependency imports external (`dts: { resolve: false }`): bundled copies
48
- would create duplicate type identities in a consumer's program.
49
-
50
- ## Native Web defaults
51
-
52
- The bundle owns native DSH Web policies as independently toggleable components.
53
- `dashr-web-trust` accepts authenticated serving Host/Origin values while retaining
54
- native cookie authentication. `dashr-webserver` inherits the native server and
55
- waits for welcome confirmation before listening; its default address is
56
- `0.0.0.0`, with the native port and CLI override behavior preserved.
57
- Automatic credential onboarding is disabled; users can still configure models.
58
-
59
- `dashr-welcome-notice` reads the current `WELCOME_NOTICE_VERSION` from the Models
60
- client belonging to the actual DSH installation and acknowledges it through the
61
- original General settings namespace before the first HTTP read. No version is
62
- pinned in the plugin. Ordinary version bumps adapt at startup; changed private
63
- symbols or build layouts fail explicitly. Restore native welcome behavior with:
64
-
65
- ```yaml
66
- - id: dashr-welcome-notice
67
- disabled: true
68
- ```
1
+ > Part of [Super D](../README.md). `better-dsh` is an independently installable native DSH bundle. Super Dash installs it through the native Plugin Manager; both packages use synchronized versions and independent enable/disable controls. Canonical shared specifications are in root `docs/`.
69
2
 
70
- The sibling Super Dash pack builds this repository locally and embeds its actual
71
- tarball. Better Dash remains independently installable. `../dev-webmcp` is an
72
- unshipped development-only bundle owned by this repository.
3
+ # Better Dash — Dashr
73
4
 
74
- ## Install
5
+ Better provides a stateful `ctx.replRuntime` and the model-facing `eval` tool. Each DSH session has its own persistent Python child: variables, imports and definitions survive across cells, while sessions remain isolated. `DashrRuntime` owns the children and their lifecycle; the presentation layer exposes the Python SDK and bridges calls to the current session's host tools.
6
+
7
+ The execution path is Node → JSON lines → an unchanged, pinned OMP Python runner → Python. Better adapts the host interfaces and packages the runner resources with compiled JavaScript. No OMP package, Bun, IPython, Jupyter or ZeroMQ is required by this path. Host modules remain optional peers and keep their runtime identity; Better's own JavaScript dependencies are bundled at build time.
8
+
9
+ ## Native Web policies
10
+
11
+ The bundle owns independently toggleable WebTrust, mobile behavior, password-page and onboarding contributions through native DSH interfaces. Native WebServer/HMR and CLI listener policy remain in place. `dashr-welcome-notice` shadows the native welcome entry and calls its public completion callback; disabling that component restores the native welcome item.
12
+
13
+ Super Dash installs Better as a separate native bundle. `../dev-webmcp` is a development-only test bundle and is not shipped in either product tarball.
14
+
15
+ ## Install and interpreter
75
16
 
76
17
  ```sh
77
18
  npm install better-dsh
78
19
  ```
79
20
 
80
- The runtime OWNS its kernel environment. With `python` left at the
81
- `python3` sentinel (or absent), it provisions a managed venv under the
82
- package (`.venv-kernel`) on first use, installing `ipykernel` + `dill`
83
- (CPython 3.11) — no `/tmp` symlink, no blind trust in a host `python3`.
84
- An explicit `python` (or `DASHR_KERNEL_PYTHON`) is verified instead. For
85
- development and tests, pre-create the venv:
21
+ Better uses an existing Python interpreter. An explicit `python` setting (or `DASHR_KERNEL_PYTHON`) takes precedence; otherwise discovery checks the active virtual/Conda environment, an existing workspace `.venv` or `venv`, then PATH `python3` and `python`. Candidates are actually probed, and a broken automatically discovered candidate does not hide a working fallback. Relative explicit paths resolve against the session workspace. No CPython implementation or Python3.11 version is selected. The packaged dill0.4.1 checkpoint helper requires Python >=3.9.
86
22
 
87
- ```sh
88
- npm run kernel:venv # uv venv .venv-kernel --python 3.11 + ipykernel + dill
89
- ```
23
+ Better does not install Python, invoke uv/pip, or create a virtual environment. When none of the existing candidates works, the error lists the attempts and asks for an existing interpreter or a Python installation. `npm run kernel:check` checks availability without changing the environment. The runner and dill sources ship in the package; runtime checkpoints belong under the native profile home, and package upgrades do not require rebuilding a package-local Python environment.
90
24
 
91
- Tests pick the kernel interpreter from `DASHR_TEST_PYTHON`, falling back to
92
- `./.venv-kernel/bin/python`, then `python3` — see `test/helpers.ts`.
25
+ The old `kernelEnvDir`, `kernelPythonVersion` and `kernelAutoInstall` provisioning fields and `kernel:venv` command are removed. Existing package-local `.venv-kernel` / `.uv-cache` directories are no longer selected automatically or modified; an existing interpreter can still be selected explicitly. A checkpoint made with another interpreter/environment may be incompatible and produces the usual explicit recovery notice; it is never replayed into a new environment.
26
+
27
+ The original OMP runner's source commit/hash and MIT license, and dill's source hashes/version and BSD-3-Clause license, accompany the packaged resources. Better's integration code is separate from these original files. OMP-specific Agent helpers are not exposed as DSH features.
93
28
 
94
29
  ## Configuration
95
30
 
96
- Every field of the plugin `Config` (schemastery defaults shown):
31
+ Runtime fields remain configurable through the ordinary Cordis row. The implementation's schema is authoritative; the principal controls are:
97
32
 
98
33
  | Field | Default | Meaning |
99
34
  | --- | --- | --- |
100
- | `python` | `python3` | Explicit interpreter with `ipykernel`; the bare `python3` sentinel (or absent) selects a managed venv under `kernelEnvDir`. |
101
- | `startupTimeoutMs` | `30000` | Budget for kernel spawn → ready, in milliseconds. |
102
- | `runTimeoutMs` | `120000` | Wall budget per run; expiry interrupts the kernel then force-settles. |
103
- | `interruptGraceMs` | `2000` | Grace between a timeout/abort interrupt and the force-settle. |
104
- | `interruptConfirmMs` | `250` | Confirm window between the control-channel interrupt and the SIGALRM escalation (must be `< interruptGraceMs`); see "Interrupts" below. |
105
- | `disposeTimeoutMs` | `5000` | Budget for graceful kernel teardown (shutdown_request → SIGKILL). |
106
- | `snapshotTimeoutMs` | `30000` | Budget for internal snapshot/restore cells (dill dump/load). |
107
- | `maxOutputBytes` | `67108864` | Hard cap for serialized log-array, completion-value, and failure-message payloads. |
108
- | `snapshotDir` | *(unset)* | Base directory for per-session namespace snapshots (`<dir>/<principal>/state.dill` + `manifest.json`); none when absent. |
109
- | `snapshotSizeCapBytes` | `268435456` | Serialized-size cap for a turn-end snapshot; over-cap snapshots are skipped (one-time model warning). |
110
- | `username` | `dashr` | Jupyter username stamped on wire messages. |
111
- | `kernelEnvDir` | *(unset)* | Managed venv directory (defaults to `<package>/.venv-kernel`). |
112
- | `kernelPythonVersion` | *(unset)* | Preferred CPython version for a managed venv (default `3.11`). |
113
- | `kernelAutoInstall` | `true` | Provision the managed venv (`ipykernel` + `dill`) on first use. |
114
-
115
- ## Persistent-state semantics
116
-
117
- - **Cell semantics**: each `run({ program })` is one cell on the calling
118
- session's kernel namespace (`user_ns`) — a pure IPython REPL. Top-level
119
- `await` works; a top-level `return` is a SyntaxError, exactly as in a
120
- native IPython cell. The completion value is the LAST expression's value
121
- (REPL displayhook-style): a statement-ending cell or a `None` final
122
- expression yields no `value` field, and a non-JSON value comes back as its
123
- repr text.
124
- - **Session keying** (M3-A): one kernel per distinct `request.principal`
125
- (the presentation bridge passes the calling agent's session id); runs
126
- without a principal share one default key, preserving M1 semantics. The
127
- service instance count is unchanged — one per mount — the keying is a
128
- `Map<principal, kernel>` inside the provider.
129
- - **Kernel lifetime**: lazy start on a key's first `run()`; teardown when
130
- that session's agent is disposed (the dsh `agent/disposed` event, payload
131
- `{ agent: { id } }`, listened through the untyped cordis event service to
132
- keep this package's zero-dsh-dependency rule) and on plugin disposal
133
- (`shutdown_request`, then SIGKILL after `disposeTimeoutMs`). A kernel that
134
- dies unexpectedly is never reused in-process: it respawns onto its nearest
135
- replayable snapshot (or a fresh empty kernel when none exists) and the run
136
- that observed the death gets an explicit `worker-exit` naming what was lost.
137
- - **Turn-end snapshots** (M3-B): with `snapshotDir` configured, every
138
- successful run is followed by a size-capped snapshot cell that dumps the
139
- user namespace to `<snapshotDir>/<principal>/state.dill` + `manifest.json`
140
- (`turn`, `pythonVersion`, `venvPath` = the kernel's own `sys.executable`,
141
- `skills`, `names`, `sizeBytes`). A namespace whose serialized size exceeds
142
- `snapshotSizeCapBytes` is skipped — estimated BEFORE any dill IO by a
143
- bounded walk that reads numpy/pandas in-memory footprints, then confirmed
144
- against the actual `.part` dump — and the model is warned once through the
145
- run's own logs. Skipped snapshots never replace the previous good one.
146
- - **Restore-on-first-boot** (M3-B): a key's first kernel boot restores its
147
- on-disk snapshot before running user code. The kernel validates the
148
- manifest itself (python version, interpreter identity, skills); a
149
- non-replayable snapshot degrades to an EMPTY namespace and the first run
150
- tells the model so. Variable state and the append-only transcript are NOT
151
- transactionally consistent (blueprint §8.3): the snapshot is a point-in-time
152
- namespace capture that can lag the transcript, and a degraded restore never
153
- fabricates variables the transcript once saw.
154
- - **Interrupts** (M3-A hardened): aborts/timeouts escalate in two phases —
155
- the zmq control `interrupt_request` first, then SIGALRM only after
156
- `interruptConfirmMs` if the cell has still not settled. The kernel-side
157
- bootstrap installs a busy guard that only raises `KeyboardInterrupt` while
158
- a dashr cell is actually executing, so a signal landing on an idle or
159
- booting kernel is swallowed instead of terminating the process (the M1
160
- same-tick dual send killed idle kernels deterministically — 10/10
161
- same-tick, 8/10 at +1-2ms, 40/40 during cold boot; see
162
- `test/interrupt-race.spec.ts`). The hard-abort contract is intact: a busy
163
- `while True: pass` still breaks inside the grace (blueprint §10.4).
164
- - **Concurrency**: the bridge serializes cells per kernel (`executeCell`
165
- awaits the previous cell), so concurrent `run()` calls on one session
166
- queue rather than interleave; see `test/parallel.spec.ts`. Runs on
167
- DIFFERENT principals execute on their own kernels concurrently.
168
- - **What snapshots do NOT carry**: nothing outside the kernel namespace is
169
- ever in scope — the v0.1.8b removal deleted the Continual Harness
170
- (`refine()`, `harnessDir`) entirely, so the snapshot/restore cycle's only
171
- persistence channel is the kernel namespace itself (blueprint §8.4).
172
- Anything a cell stores in ordinary variables follows the snapshot rules
173
- above as before.
35
+ | `python` | automatic discovery | Existing executable name/path; explicit selection bypasses discovery. |
36
+ | `startupTimeoutMs` | `30000` | Budget for child startup and initialization. |
37
+ | `runTimeoutMs` | `120000` | Wall budget per user cell. |
38
+ | `interruptGraceMs` | `2000` | Grace after an interrupt before terminating an unresponsive child. |
39
+ | `interruptConfirmMs` | `250` | Delay before confirming an interrupt that has not finished the cell. |
40
+ | `disposeTimeoutMs` | `5000` | Bound on graceful child teardown before forced cleanup. |
41
+ | `snapshotTimeoutMs` | `30000` | Budget for internal save/restore operations. |
42
+ | `maxOutputBytes` | `67108864` | Cap for serialized logs, completion and error output; overflow is reported. |
43
+ | `snapshotDir` | native profile checkpoint directory | Explicit directory overrides the native profile default; `""` disables persistence and retains only live process state. Standalone runtimes must explicitly configure persistence. |
44
+ | `snapshotSizeCapBytes` | `268435456` | Checkpoint payload cap; failed or over-cap saves preserve the previous committed generation. |
45
+ | `username` | `dashr` | Retained compatibility field. |
174
46
 
175
- ## Testing
47
+ ## Cells and recovery
176
48
 
177
- ```sh
178
- npm install
179
- npm run kernel:venv # once; or export DASHR_TEST_PYTHON=/path/to/python
180
- npm run typecheck # tsc --noEmit
181
- npm test # vitest --run (fileParallelism: false)
182
- ```
49
+ One `eval` call runs one Python cell. Top-level `await` works and top-level `return` is invalid. The final expression supplies the completion value; a final `None` or statement produces no value. Non-JSON completions return repr text. Printed stdout/stderr become logs. Tools use `await tool.<name>({...})` and failures raise `ToolCallError`.
50
+
51
+ Operations for one session serialize, including initialization, user cells, checkpointing, variable access and reset. Different sessions can execute concurrently; one cell can fan out independent asynchronous host tool calls.
52
+
53
+ Completed cells attempt a checkpoint, including cells that raise after changing state. The helper saves an admitted user object graph, preserving shared references among retained values. Serialization failures, resource exclusions and partial coverage are reported to the model. A complete generation is committed atomically; a failed attempt before commit retains the previous valid checkpoint. If interruption prevents confirmation of the commit, Better reports that uncertainty.
183
54
 
184
- Teardown discipline: every test context is disposed through
185
- `onTestFinished`, and CI must assert no orphan kernels remain:
55
+ On a fresh child, Better validates and attempts restoration once before admitting user code. The notice identifies the saved cell and any omissions. A damaged or incompatible checkpoint starts empty and reports why. Runner plumbing, tool bindings and OS resources are excluded. Current tools are recreated from the host; files, sockets, subprocesses, threads, locks, connections, environment mutations and external side effects are not restored as a Python process.
56
+
57
+ A dying or interrupted cell may already have performed side effects. Better never automatically retries it or blindly replays historical cells. Read the recovery notice and inspect real state before deciding which work to repeat. `reset: true` clears the session's memory and disk checkpoints.
58
+
59
+ For the full boundary and source provenance contract, see [the runtime guide](docs/specs/omp-python-repl-port/runtime-guide.md) and [spec](docs/specs/omp-python-repl-port/spec.md).
60
+
61
+ ## Testing
186
62
 
187
63
  ```sh
188
- pgrep -cf -- '-[m] ipykernel_launcher' || echo no-orphans
64
+ npm run typecheck
65
+ DASHR_TEST_PYTHON=python3 npm test
189
66
  ```
190
67
 
191
- (The `-[m]` trick prevents `pgrep` from matching itself; 208 orphaned
192
- kernels once exhausted machine memory while every unit test stayed green —
193
- blueprint §10.8/§10.9.)
68
+ Tests create real Python children and dispose their contexts. Stdlib-only interpreter coverage verifies that external kernel packages are unnecessary. Consumer acceptance additionally requires the root dual-tarball launcher and dev3 installed behavior; source tests and process liveness alone are insufficient.
194
69
 
195
70
  ---
196
71
 
@@ -198,7 +73,7 @@ blueprint §10.8/§10.9.)
198
73
 
199
74
  The DASHR agent-plane presentation (blueprint §7.4): the plugin half that
200
75
  presents the persistent-kernel runtime to the model as **cells on one
201
- persistent IPython kernel**. It contributes:
76
+ persistent Python runner**. It contributes:
202
77
 
203
78
  - **`eval`** — the cell transport tool: one call = one cell on the calling
204
79
  session's kernel (the `ctx.replRuntime` service). Variables, imports, and
@@ -303,15 +178,12 @@ unknown id, or an infrastructure rejection all map to an `error` string.
303
178
 
304
179
  ```sh
305
180
  npm install
306
- npm run kernel:venv # once; or export DASHR_TEST_PYTHON=/path/to/python
181
+ export DASHR_TEST_PYTHON=python3 # or an explicit supported interpreter
307
182
  npm run typecheck
308
183
  npm test # vitest --run
309
184
  ```
310
185
 
311
- The suite needs a Python interpreter with `ipykernel` (+ `dill` for the
312
- snapshot tiers) for the real-kernel specs; it resolves one from
313
- `DASHR_TEST_PYTHON`, then `./.venv-kernel/bin/python`, then `python3` —
314
- see `test/helpers.ts`.
186
+ The suite needs a supported Python interpreter; the runner and checkpoint serializer are supplied by Better. `DASHR_TEST_PYTHON` selects the test interpreter.
315
187
 
316
188
  ## Relationship to upstream
317
189
 
package/cordis.patch.yml CHANGED
@@ -14,17 +14,14 @@
14
14
  # a PTC session resolves to UNKNOWN_TOOL). DASHR ships no PTC mode, so that
15
15
  # rule never bites here.
16
16
  #
17
- # `python` is only a hint: the runtime OWNS the kernel environment. An
18
- # explicit interpreter (DASHR_KERNEL_PYTHON) is verified; the bare
17
+ # An explicit DASHR_KERNEL_PYTHON interpreter is verified. The default
18
+ # python3 compatibility path may prepare a managed Python environment;
19
+ # the packaged OMP runner needs no Jupyter or kernel package installation.
19
20
  - insert:
20
- # No-op standalone; keeps a containing pack's local companion delivery current.
21
- - id: dashr-bundle-delivery
22
- name: 'better-dsh/bundle-delivery'
23
-
24
21
  - id: dashr-repl
25
22
  name: 'better-dsh'
26
23
  config:
27
- python: !!js process.env.DASHR_KERNEL_PYTHON ?? 'python3'
24
+ python: !!js process.env.DASHR_KERNEL_PYTHON
28
25
 
29
26
  # URL-aware I/O + hashline + devices. Gates ride THIS row's config
30
27
  # (schema defaults: both ON — opt-out), no longer the core row's.
@@ -72,7 +69,7 @@
72
69
  - id: dashr-web-password
73
70
  name: 'better-dsh/web-password'
74
71
 
75
- # Automatically acknowledge the installed Host's current welcome version.
72
+ # Skip native welcome selections through the client's public onboarding slot.
76
73
  # Single-row opt-out: dashr-welcome-notice: disabled.
77
74
  - id: dashr-welcome-notice
78
75
  name: 'better-dsh/welcome-notice'
@@ -105,35 +102,7 @@
105
102
  config:
106
103
  trustedHosts: !!js (process.env.DSH_TRUSTED_HOSTS ?? '').split(/\s+/).filter(Boolean).concat(ctx.webRuntime.trustedHosts)
107
104
 
108
- # Replacement uses the public patch mechanism: disable the native provider,
109
- # then insert a distinct row providing the same native webServer service.
110
- - id: webserver
111
- name: '@deepseek-ai/dsh-host-webserver'
112
- disabled: true
113
- - insert:
114
- - id: dashr-webserver
115
- name: 'better-dsh/webserver'
116
- inject: [webStartup]
117
- config:
118
- host: !!js ctx.webStartup.host ?? '0.0.0.0'
119
- port: !!js ctx.webStartup.port ?? 3080
120
- compression: gzip
121
- compressionLevel: 1
122
- compressionThresholdBytes: 1024
123
-
124
105
  - id: ui-settings-models
125
106
  name: '@deepseek-ai/dsh-client-ui-settings-models'
126
107
  config:
127
108
  credentialOnboarding: false
128
-
129
- # Native watcher/queue with scoped transaction context across listener replacement.
130
- # Distinct row preserves whole-bundle disablement in the native manager.
131
- - id: hmr
132
- name: '@deepseek-ai/dsh-hmr'
133
- disabled: true
134
- - insert:
135
- - id: dashr-hmr
136
- name: 'better-dsh/hmr'
137
- disabled: !!js "!ctx.get('profileContext')"
138
- config:
139
- root: []
@@ -1,5 +1,7 @@
1
1
  # kernel-provisioning Specification
2
2
 
3
+ > Historical iKernel contract, superseded on2026-10-12. Do not apply the provisioning requirements below to current Better. The active [existing-interpreter discovery contract](../../specs/kernel-provisioning/spec.md) removes uv/pip provisioning,3.11 selection and package-local venv creation.
4
+
3
5
  ## Purpose
4
6
  better-dsh 的 REPL kernel(IPython)供给契约:独占 managed venv(插件包目录内,`kernelEnvDir` 可显式覆盖)、版本锁定(ipykernel 7.3.0 / dill 0.4.1,uv 与 pip 双路径)、三级触发供给(postinstall 加速器 / daemon spin-up 主路径 / 首用 lazy 兜底)全 fail-open 且幂等、供给状态可观测;npm 包不内嵌预建 venv(供给能力随包,供给结果不随包)。
5
7
 
@@ -0,0 +1,40 @@
1
+ # ADR 0015 — Independent bundles and Agent-owned remote tools
2
+
3
+ Status: accepted design (2026-10-10); consumer acceptance tracked separately.
4
+
5
+ ## Context
6
+
7
+ The October 9 user decision supersedes ADR 0014's npm dependency architecture. Better Dash and Super Dash are independent native DSH bundles at synchronized versions. Their independent disable/uninstall choices must remain meaningful.
8
+
9
+ ## Decision
10
+
11
+ - Super has no Better npm dependency, optional dependency or peer edge. The native Plugin Manager companion operation requests `better-dsh@<installed Super version>`, then `latest`, then the bare name. Existing installation and user choices are preserved; Super upgrade does not reinstall a removed companion. The same native operation serves Settings explicit installation.
12
+ - `better-dsh/src/remote/` is the sole editable enhanced-remote source. The build generates Super's self-contained `remote/` ESM/declarations and source SHA-256 provenance, exposed by `super-dsh/remote`. This narrow exception carries neither the Better bundle, client, other tools nor a runtime dependency on Better. Host modules remain external optional peers and single-instance.
13
+ - Both remote rows default on. Official Agent creation events allow the first enabled row to own registration for that Agent instance. A loser does not retry. Winner unload cleans its Agent resources; it does not trigger automatic hot takeover. New Agent creation and cold resume can compete again.
14
+ - Hub is optional and independent of native remote registration. Hub-body authenticated executors do not depend on the model-facing remote row. No machine availability poller or second background job registry is introduced.
15
+
16
+ ## Consequences and verification
17
+
18
+ Pack validation covers exports, generated closure/provenance, host externalization, and absence of Better payload/dependency edges. Consumer acceptance must additionally exercise Super's worker while Better is absent, independent Plugin Manager transitions, actual tools and model-visible recovery guidance. Source tests or mounted plugin rows are not substitutes.
19
+
20
+ See [the feature specification](../specs/remote-tool-enhancement/spec.md), [delivery guide §8](../07-cordis-dsh-dev-test-guides.md#8-better-dash-双构建与原生-companion-交付2026-10-09-再修订), and the dated acceptance report. ADR 0014 and its historical reports are preserved as evidence of the former design.
21
+
22
+
23
+ ### 2026-10-10 consumer worker delivery correction
24
+
25
+ The generated Super `remote/` contains exactly `plugin.js`, `plugin.d.ts`, private `file-worker.js`, and `provenance.json`. The worker leaf is built solely from canonical `better-dsh/src/remote/file-worker.ts`, has Node-builtins-only imports, and is hashed/checked with the closure. It has no public package export, host peer imports, duplicated host modules, source loader or Better dependency. The public plugin contract remains unchanged. The existing owned child-process/group cleanup remains the receiver boundary; no worker-thread lifecycle replacement. This narrow extra generated leaf fixes actual installed receiver resolution, not a general guarantee that host plugins import outside their host resolver.
26
+
27
+ ### Receiving ownership and two-remote byte route (Rulings22–25)
28
+
29
+ SSH and canonical Docker/Incus oneshots share the portable receiving gate/control/work owner, preserving official jobs and Agent/plugin ownership. Container helpers additionally self-enable unprivileged child-subreaping before work to reap only their own/adopted descendants. Actual receiving helper/group cleanup precedes settlement; unavailable final transport drain is diagnosed honestly. Prerequisites and three-startup cost are explicit in the tool Surface.
30
+
31
+ Ruling25 supersedes the original two-remote source -R/private-master/sshd-FD/pidfd design after actual default dev3 source failed its permission requirement. A task-owned portable Python loopback proxy, inside the existing receiving helper/group/lease, carries bounded binary frames over authenticated SSH work stdio to an origin TCP bridge into the destination -L tunnel. A real full-route @RSYNCD greeting precedes business admission. No source Linux /proc, pidfd, privileged sshd access or source forwarding permission is required; native find, native rsync and the existing portable Bash/Python/fcntl/shared-monotonic prerequisites remain. No origin Python, credentials on peers, copy fallback, lease increase or second registry. Five-byte type/length frames cap data at64KiB and Python queues at256KiB; one active connection, real backpressure and half-close, prior reader/socket close before next OPEN. Ordinary two-remote transfer measures12 SSH startups: source preflight+inspection clock/work3, relay source/destination preflight2, destination clock/daemon+tunnel2, source proxy clock/owner/work3, native source rsync clock/work2. Fault recovery adds only task-owned authenticated cleanup reconnects, never transfer retries. Same real helper/listener/group/metadata/tunnel/socket cleanup barrier; receiving progress remains an explicit assumption. Historical M permission failure remains in the acceptance report; current default-dev3-source source test passes, installed next-artifact proof is separate.
32
+
33
+
34
+ ### CLI binary receiving ownership (consumer correction, Ruling26)
35
+
36
+ Actual N native SSH copy cancellation settled while the remote import process and its unnamed staging file remained live under a finite work-channel EOF hold. CLI export/import/digest now reuse the canonical portable receiving owner, with raw binary work streams and the same counted lease, admission gate and authenticated recovery. Cancellation/failure waits for receiving helper/group/temp cleanup before local transport teardown/settlement. Normal success must completely drain the binary stream; the oneshot text lane's 2500ms incomplete-output rule does not apply. Archive validation, selected manifest digests, merge/no-delete behavior and canonical SSH identity/ProxyJump are unchanged. Local and Hub file lanes are unchanged.
37
+
38
+ This intentionally replaces the former Python-only minimum for CLI peers: Bash4.1+, Python3.7+ (macOS3.10+), Unix fcntl.flock/shared non-adjustable cross-process monotonic clock, mktemp/mv/rm/sleep; Linux containers also need stdlib ctypes and permitted unprivileged PR_SET_CHILD_SUBREAPER. No rsync or origin Python requirement, auto-install, second registry, fallback, peer polling or increased lease. Minimal Python-only peers no longer support this CLI lane; use a supported peer or authenticated Hub.
39
+
40
+ Each remote export/import/digest costs three native transport starts: clock, independent owner/control, literal binary work. Thus verified local→CLI or CLI→local costs six; CLI→CLI twelve. verify:false skips digest operations, reducing counts to three/three/six. Local/Hub operations do not contribute CLI starts. Fault recovery may add same-identity authenticated cleanup starts and may remain pending until genuine cleanup observation is possible. Artifact O must re-prove installed cancellation and complete binary integrity; N failures remain historical evidence.
@@ -0,0 +1,56 @@
1
+ # Remote tools
2
+
3
+ Better Dash and Super Dash each provide the native `remote` tool and REPL route. The first enabled provider owns each Agent; unloading it does not hot-transfer ownership. Start a new Agent or cold-resume after changing providers. Super works without Better; Hub transport is optional. See [ADR 0015](adr/0015-independent-bundles-agent-remote.md).
4
+
5
+ ## Choose a call
6
+
7
+ | Goal | Input |
8
+ | --- | --- |
9
+ | List locally known targets without dialing | `{}` |
10
+ | Probe one target | `{target:"ssh:alias"}` |
11
+ | Register an Agent-local SSH identity | `{target:"byossh:build",ssh:{host:"host",user:"user",key:"/origin/key"}}` |
12
+ | Stateless command and stdin | `{target:"byossh:build",cmd:"cat",stdin:"hello"}` |
13
+ | Stream a local script to fixed `bash -s` | `{target:"ssh:alias",script:"/absolute/script.sh"}` |
14
+ | Persistent shell | `{target:"ssh:alias",mode:"pty",cmd:"cd /tmp; export CHECK=ready"}` |
15
+ | Official background job | `{target:"ssh:alias",cmd:"long-command",run_in_background:true}` |
16
+ | Native CLI copy | `{action:"copy",src:"/absolute/source",dst:"ssh:alias:/absolute/destination",argv:["-p"]}` |
17
+ | Native rsync | `{action:"rsync",src:"/absolute/source/",dst:"ssh:alias:/existing/destination/",argv:["-a","--partial","--dry-run"]}` |
18
+
19
+ `script` cannot combine with cmd/stdin/spawn/PTY. Direct local command execution is not provided; use native local shell tools. Background jobs are oneshot-only, require the official jobs service/controller, and are read/killed with `job_output`/`job_kill`; they have no default foreground timeout. An explicit timeout still applies. SSH keys/config stay on the origin; BYO identities are not persisted across new/cold Agents. PTY backlog recovery and live probes are not part of this release.
20
+
21
+ Native/PTY output retains at most 4,000,000 UTF-16 units per stream; Hub oneshot retains 4 MiB bytes. Normal returned tails are smaller. Read `outputStats` units and dropped/returned counters rather than treating character counts as bytes. Discarded output is not recoverable after restart.
22
+
23
+ ## File operations: native CLI semantics
24
+
25
+ `remote` addresses targets once (`ssh:<name>`, `byossh:<label>`, `docker:<name>`, `incus:<name>`, `hub:<machine>`, local paths) and hands the actual transfer to the native CLI. `argv` is an ordered array of native option tokens — one element = one argv token, appended verbatim before the parsed `src`/`dst`. There is no option whitelist, no shell parsing, and no forced defaults: no hidden `--dry-run`, no forced archive/timeout, no automatic verification. A dry run means passing `--dry-run` yourself; unknown or incompatible options fail in the native CLI and its exit code and stderr come back as-is. Success of a native command is never reported as independently verified — `verified:false` on native copy receipts means exactly that.
26
+
27
+ ### copy routes
28
+
29
+ | Route | Command | Notes |
30
+ | --- | --- | --- |
31
+ | local↔SSH | `scp` | default SFTP; caller may pass `-O` explicitly |
32
+ | SSH↔SSH | `scp -3` (data through the origin) | both identities stay on the origin; differing BYO identities are separated by a temporary origin-only SSH config, removed afterwards. `scp -R` is refused before start: it would authenticate from the source host |
33
+ | local↔Docker | `docker cp` | |
34
+ | local↔Incus | `incus file push/pull` | |
35
+ | other cross-endpoint pairs (SSH↔Docker, Docker↔Incus, …) | two native commands with origin staging | each half takes its own options via `pullArgv`/`pushArgv` (mutually exclusive with `argv`); the receipt states both commands |
36
+ | Hub↔local↔local | existing staged archive lane | the only lane where `verify`/`mkdir` apply; it keeps SHA-256 verification and destination-parent creation. CLI-option fields are rejected on this lane |
37
+
38
+ The native CLIs do not create destination parents or verify results on their own; receipts report each command's exit, stdout/stderr and the sanitized argv. Two-segment routes stage in an origin scratch directory that is removed on normal exit (`cleanup:"released"`); an interrupted channel is reported as `cleanup:"unknown"`.
39
+
40
+ ### rsync routes
41
+
42
+ rsync supports SSH/byossh endpoints with at most one local endpoint. Local↔SSH runs `rsync` on the origin with the endpoint's SSH identity wired through `RSYNC_RSH`; an explicit `-e`/`--rsh` in `argv` replaces that wiring per rsync's own rules (the command must then carry its own port/key/config). Nothing else is injected — no archive, no timeout, no dry-run, no remote owner, no `--rsync-path`.
43
+
44
+ SSH↔SSH prefers the native forwarding route: the destination runs a temporary loopback-only rsync daemon, `ssh -L` brings it to an origin port, `ssh -R` carries it onto the source's loopback, and the source's own native rsync client performs one protocol conversation through the chain. The origin only forwards rsync protocol bytes; the destination's existing file data feeds real delta matching (`--stats` shows Matched data dominating for a small change on a large file). If an endpoint refuses TCP forwarding (`AllowTcpForwarding no`), a standard-stream route would be attempted; on the current OpenSSH/rsync combination (10.2 / 3.4.1) that route reports itself unavailable — the error says so and nothing is copied. rsync never falls back to a full copy or to Python.
45
+
46
+ Tool-owned daemons/forwards/SSH sessions are torn down on normal exit and observable cancellation; a lost channel is reported with `cleanup:"unknown"` rather than a false success claim.
47
+
48
+ ### Prerequisites
49
+
50
+ File operations require the corresponding CLI on the origin (`scp`, `rsync`, `docker`, `incus`) and on the SSH/container endpoints that execute it (`rsync` for rsync routes; none beyond the container's own CLI for docker/incus copy). No remote Python, Node or worker is deployed for file transfers. The dual-SSH forwarding route requires `AllowTcpForwarding` on both endpoints. BYO-PTY is not a file endpoint.
51
+
52
+ ## One-shot command requirements
53
+
54
+ One-shot SSH/byossh commands (foreground, background, script and literal stdin) run through ONE native SSH exec: the command executes under bash with a small POSIX wrapper whose TERM/HUP trap kills the command's whole process group, so backgrounded children do not outlive the session teardown. Remote prerequisites: bash (POSIX) — **no Python, no owner/lease protocol, no flock/monotonic clock, no extra sessions**. On normal completion the wrapper's EXIT trap reaps the command's whole process group (backgrounded children included — they neither outlive the call nor hold the channel open), and the exit code passes through verbatim. On timeout/cancellation the local transport dies at once and a dedicated cleanup session reads the pidfile the wrapper left and kills the group (TERM→KILL); sshd delivers no signal to a no-PTY session, so this explicit cleanup is the remote kill path.
55
+
56
+ Docker/Incus one-shots use the same pure-CLI lane (`docker exec` / `incus exec` with the same wrapper); container prerequisites are the container's own bash. PTY paths are unchanged (`ssh -tt bash`, docker via script(1), incus interactive) and never required Python.
@@ -1,53 +1,25 @@
1
- # kernel-provisioning Specification
1
+ # Existing Python interpreter discovery
2
2
 
3
- ## Purpose
4
- better-dsh 的 REPL kernel(IPython)供给契约:独占 managed venv(插件包目录内,`kernelEnvDir` 可显式覆盖)、版本锁定(ipykernel 7.3.0 / dill 0.4.1,uv 与 pip 双路径)、三级触发供给(postinstall 加速器 / daemon spin-up 主路径 / 首用 lazy 兜底)全 fail-open 且幂等、供给状态可观测;npm 包不内嵌预建 venv(供给能力随包,供给结果不随包)。
3
+ Updated 2026-10-12 by explicit user correction. This replaces the former iKernel managed-provisioning contract. Execution and recovery are specified in [OMP Python REPL](../omp-python-repl-port/spec.md); consumer behavior is in its [runtime guide](../omp-python-repl-port/runtime-guide.md).
5
4
 
6
5
  ## Requirements
7
6
 
8
- ### Requirement: Exclusive managed kernel venv
7
+ ### Existing interpreters only
9
8
 
10
- The plugin SHALL provision its REPL kernel interpreter exclusively inside a managed venv rooted in the plugin's own package directory (web-profile scope in production deployments, `kernelEnvDir` as the explicit override), and SHALL NOT install into, mutate, or depend on the user's global Python environment; kernel probes MUST NOT import user-site modules, and the distributed npm package MUST NOT embed a prebuilt venv (provisioning capability ships, not provisioning results).
9
+ Better SHALL run an existing Python interpreter. It SHALL NOT invoke uv/pip, download Python, create or repair a virtual environment, or place Python environment/cache state in the installed npm package. No CPython implementation or Python3.11 preference is applied. The packaged dill0.4.1 checkpoint helper requires Python >=3.9.
11
10
 
12
- #### Scenario: Venv lives inside the plugin territory
11
+ ### Explicit selection
13
12
 
14
- - **WHEN** the plugin is installed into a web profile and the kernel is provisioned
15
- - **THEN** the venv root is inside the plugin's package directory (or the explicitly configured `kernelEnvDir`), and no file outside that territory is required for kernel operation
13
+ An explicit `python` executable name/path SHALL bypass automatic discovery. Bare `python3` is an explicit executable name, not a provisioning sentinel. Relative paths SHALL resolve against the session workspace and `~/` against the user's home. An unusable explicit selection SHALL report the actual failure without silently substituting another interpreter. Existing explicit virtual environments SHALL receive their activation environment.
16
14
 
17
- #### Scenario: User global environment untouched
15
+ ### Automatic discovery
18
16
 
19
- - **WHEN** provisioning runs on a host with an existing global Python installation
20
- - **THEN** no package is installed into, and no configuration of, the user's global environment is modified
17
+ Without explicit selection, Better SHALL probe an existing active virtual/Conda environment or workspace `.venv`/`venv`, then PATH `python3` and `python`. A broken automatically discovered candidate SHALL permit a working fallback without repair. Resolved executable identity and activation environment SHALL reach the spawned child. One session's project interpreter SHALL NOT be cached as the choice for another workspace.
21
18
 
22
- ### Requirement: Robust provisioning with pinned versions
19
+ ### Readiness and failure
23
20
 
24
- Provisioning SHALL redirect its tool caches (uv) to a plugin-territory location so read-only home directories cannot block it, SHALL fall back to `python3 -m venv` + ensurepip when `uv` is unavailable, and SHALL install pinned, tested versions of `ipykernel` and `dill` alongside the pinned CPython version; provisioning MUST be idempotent (a complete venv is reused; a partial one is repaired).
21
+ Availability is checked when the session needs its Python child. Plugin mounting SHALL NOT provision an environment. With no working candidate, the error SHALL identify attempted interpreters and explain how to install Python or configure an existing executable. `npm run kernel:check` SHALL perform discovery only, with no installation side effects.
25
22
 
26
- #### Scenario: Read-only home cache does not block
23
+ ### Runtime state and migration
27
24
 
28
- - **WHEN** the kernel is provisioned on a host whose `~/.cache` is read-only
29
- - **THEN** provisioning succeeds using the redirected cache location
30
-
31
- #### Scenario: Pinned versions installed
32
-
33
- - **WHEN** provisioning creates or repairs the venv
34
- - **THEN** the installed `ipykernel` and `dill` match the pinned tested versions, verifiable by the kernel probe
35
-
36
- ### Requirement: Provisioning ladder — install-time, daemon spin-up, first-use
37
-
38
- The plugin SHALL provision the kernel through a three-trigger ladder converging on the same managed venv, every trigger fail-open (a failure or suppression at any level degrades to a logged status message and NEVER blocks package installation, daemon startup, or plugin mounting, and never asks the user for approval): (1) an install-time postinstall best-effort step that runs only when the package manager executes build scripts; (2) a daemon spin-up check as the PRIMARY path — when the plugin is brought up on the host plane by the host daemon, it asynchronously verifies the kernel environment and provisions it immediately when absent (uv → `python3 -m venv` → explicit configuration), so an agent session never discovers a missing kernel at first use; (3) first-use lazy provisioning as the final fallback. Provisioning status (provisioning/ready/degraded/failed) SHALL be logged, and failure messages MUST name the available remedies (`npm run kernel:venv`, `kernelAutoInstall`, explicit `python`).
39
-
40
- #### Scenario: Daemon spin-up provisions before any agent session
41
-
42
- - **WHEN** the host daemon starts and brings the plugin up on the host plane, and the managed venv is absent
43
- - **THEN** the plugin asynchronously provisions the kernel within the spin-up window without blocking daemon startup or plugin mounting, and a new agent session's first REPL cell finds the kernel already in place
44
-
45
- #### Scenario: Suppressed postinstall still reaches a working kernel
46
-
47
- - **WHEN** the package manager skips the plugin's postinstall script and the daemon later starts
48
- - **THEN** the spin-up check provisions the kernel and REPL cells execute, with the install itself never having failed and no approval ever having been requested
49
-
50
- #### Scenario: Failure is non-blocking and visible
51
-
52
- - **WHEN** provisioning fails on a constrained host at any trigger
53
- - **THEN** the installation, daemon startup, and plugin mounting all succeed; the surfaced error names the concrete remedy commands/configuration paths
25
+ Checkpoints SHALL remain under the native profile home (or explicitly configured snapshot directory), isolated by profile/session. Old `kernelEnvDir`, `kernelPythonVersion`, `kernelAutoInstall` fields and `kernel:venv` command are removed. Existing package-local `.venv-kernel`/`.uv-cache` files SHALL be left untouched and SHALL NOT be selected automatically. An interpreter/environment change SHALL pass the existing checkpoint compatibility checks and report incompatible recovery explicitly; no user cell is replayed.
@@ -0,0 +1,35 @@
1
+ # Better Dash Python REPL execution and recovery
2
+
3
+ Implementation and acceptance are tracked in the [plan](../../superpowers/plans/2026-10-12-omp-python-repl-port.md). The [spec](spec.md) defines the recovery boundary; this guide explains the consumer contract being implemented.
4
+
5
+ Better runs a persistent Python child for each DSH session. Node communicates with an unchanged, pinned OMP runner over JSON lines. Better owns tool bindings, cancellation, output mapping and checkpoint management. Interpreter discovery reuses an explicitly configured interpreter, otherwise an active/project environment then PATH `python3` or `python`. It probes candidates and falls back from broken automatically discovered environments; it never invokes uv/pip, installs Python or creates a venv. Explicit relative paths and project environments resolve against the session workspace. Python >=3.9 is required by the packaged dill0.4.1 checkpoint helper; no3.11 or CPython implementation is selected. Better ships the Python runner and dill 0.4.1 source. It does not install Jupyter, IPython, ZeroMQ or dill into the consumer's interpreter. OMP's Agent-specific prelude is not exposed as a DSH feature.
6
+
7
+ Variables, functions, classes and imports remain available across cells while the child lives. Top-level await works; a top-level return is invalid. The final expression supplies the completion value and print supplies logs. Host tools remain callable with `await tool.<name>({...})`. Internal initialization and checkpoint cells do not appear as user results or user scratchpad variables.
8
+
9
+ `tool.read` forwards to the current DSH native read tool with its usual permissions, scheduling and results. Better supplies the call-site helper required by OMP's compiler without enabling OMP speculative reads. Interactive `input()` is unsupported and returns an actionable error. Output uses the existing text/log/result contract; a nontext-only display produces an explicit MIME notice.
10
+
11
+ `npm run kernel:check` checks existing interpreter availability without provisioning. Legacy provisioning fields `kernelEnvDir`, `kernelPythonVersion`, `kernelAutoInstall` and the `kernel:venv` command are removed. Old package-local environments/caches are left untouched and are not auto-selected. An interpreter change may make existing checkpoints incompatible; recovery reports that condition explicitly.
12
+
13
+ ## Checkpoint boundary
14
+
15
+ In a native DSH profile, checkpoints belong under that profile's resolved DSH home and are isolated by profile and session. An explicitly configured checkpoint directory takes precedence; `snapshotDir: ""` disables persistence and leaves process-local state only. A standalone runtime without a native profile must explicitly choose a checkpoint directory.
16
+
17
+ A completed cell attempts a new checkpoint, including a cell that raises after changing variables. Saving a checkpoint can fail or skip values. Better reports the coverage and latest committed cell instead of claiming complete process recovery. Aliases and dependencies among retained values must survive as one object graph; values whose dependencies cannot safely be preserved are omitted or the checkpoint is rejected.
18
+
19
+ The serializer does not preserve runner state, host tool bindings, open files, sockets, subprocesses, threads, locks or database connections. Tool bindings are recreated from the current host. Current directory, environment changes, module caches and library global state are not generally restored. File modifications and network/tool side effects have their own real sources and are never replayed or rolled back by a variable checkpoint.
20
+
21
+ The checkpoint manifest records format, completed-cell sequence, interpreter/environment identity, payload size/integrity, saved names and omissions. Only a complete committed generation is eligible for restoration. A failed attempt before commit preserves the previous valid generation. If interruption prevents commit confirmation, the result reports that uncertainty; the manifest remains the authority on the latest complete generation. Restore validates the owned session checkpoint before loading it and runs once before admitting a new user cell. Damage or incompatibility produces an explicit empty-state notice. A partial restore lists the missing coverage.
22
+
23
+ `reset: true` discards the session child and its persisted checkpoints. Do not use it when you intend to retain state.
24
+
25
+ ## After a crash or interruption
26
+
27
+ Read the recovery notice, check the latest successful checkpoint and inspect real external state. A cell that died after starting may already have performed side effects. Better does not automatically retry it and does not instruct blind replay. Recreate omitted resources and rerun only the operations you determine are still needed.
28
+
29
+ Snapshots are private executable serialization, not an interchange format. Do not copy arbitrary external dill files into the checkpoint directory or treat them as data imports. The owned directory, session isolation and permissions are part of the recovery contract.
30
+
31
+ ## Delivery verification
32
+
33
+ The original OMP runner, its MIT license and source/hash provenance travel inside the npm package. Vendored dill carries its version, original source hashes and BSD-3-Clause license. The build verifies these resources and copies them beside compiled JavaScript; consumers never execute TypeScript or load OMP/Bun packages.
34
+
35
+ Acceptance uses the normal dual tarball launcher and a dev3 test installation. Source tests, successful build or a live Python process alone are insufficient. The final test report records exact artifacts and the observed model-visible recovery behavior.