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.
- package/README.md +49 -178
- package/cordis.patch.yml +4 -35
- package/docs/adr/0015-independent-bundles-agent-remote.md +40 -0
- package/docs/remote-tools.md +56 -0
- package/docs/specs/omp-python-repl-port/runtime-guide.md +33 -0
- package/docs/specs/omp-python-repl-port/spec.md +61 -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-kGXcZOe0.js → LaunchOptions-5eg7E0ye.js} +1 -1
- package/lib/LaunchOptions-DxQQ0s-A.js +4 -0
- package/lib/{bidi-BalpKaJc.js → bidi-CwabFH07.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-B-9jm12n.js} +4 -4
- package/lib/index.d.ts +17 -41
- package/lib/index.js +473 -1678
- package/lib/kernel-env-BOF0fNHn.js +91 -0
- package/lib/kernel-env.d.ts +4 -58
- 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-CupOhMp5.js → main-BeCgUd20.js} +8 -6
- package/lib/{main-BQeFrBM1.js → main-Dv_UQqG2.js} +1 -1
- 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-Dnd7eqel.js} +9 -9
- 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 +12 -12
- 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-CwZrTBcl.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 +6 -23
- package/scripts/kernel-provision.mjs +4 -34
- 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/register-bundle.mjs +0 -80
package/README.md
CHANGED
|
@@ -1,196 +1,70 @@
|
|
|
1
|
-
> Part of [Super D](../README.md).
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
75
6
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
79
10
|
|
|
80
|
-
The
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
development and tests, pre-create the venv:
|
|
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
|
|
86
16
|
|
|
87
17
|
```sh
|
|
88
|
-
npm
|
|
18
|
+
npm install better-dsh
|
|
89
19
|
```
|
|
90
20
|
|
|
91
|
-
|
|
92
|
-
|
|
21
|
+
The runtime requires Python >=3.9. Configure `python` (or the bundle's `DASHR_KERNEL_PYTHON` hint) to select one. The default `python3` compatibility path prepares a managed environment using uv or system Python, without kernel package installs. uv may download the selected interpreter when it is absent; an explicit interpreter path uses your existing installation. Better probes the interpreter and ships the runner, checkpoint helper and dill0.4.1 sources; it does not run pip to install kernel packages. Python is not bundled by this change. A missing or unsupported interpreter produces an actionable error.
|
|
22
|
+
|
|
23
|
+
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
24
|
|
|
94
25
|
## Configuration
|
|
95
26
|
|
|
96
|
-
|
|
27
|
+
Runtime fields remain configurable through the ordinary Cordis row. The implementation's schema is authoritative; the principal controls are:
|
|
97
28
|
|
|
98
29
|
| Field | Default | Meaning |
|
|
99
30
|
| --- | --- | --- |
|
|
100
|
-
| `python` | `python3` |
|
|
101
|
-
| `startupTimeoutMs` | `30000` | Budget for
|
|
102
|
-
| `runTimeoutMs` | `120000` | Wall budget per
|
|
103
|
-
| `interruptGraceMs` | `2000` | Grace
|
|
104
|
-
| `interruptConfirmMs` | `250` |
|
|
105
|
-
| `disposeTimeoutMs` | `5000` |
|
|
106
|
-
| `snapshotTimeoutMs` | `30000` | Budget for internal
|
|
107
|
-
| `maxOutputBytes` | `67108864` |
|
|
108
|
-
| `snapshotDir` |
|
|
109
|
-
| `snapshotSizeCapBytes` | `268435456` |
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
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.
|
|
31
|
+
| `python` | `python3` | Interpreter selection; no Jupyter installation requirement. |
|
|
32
|
+
| `startupTimeoutMs` | `30000` | Budget for child startup and initialization. |
|
|
33
|
+
| `runTimeoutMs` | `120000` | Wall budget per user cell. |
|
|
34
|
+
| `interruptGraceMs` | `2000` | Grace after an interrupt before terminating an unresponsive child. |
|
|
35
|
+
| `interruptConfirmMs` | `250` | Delay before confirming an interrupt that has not finished the cell. |
|
|
36
|
+
| `disposeTimeoutMs` | `5000` | Bound on graceful child teardown before forced cleanup. |
|
|
37
|
+
| `snapshotTimeoutMs` | `30000` | Budget for internal save/restore operations. |
|
|
38
|
+
| `maxOutputBytes` | `67108864` | Cap for serialized logs, completion and error output; overflow is reported. |
|
|
39
|
+
| `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. |
|
|
40
|
+
| `snapshotSizeCapBytes` | `268435456` | Checkpoint payload cap; failed or over-cap saves preserve the previous committed generation. |
|
|
41
|
+
| `kernelEnvDir` | `<package>/.venv-kernel` | Managed environment location used by the `python3` compatibility selection. |
|
|
42
|
+
| `kernelPythonVersion` | `3.11` | Interpreter version requested from uv for a new managed environment. |
|
|
43
|
+
| `kernelAutoInstall` | `true` | Allow creation of the managed environment on first use. |
|
|
44
|
+
| `username` | `dashr` | Retained compatibility field. |
|
|
174
45
|
|
|
175
|
-
##
|
|
46
|
+
## Cells and recovery
|
|
176
47
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```
|
|
48
|
+
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`.
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
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
53
|
|
|
184
|
-
|
|
185
|
-
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
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).
|
|
59
|
+
|
|
60
|
+
## Testing
|
|
186
61
|
|
|
187
62
|
```sh
|
|
188
|
-
|
|
63
|
+
npm run typecheck
|
|
64
|
+
DASHR_TEST_PYTHON=python3 npm test
|
|
189
65
|
```
|
|
190
66
|
|
|
191
|
-
|
|
192
|
-
kernels once exhausted machine memory while every unit test stayed green —
|
|
193
|
-
blueprint §10.8/§10.9.)
|
|
67
|
+
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
68
|
|
|
195
69
|
---
|
|
196
70
|
|
|
@@ -198,7 +72,7 @@ blueprint §10.8/§10.9.)
|
|
|
198
72
|
|
|
199
73
|
The DASHR agent-plane presentation (blueprint §7.4): the plugin half that
|
|
200
74
|
presents the persistent-kernel runtime to the model as **cells on one
|
|
201
|
-
persistent
|
|
75
|
+
persistent Python runner**. It contributes:
|
|
202
76
|
|
|
203
77
|
- **`eval`** — the cell transport tool: one call = one cell on the calling
|
|
204
78
|
session's kernel (the `ctx.replRuntime` service). Variables, imports, and
|
|
@@ -303,15 +177,12 @@ unknown id, or an infrastructure rejection all map to an `error` string.
|
|
|
303
177
|
|
|
304
178
|
```sh
|
|
305
179
|
npm install
|
|
306
|
-
|
|
180
|
+
export DASHR_TEST_PYTHON=python3 # or an explicit supported interpreter
|
|
307
181
|
npm run typecheck
|
|
308
182
|
npm test # vitest --run
|
|
309
183
|
```
|
|
310
184
|
|
|
311
|
-
The suite needs a Python interpreter
|
|
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`.
|
|
185
|
+
The suite needs a supported Python interpreter; the runner and checkpoint serializer are supplied by Better. `DASHR_TEST_PYTHON` selects the test interpreter.
|
|
315
186
|
|
|
316
187
|
## Relationship to upstream
|
|
317
188
|
|
package/cordis.patch.yml
CHANGED
|
@@ -14,13 +14,10 @@
|
|
|
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
|
-
#
|
|
18
|
-
#
|
|
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:
|
|
@@ -72,7 +69,7 @@
|
|
|
72
69
|
- id: dashr-web-password
|
|
73
70
|
name: 'better-dsh/web-password'
|
|
74
71
|
|
|
75
|
-
#
|
|
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: []
|
|
@@ -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.
|
|
@@ -0,0 +1,33 @@
|
|
|
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. The runtime requires Python >=3.9; the compatibility managed-environment path may let uv download that interpreter, while an explicit path uses an existing installation. 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
|
+
## Checkpoint boundary
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
`reset: true` discards the session child and its persisted checkpoints. Do not use it when you intend to retain state.
|
|
22
|
+
|
|
23
|
+
## After a crash or interruption
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
## Delivery verification
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
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.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Better Dash Python REPL:移植 OMP 执行链与快照边界
|
|
2
|
+
|
|
3
|
+
状态:两阶段已实现并完成 `0.3.0-s` 整包消费者验收;整体验审发现的检查点目录失败边界已修复,37 项相关回归与两条类型检查通过;`0.3.0-t` 在 PC 与 dev3 的实装增量复验也已完成。尚未获得发布放行。2026-10-12 user 明确批准第一、第二阶段一起实施。原调研日期:2026-10-11。见 [执行计划](../../superpowers/plans/2026-10-12-omp-python-repl-port.md) 与 [验收报告](../../test-reports/2026-10-12-omp-python-repl-port.md)。
|
|
4
|
+
|
|
5
|
+
## 1. 方向
|
|
6
|
+
|
|
7
|
+
“抄 OMP”指将所需源码复制到 Better Dash 自有源码中,完成本地 Node、DSH、Cordis 接口适配并随 `better-dsh` 交付;不 import OMP 包,也不要求消费者安装 Bun。现有 AST device 已有同类剥离先例:`better-dsh/src/devices/ast/ast-device.ts`。
|
|
8
|
+
|
|
9
|
+
第一阶段以 OMP Python REPL 的运行语义为基线,**`runner.py` 逐字节复制,保持原文件不改**。复制的 Python runner 本身不依赖 IPython、ipykernel、Jupyter 或 ZeroMQ,只使用 Python 标准库。根据实际采用的便利函数,将 OMP `prelude.py` 一同纳入并注明来源;它和 OMP 专有的 Agent 桥接需要在 Better 的工具面上作取舍。宿主侧从 OMP 的 TypeScript kernel、共享 IPC 与会话管理中抽取所需逻辑,接上 Better 的 `ReplRuntime`、工具绑定、输出、取消、reset 和生命周期接口。`Bun.spawn` 换成 Node `child_process.spawn`;Bun 的脚本文本导入换成包内 `.py` 资源路径。Better 的 TS 在构建时编译为 JS,运行时不执行 TS。
|
|
10
|
+
|
|
11
|
+
新路径是 `Better/Node → stdin/stdout JSON 行 → Python runner → Python 解释器`。因此可移除这条路径上的 IPython、ipykernel、Jupyter client 和 ZeroMQ 需求;**Python 解释器仍是运行前提**。是否随包交付解释器是另一项决策,不由移植 OMP 自动解决。第一阶段按 OMP 策略保留进程内会话状态,不承诺进程重启后的变量恢复。
|
|
12
|
+
|
|
13
|
+
复制时固定一个 OMP commit,保存原始文件、许可证和来源记录;若 `runner.py` 原样复制,校验其字节哈希。本文调研时的 OMP HEAD 为 `fa5ff4a3a977a28b93d025f4cf50f046e019f18c`,实施时应以实际复制的 commit 为准。MIT 许可声明随交付物保留。
|
|
14
|
+
|
|
15
|
+
资料:[OMP Python REPL 说明](https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/python-repl.md)、[runner.py](https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/packages/coding-agent/src/eval/py/runner.py)、[TS kernel](https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/packages/coding-agent/src/eval/py/kernel.ts)、[共享 IPC](https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/packages/coding-agent/src/eval/kernel-base.ts)、[MIT 许可](https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/LICENSE)。
|
|
16
|
+
|
|
17
|
+
## 2. “快照恢复”恢复什么
|
|
18
|
+
|
|
19
|
+
快照是**会话运行时 Python 用户命名空间的一份持久检查点**,用于 Python 子进程死亡、插件重启或同一会话恢复时,让新进程尽量接续旧 REPL 中由模型创建的值。例如 `x = 3`、列表、字典、对象实例、可序列化的函数或类,以及 Better 经 `setVar` 放入命名空间的值。它不是模型聊天记录或 compaction 摘要;那些属于 DSH 会话数据。也不是“把整个 Python 进程冻住”。
|
|
20
|
+
|
|
21
|
+
| 状态 | 是否属于快照目标 | 说明 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| 每会话的用户变量及其可序列化对象图 | 是,best effort | 核心恢复对象;按会话键隔离 |
|
|
24
|
+
| 检查点序号、格式、解释器及环境标识、跳过项 | 是 | 判断能否加载、丢失了多少轮、向模型准确报告 |
|
|
25
|
+
| Better 的宿主工具绑定、prelude、IPC、事件循环 | 否 | 新 runner 启动时重新建立;不得把旧进程句柄反序列化 |
|
|
26
|
+
| DSH 对话、工具日志、compaction、文件内容 | 否 | 各有独立持久化和真实来源;快照不复制它们 |
|
|
27
|
+
| 打开的文件/套接字、子进程、线程、数据库连接、锁 | 否 | 与旧 OS 进程绑定;恢复后需重新打开 |
|
|
28
|
+
| `cwd`、环境变量、`sys.path`、模块缓存和库内部全局状态 | 默认不保证 | 启动配置可重新应用;用户代码造成的任意进程副作用不能由变量快照完整重建 |
|
|
29
|
+
| 对外副作用,如文件修改、网络请求、工具调用 | 否 | 已发生的副作用不可通过恢复变量撤销,也不可盲目重放代码 |
|
|
30
|
+
|
|
31
|
+
特别要区分:会话**活着时**,OMP runner 中的变量自然跨 cell 保留;需要快照的是**旧 runner 退出、新 runner 启动**这个边界。OMP 自身的 compaction 只提供有限的运行时提示,不恢复新进程中的历史变量值。[OMP eval 文档](https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/tools/eval.md)
|
|
32
|
+
|
|
33
|
+
## 3. 若第二阶段加快照:技术边界
|
|
34
|
+
|
|
35
|
+
快照功能放在 Better 自有扩展层,仍保持复制的 `runner.py` 字节不变:在没有用户 cell 并发执行的时点,由宿主发送内部 cell,在同一用户 namespace 中读取 `globals()`、过滤 runner/prelude/Better 内部绑定,并调用随包交付的快照辅助逻辑。恢复时先启动 runner、完成初始化与 prelude,再运行内部恢复 cell,最后允许用户 cell 进入。内部 cell 必须不污染可见结果和用户变量清单。若这一方式经实测无法满足隔离要求,再单独裁决是否改 runner;不能暗中把“原样复制”改成 fork。
|
|
36
|
+
|
|
37
|
+
触发点:完成的用户 cell 后尝试保存最新检查点;正常关闭时可再尝试一次。`reset` 清除内存与磁盘检查点。异常终止时只使用**已经完整提交**的最近检查点,不重放死亡时的 cell,也不假装它执行成功。检查点写入临时文件、验证大小与格式后再原子替换;失败或超限保留上一份可用检查点。每个会话单独存放,首次启动或故障重启只尝试恢复一次。
|
|
38
|
+
|
|
39
|
+
### 3.1 `dill` 随包源码交付的裁决
|
|
40
|
+
|
|
41
|
+
以 Better 当前锁定的 `dill==0.4.1` 为基线,将其 Python 包源码复制到 Better 的交付资源中,让子进程直接从包内路径 `import dill`,不在消费者机器上执行 `pip install dill`。保留上游 BSD-3-Clause 许可、版本和来源记录。优先保持 vendored 源码原样;Better 特有的 namespace 过滤、检查点格式和错误报告放在自有快照辅助层,避免改动 dill 内部。`dill` 自身不是解释器,Python 解释器仍需由宿主或另行交付。
|
|
42
|
+
|
|
43
|
+
包体积实测(PyPI 的 0.4.1 `py3-none-any` wheel):压缩 wheel **120,019 bytes**;其中 `dill/` 目录的 46 个 `.py` 文件展开为 **350,717 bytes**,排除自带测试后 14 个 `.py` 文件为 **245,541 bytes**。这些是 Python 源文件体积,不含打包后的 npm 压缩率或解释器体积。wheel 元数据没有默认 `Requires-Dist`;`objgraph`、`gprof2dot` 是可选诊断功能的 extra。PyPI 页面列出的 `setuptools` 是构建/安装要求,不是把已复制的 `dill/` 源码导入并运行时须安装的包。上游声明支持 Python/PyPy ≥3.9。资料:[PyPI dill 0.4.1](https://pypi.org/project/dill/0.4.1/)、[dill 源码](https://github.com/uqfoundation/dill)、[BSD 许可](https://github.com/uqfoundation/dill/blob/master/LICENSE)。
|
|
44
|
+
|
|
45
|
+
衔接点是 OMP runner 的用户 namespace,与 IPython 或 Jupyter 协议无关。在 2026-10-11 的历史隔离探针中,未修改的 OMP `runner.py` 使用随进程路径提供的 dill 0.4.1:第一个 Python 子进程创建 `x = [1, 2]` 与引用 `x` 的 lambda,内部 cell 用 `dill.dump` 保存变量字典;第二个全新子进程用 `dill.load` 和 `globals().update` 恢复,随后 `f(4)` 得到 `5`。这项历史探针只证明基本衔接可行,不能证明任意对象都可恢复。当时尚未经过 Better 真实交付测试;2026-10-12 的 `0.3.0-s` 整包已在 PC4999 与 dev3 独立实装完成原生模型验收,具体结果与限制见上方验收报告。
|
|
46
|
+
|
|
47
|
+
Best effort 不应退回普通标准库 `pickle`,否则对 REPL 函数、闭包等的覆盖可能明显缩小。仍会有 dill 无法序列化或恢复的对象,以及依赖缺失或环境不一致的对象;这些值允许跳过并报告。要注意:把整个变量字典一次 `dill.dump` 时,一个坏值可使整份失败;若要“丢弃坏值并保住其他变量”,须由 Better 自有层实现筛选与部分恢复记录,并验证对象间引用关系。不能只捕获一次异常就声称做到了按变量 best effort。也不能重放任意历史 cell 来假装恢复,因为副作用可能重复发生。
|
|
48
|
+
|
|
49
|
+
`dill.load` 会执行反序列化逻辑,因此只加载本会话受控目录中的检查点,目录权限和会话键隔离属于恢复边界;不要把外来 `.dill` 文件当作数据导入。
|
|
50
|
+
|
|
51
|
+
## 4. Best effort 的具体承诺
|
|
52
|
+
|
|
53
|
+
可以沿用现有 best effort 标准,但必须**如实暴露恢复结果**,不能把“找到快照文件”等同于“状态完整恢复”——当前 `dill` 实现也会因不可序列化对象、超限、超时或环境变化而退化。
|
|
54
|
+
|
|
55
|
+
1. 保存与恢复都允许失败,失败不阻止 REPL 从空命名空间继续运行;失败原因写入日志和模型可见结果。
|
|
56
|
+
2. Manifest 记录检查点轮次、格式、Python/环境标识、大小和保存覆盖情况。恢复前校验;不兼容或损坏时明确报告空状态。
|
|
57
|
+
3. 若只恢复部分变量,应报告“部分恢复”及跳过的名称或数量;**不能报告“已恢复整个 namespace”**。对象间共享引用和依赖关系无法保持时也应视作部分恢复,或整份拒绝。
|
|
58
|
+
4. 模型须知道最新成功检查点的轮次,以及检查点之后哪些 cell 可能丢失;故障时观察到的那次调用不自动重试。
|
|
59
|
+
5. 资源句柄、网络连接和其他进程副作用始终超出承诺;用户代码应能检测和重建这些资源。
|
|
60
|
+
|
|
61
|
+
因此,第二阶段的目标是“尽可能恢复模型的 Python scratchpad 变量,并准确报告缺口”,不是恢复整个 Python 进程。是否加这一阶段,不影响先完成 OMP 执行链移植与去除 Jupyter/ZMQ 依赖。
|