better-dsh 0.0.0 → 0.2.2-b

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 (91) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +294 -4
  3. package/control-prompt.md +37 -0
  4. package/cordis.patch.yml +53 -0
  5. package/docs/00_adr/0001-bridge-tool-layer-not-service-layer.md +14 -0
  6. package/docs/00_adr/0002-masking-is-presentation-only.md +15 -0
  7. package/docs/10_plans/A2A-messaging-channel-test-archive.md +256 -0
  8. package/docs/10_plans/code-mode-vs-rlm-ipython-comparison.md +137 -0
  9. package/docs/10_plans/dashr-blueprint-review.md +201 -0
  10. package/docs/10_plans/dashr-blueprint.md +561 -0
  11. package/docs/10_plans/dashr-compaction-window-and-archive.md +307 -0
  12. package/docs/10_plans/dashr-profile-layer-feasibility.md +367 -0
  13. package/docs/10_plans/dashr-sandbox-escalation-semantics-gap.md +171 -0
  14. package/docs/10_plans/dashr-security-sandbox-analysis.md +187 -0
  15. package/docs/10_plans/dashr-surface-invariant-and-omp-imports.md +97 -0
  16. package/docs/10_plans/ipython-kernel-interactive-interface-test-report.md +152 -0
  17. package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +146 -0
  18. package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +50 -0
  19. package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +79 -0
  20. package/docs/10_plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +138 -0
  21. package/docs/10_plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +161 -0
  22. package/docs/10_plans/kernel-refactoring/V0.1.5-development-plan.md +109 -0
  23. package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +50 -0
  24. package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +113 -0
  25. package/docs/10_plans/recallable-compaction.md +147 -0
  26. package/docs/10_plans/spike-tag-repro.mjs +102 -0
  27. package/docs/10_plans/upstream-analysis.md +128 -0
  28. package/docs/50_test-reports/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +110 -0
  29. package/docs/50_test-reports/kernel-provisioning.md +44 -0
  30. package/docs/50_test-reports/repl-kernel-provisioning-test-report.md +87 -0
  31. package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-local-test-report.md +81 -0
  32. package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-report.md +93 -0
  33. package/docs/50_test-reports/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +142 -0
  34. package/docs/50_test-reports/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +193 -0
  35. package/docs/50_test-reports/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +96 -0
  36. package/docs/50_test-reports/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +127 -0
  37. package/docs/50_test-reports/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +150 -0
  38. package/docs/50_test-reports/v0.1.8d_artifacts/README.md +138 -0
  39. package/docs/50_test-reports/v0.1.8d_artifacts/code-mode-repl-only.observation.md +74 -0
  40. package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +3890 -0
  41. package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +544 -0
  42. package/docs/50_test-reports/v0.1.8d_artifacts/functions.json +592 -0
  43. package/docs/50_test-reports/v0.1.8d_artifacts/skills-catalog.snapshot.md +30 -0
  44. package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.output-schemas.json +1236 -0
  45. package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.python.txt +592 -0
  46. package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.typescript.txt +516 -0
  47. package/docs/50_test-reports/v0.1.8d_artifacts/wire-vs-transcription.diff.md +54 -0
  48. package/docs/50_test-reports/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +224 -0
  49. package/docs/50_test-reports/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +168 -0
  50. package/docs/50_test-reports/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +123 -0
  51. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.lock +7 -0
  52. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.toml +6 -0
  53. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +8 -0
  54. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/main.rs +4 -0
  55. package/docs/50_test-reports/v0.2.0b_artifacts/hashline-probe.md +5 -0
  56. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.lock +7 -0
  57. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.toml +7 -0
  58. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/build.rs +4 -0
  59. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/src/main.rs +13 -0
  60. package/docs/50_test-reports/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +110 -0
  61. package/docs/50_test-reports/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +86 -0
  62. package/docs/50_test-reports/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +66 -0
  63. package/docs/50_test-reports/v0.2.1d-/345/256/236/346/265/213/346/212/245/345/221/212.md +67 -0
  64. package/docs/50_test-reports/v0.2.1e-P1-/345/256/236/346/265/213/346/212/245/345/221/212.md +136 -0
  65. package/docs/50_test-reports/v0.2.1ef-dev-audit-report.md +73 -0
  66. package/docs/50_test-reports/v0.2.1f-plugin-shipped-ui-patches/345/256/236/346/265/213/346/212/245/345/221/212.md +102 -0
  67. package/docs/50_test-reports/v0.2.4-ios-focus-zoom-suppression/345/256/236/346/265/213/346/212/245/345/221/212.md +158 -0
  68. package/docs/60_exploration-and-research/bun-compile-cordis-runtime-bootstrap-research.md +348 -0
  69. package/docs/60_exploration-and-research/cordis-research.md +350 -0
  70. package/docs/60_exploration-and-research/dsh-mobile-spa-ios-input-experience-research.md +160 -0
  71. package/docs/60_exploration-and-research/dsh-web-profile-package-map.md +186 -0
  72. package/docs/60_exploration-and-research/dsh-web-ui-slot-system-research.md +310 -0
  73. package/docs/60_exploration-and-research/dsh-webui-strip-boundary-research.md +300 -0
  74. package/docs/60_exploration-and-research/ios-chat-app-bridge-research.md +324 -0
  75. package/docs/60_exploration-and-research/web-frontend-composability-research.md +191 -0
  76. package/docs/distro-blueprint.md +81 -0
  77. package/docs/dsh-webUI-with-rlm-mode.png +0 -0
  78. package/docs/repositioning-and-rebranding.md +102 -0
  79. package/lib/client/index.js +473 -0
  80. package/lib/index.d.ts +744 -0
  81. package/lib/index.js +11768 -0
  82. package/lib/kernel-env-hxaihi9C.js +195 -0
  83. package/lib/kernel-env.d.ts +80 -0
  84. package/lib/kernel-env.js +3 -0
  85. package/lib/py-sdk-BCaOGYz7.d.ts +125 -0
  86. package/lib/py-sdk-CbgYiX8O.js +691 -0
  87. package/lib/py-sdk.d.ts +2 -0
  88. package/lib/py-sdk.js +3 -0
  89. package/package.json +325 -4
  90. package/scripts/kernel-provision.mjs +35 -0
  91. package/index.js +0 -3
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RimuruW and Yugimob (original pi-hashline-edit / pi-hashline-edit-pro)
4
+ Copyright (c) 2026 pi-hashline-edit-lsz contributors (pi port, hashline core)
5
+ Copyright (c) 2026 dsh-better-edit contributors (dsh port)
6
+ Copyright (c) 2026 DASHR contributors (dsh-url-schema vendoring)
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
package/README.md CHANGED
@@ -1,9 +1,299 @@
1
- # better-dsh
1
+ # Better Dsh — Dashr
2
2
 
3
- > **This package name has moved.**
3
+ DASHR is a persistent-kernel REPL for the DeepSeek Harness (dsh): a
4
+ **stateful `ctx.replRuntime` provider** (one persistent IPython kernel
5
+ subprocess **per session**, the run's `principal`, held in a map inside the
6
+ one service instance per mount). Each `run()` is one cell on the calling
7
+ session's kernel; variables, imports, and definitions assigned in run N
8
+ survive into run N+1 — *state codification* (blueprint §1.1 channel ②),
9
+ deliberately NOT the per-run isolation a one-shot execution backend
10
+ provides — and two sessions sharing one service instance never see each
11
+ other's variables.
4
12
 
5
- Install the scoped package instead:
13
+ Naming (v0.1.5 layer model): the runtime class is **`DashrRuntime`** — the
14
+ standing-mount-layer component (one instance per mount holding the
15
+ cross-session kernel map), and therefore the de facto daemon while the
16
+ profile-level `DashrDaemon` concept stays an empty shell; the session layer
17
+ is the ipykernel subprocess itself, a pure interpreter with no harness
18
+ awareness.
19
+
20
+ The presentation half — the `eval` transport tool, the Python SDK renderer,
21
+ and the tool→binding bridge — lives in the SAME package (merged in v0.1.8;
22
+ the pre-merge `dsh-rlm-mode`/`dashr-presentation` sibling split is gone).
23
+ The runtime registers the service key `replRuntime` through its own vendored
24
+ Service Definition (see `src/vendored/repl-runtime.ts`), so it carries **zero
25
+ dsh runtime package dependencies**: only `@deepseek-ai/cordis` (peer),
26
+ `schemastery`, and `zeromq`.
27
+
28
+ ## Package positioning
29
+
30
+ - npm name: `better-dsh` (unscoped; formerly `@pgmi-builds/better-dsh`, which
31
+ now stays published only as a deprecated pointer to this name).
32
+ - A standard Cordis plugin (`Context` + schemastery `Config`, every tunable
33
+ configurable from `cordis.yml`, no hardcoded tunables).
34
+ - "Registrations are effects": the kernel lifecycle (lazy spawn on a key's
35
+ first `run()`, teardown on that session's `agent/disposed`, optional
36
+ snapshot at either teardown) is effect-owned, so plugin disposal tears
37
+ every subprocess down.
38
+ - Published surface: `lib/` only (`files: ["lib"]`, `main`/`types`/`exports`
39
+ pointing at `lib/index.js` / `lib/index.d.ts`) — the build emits beside the
40
+ manifest (`outDir: 'lib'`; the tsdown default `dist/` left the exports map
41
+ dangling, fixed in M2B). The root also re-exports the vendored Service
42
+ Definition's public contract (`ReplRuntime` plus the `CodeRun*` /
43
+ `CodeBinding*` / `CodeJsonValue` types) so consumers depend on the
44
+ published shape instead of reaching into sources. The declaration keeps
45
+ dependency imports external (`dts: { resolve: false }`): bundled copies
46
+ would create duplicate type identities in a consumer's program.
47
+
48
+ ## Install
49
+
50
+ ```sh
51
+ npm install better-dsh
52
+ ```
53
+
54
+ The runtime OWNS its kernel environment. With `python` left at the
55
+ `python3` sentinel (or absent), it provisions a managed venv under the
56
+ package (`.venv-kernel`) on first use, installing `ipykernel` + `dill`
57
+ (CPython 3.11) — no `/tmp` symlink, no blind trust in a host `python3`.
58
+ An explicit `python` (or `DASHR_KERNEL_PYTHON`) is verified instead. For
59
+ development and tests, pre-create the venv:
60
+
61
+ ```sh
62
+ npm run kernel:venv # uv venv .venv-kernel --python 3.11 + ipykernel + dill
63
+ ```
64
+
65
+ Tests pick the kernel interpreter from `DASHR_TEST_PYTHON`, falling back to
66
+ `./.venv-kernel/bin/python`, then `python3` — see `test/helpers.ts`.
67
+
68
+ ## Configuration
69
+
70
+ Every field of the plugin `Config` (schemastery defaults shown):
71
+
72
+ | Field | Default | Meaning |
73
+ | --- | --- | --- |
74
+ | `python` | `python3` | Explicit interpreter with `ipykernel`; the bare `python3` sentinel (or absent) selects a managed venv under `kernelEnvDir`. |
75
+ | `startupTimeoutMs` | `30000` | Budget for kernel spawn → ready, in milliseconds. |
76
+ | `runTimeoutMs` | `120000` | Wall budget per run; expiry interrupts the kernel then force-settles. |
77
+ | `interruptGraceMs` | `2000` | Grace between a timeout/abort interrupt and the force-settle. |
78
+ | `interruptConfirmMs` | `250` | Confirm window between the control-channel interrupt and the SIGALRM escalation (must be `< interruptGraceMs`); see "Interrupts" below. |
79
+ | `disposeTimeoutMs` | `5000` | Budget for graceful kernel teardown (shutdown_request → SIGKILL). |
80
+ | `snapshotTimeoutMs` | `30000` | Budget for internal snapshot/restore cells (dill dump/load). |
81
+ | `maxOutputBytes` | `67108864` | Hard cap for serialized log-array, completion-value, and failure-message payloads. |
82
+ | `snapshotDir` | *(unset)* | Base directory for per-session namespace snapshots (`<dir>/<principal>/state.dill` + `manifest.json`); none when absent. |
83
+ | `snapshotSizeCapBytes` | `268435456` | Serialized-size cap for a turn-end snapshot; over-cap snapshots are skipped (one-time model warning). |
84
+ | `username` | `dashr` | Jupyter username stamped on wire messages. |
85
+ | `kernelEnvDir` | *(unset)* | Managed venv directory (defaults to `<package>/.venv-kernel`). |
86
+ | `kernelPythonVersion` | *(unset)* | Preferred CPython version for a managed venv (default `3.11`). |
87
+ | `kernelAutoInstall` | `true` | Provision the managed venv (`ipykernel` + `dill`) on first use. |
88
+
89
+ ## Persistent-state semantics
90
+
91
+ - **Cell semantics**: each `run({ program })` is one cell on the calling
92
+ session's kernel namespace (`user_ns`) — a pure IPython REPL. Top-level
93
+ `await` works; a top-level `return` is a SyntaxError, exactly as in a
94
+ native IPython cell. The completion value is the LAST expression's value
95
+ (REPL displayhook-style): a statement-ending cell or a `None` final
96
+ expression yields no `value` field, and a non-JSON value comes back as its
97
+ repr text.
98
+ - **Session keying** (M3-A): one kernel per distinct `request.principal`
99
+ (the presentation bridge passes the calling agent's session id); runs
100
+ without a principal share one default key, preserving M1 semantics. The
101
+ service instance count is unchanged — one per mount — the keying is a
102
+ `Map<principal, kernel>` inside the provider.
103
+ - **Kernel lifetime**: lazy start on a key's first `run()`; teardown when
104
+ that session's agent is disposed (the dsh `agent/disposed` event, payload
105
+ `{ agent: { id } }`, listened through the untyped cordis event service to
106
+ keep this package's zero-dsh-dependency rule) and on plugin disposal
107
+ (`shutdown_request`, then SIGKILL after `disposeTimeoutMs`). A kernel that
108
+ dies unexpectedly is never reused in-process: it respawns onto its nearest
109
+ replayable snapshot (or a fresh empty kernel when none exists) and the run
110
+ that observed the death gets an explicit `worker-exit` naming what was lost.
111
+ - **Turn-end snapshots** (M3-B): with `snapshotDir` configured, every
112
+ successful run is followed by a size-capped snapshot cell that dumps the
113
+ user namespace to `<snapshotDir>/<principal>/state.dill` + `manifest.json`
114
+ (`turn`, `pythonVersion`, `venvPath` = the kernel's own `sys.executable`,
115
+ `skills`, `names`, `sizeBytes`). A namespace whose serialized size exceeds
116
+ `snapshotSizeCapBytes` is skipped — estimated BEFORE any dill IO by a
117
+ bounded walk that reads numpy/pandas in-memory footprints, then confirmed
118
+ against the actual `.part` dump — and the model is warned once through the
119
+ run's own logs. Skipped snapshots never replace the previous good one.
120
+ - **Restore-on-first-boot** (M3-B): a key's first kernel boot restores its
121
+ on-disk snapshot before running user code. The kernel validates the
122
+ manifest itself (python version, interpreter identity, skills); a
123
+ non-replayable snapshot degrades to an EMPTY namespace and the first run
124
+ tells the model so. Variable state and the append-only transcript are NOT
125
+ transactionally consistent (blueprint §8.3): the snapshot is a point-in-time
126
+ namespace capture that can lag the transcript, and a degraded restore never
127
+ fabricates variables the transcript once saw.
128
+ - **Interrupts** (M3-A hardened): aborts/timeouts escalate in two phases —
129
+ the zmq control `interrupt_request` first, then SIGALRM only after
130
+ `interruptConfirmMs` if the cell has still not settled. The kernel-side
131
+ bootstrap installs a busy guard that only raises `KeyboardInterrupt` while
132
+ a dashr cell is actually executing, so a signal landing on an idle or
133
+ booting kernel is swallowed instead of terminating the process (the M1
134
+ same-tick dual send killed idle kernels deterministically — 10/10
135
+ same-tick, 8/10 at +1-2ms, 40/40 during cold boot; see
136
+ `test/interrupt-race.spec.ts`). The hard-abort contract is intact: a busy
137
+ `while True: pass` still breaks inside the grace (blueprint §10.4).
138
+ - **Concurrency**: the bridge serializes cells per kernel (`executeCell`
139
+ awaits the previous cell), so concurrent `run()` calls on one session
140
+ queue rather than interleave; see `test/parallel.spec.ts`. Runs on
141
+ DIFFERENT principals execute on their own kernels concurrently.
142
+ - **What snapshots do NOT carry**: nothing outside the kernel namespace is
143
+ ever in scope — the v0.1.8b removal deleted the Continual Harness
144
+ (`refine()`, `harnessDir`) entirely, so the snapshot/restore cycle's only
145
+ persistence channel is the kernel namespace itself (blueprint §8.4).
146
+ Anything a cell stores in ordinary variables follows the snapshot rules
147
+ above as before.
148
+
149
+ ## Testing
150
+
151
+ ```sh
152
+ npm install
153
+ npm run kernel:venv # once; or export DASHR_TEST_PYTHON=/path/to/python
154
+ npm run typecheck # tsc --noEmit
155
+ npm test # vitest --run (fileParallelism: false)
156
+ ```
157
+
158
+ Teardown discipline: every test context is disposed through
159
+ `onTestFinished`, and CI must assert no orphan kernels remain:
6
160
 
7
161
  ```sh
8
- npm install @pgmi-builds/better-dsh
162
+ pgrep -cf -- '-[m] ipykernel_launcher' || echo no-orphans
9
163
  ```
164
+
165
+ (The `-[m]` trick prevents `pgrep` from matching itself; 208 orphaned
166
+ kernels once exhausted machine memory while every unit test stayed green —
167
+ blueprint §10.8/§10.9.)
168
+
169
+ ---
170
+
171
+ # Presentation half (eval transport, SDK, bindings)
172
+
173
+ The DASHR agent-plane presentation (blueprint §7.4): the plugin half that
174
+ presents the persistent-kernel runtime to the model as **cells on one
175
+ persistent IPython kernel**. It contributes:
176
+
177
+ - **`eval`** — the cell transport tool: one call = one cell on the calling
178
+ session's kernel (the `ctx.replRuntime` service). Variables, imports, and
179
+ definitions survive across calls. Nested tool calls ride the host
180
+ registry's native scheduling pipeline as `await tool.name({...})` inside
181
+ the cell — one positional arguments object per call, keyword arguments
182
+ rejected. Every registry-visible tool is bound as a member; the
183
+ `send_message` bridge callable joins them in the same catalog block.
184
+ - **`dashr:tool-catalog`** — a generated Python SDK prompt section: one
185
+ `async def name(args) -> Output` per visible tool (flat `tool.*` shape)
186
+ plus the cell contract (persistent namespace, completion-value rules,
187
+ `ToolCallError`, sub-call concurrency).
188
+ - **The model-direct collapse** — an assembly filter leaves `eval` the only
189
+ contributed tool schema, and a monotonic guard denies a model-direct call
190
+ naming anything else with the route back into a cell. Both are scoped to
191
+ the mounting composition, so a PTC (native Code Mode) preset in the same
192
+ process keeps its own presentation.
193
+ - **Masking (ADR-0002)** — exactly two upstream tool names are displaced
194
+ from the model's surface (`send_message` and the child-scoped `report`),
195
+ collapsed into the single dual-direction `send_message` bridge. Every
196
+ other delegation tool (`subagent`, `subagent_fork`, `list_agents`,
197
+ `interrupt_agent`, `workflow`, `ralph`) stays **directly exposed** as a
198
+ native `tool.*` binding — the model calls it exactly as the host ships it.
199
+ Masking is presentation-only: the registry is never touched.
200
+
201
+ Removed in v0.1.8b: the `refine` Continual Harness and the `compact` REPL
202
+ bridge (harness/refine became third-party territory; context compaction is
203
+ the host runtime's business, not the REPL's).
204
+
205
+ ## Install
206
+
207
+ The canonical path is the repo-root one-click installer (`install.sh`): it
208
+ installs the plugin from the npm registry and notes the restart. The
209
+ equivalent manual steps, for reference:
210
+
211
+ ```sh
212
+ # 1. plugin — the pnpm registry-metadata cache can lag a fresh npm publish
213
+ # by minutes-to-hours, so drop it first, or `@latest` resolves the OLD
214
+ # version right after a release:
215
+ rm -rf ~/.cache/pnpm
216
+ dsh plugin --profile web add --config.auto-install-peers=false better-dsh@latest
217
+
218
+ # 2. restart the running instance: systemctl --user restart dsh
219
+ ```
220
+
221
+ Notes: `--config.auto-install-peers=false` is MANDATORY — the profile
222
+ already resolves `@deepseek-ai/*` peers through the harness install; letting
223
+ the package manager auto-install them adds a second, divergent copy of
224
+ cordis and friends. The version is deliberately unpinned (`@latest`).
225
+
226
+ ## Coexistence with a PTC Code-Mode session
227
+
228
+ `eval` is our own transport name (the registry reserves `run_code`), so a
229
+ Code-Mode preset (`@deepseek-ai/dsh-agent-tool-presentation` with
230
+ `mode: code` over the host-plane worker-thread `codeRuntime`) composes
231
+ beside a DASHR session in one process: the PTC agent's assembly shows
232
+ `run_code` plus the TS `tools:sdk` section, the DASHR agent's shows `eval`
233
+ plus the Python `dashr:tool-catalog`, and neither execution path touches
234
+ the other's runtime.
235
+
236
+ ## Composition
237
+
238
+ ```ts
239
+ import dashr from 'better-dsh'
240
+
241
+ ctx.plugin(dashr, { maxParallelSubCalls: 10 })
242
+ ```
243
+
244
+ One plugin, one row: `apply` mounts `DashrRuntime` first (providing
245
+ `ctx.replRuntime`), then the presentation half injects on `replRuntime` —
246
+ a composition without the runtime fails AT MOUNT, named in the preset's
247
+ activation audit, instead of at the first prompt. The dashr bundle patch
248
+ mounts the row on the HOST plane, so every agent in every preset sees the
249
+ `eval` tool (`agent → preset → global`).
250
+
251
+ ## Delegation and messaging
252
+
253
+ The upstream delegation tools stay REGISTERED, EXECUTABLE, and directly
254
+ bound — `subagent`, `subagent_fork`, `list_agents`, `interrupt_agent`,
255
+ `workflow`, `ralph` are ordinary `tool.*` members the model calls exactly
256
+ as the host ships them. Two names are displaced by the single
257
+ `send_message` bridge (ADR-0001): upstream `send_message` (the
258
+ parent→child downlink) and the child-scoped `report` (the child→parent
259
+ uplink) collapse into one dual-direction channel:
260
+
261
+ - `await tool.send_message({"receiver": "child", "message": ..., "subagent_id": ...})`
262
+ delivers down through the tool layer;
263
+ - `await tool.send_message({"receiver": "parent", "message": ...})` reports
264
+ up through the SERVICE layer (`ctx.subagents.reportFrom(...)`) — live
265
+ continuable children only; a root agent gets a structured `UNAUTHORIZED`.
266
+
267
+ Every binding returns structured JSON; errors are a FIELD on the result,
268
+ never a host crash — a missing `ctx.subagents` service, a depth cap, an
269
+ unknown id, or an infrastructure rejection all map to an `error` string.
270
+
271
+ ## Config
272
+
273
+ | Field | Default | Meaning |
274
+ | --- | --- | --- |
275
+ | `maxParallelSubCalls` | `10` | Cap on one cell's overlapping sub-calls (native scheduler contract; `1` = strictly serial). |
276
+ | *(runtime keys)* | — | The runtime slice (`python`, `snapshotDir`, timeouts, caps, kernel-env knobs) is documented in the Configuration table above. |
277
+
278
+ ## Tests
279
+
280
+ ```sh
281
+ npm install
282
+ npm run kernel:venv # once; or export DASHR_TEST_PYTHON=/path/to/python
283
+ npm run typecheck
284
+ npm test # vitest --run
285
+ ```
286
+
287
+ The suite needs a Python interpreter with `ipykernel` (+ `dill` for the
288
+ snapshot tiers) for the real-kernel specs; it resolves one from
289
+ `DASHR_TEST_PYTHON`, then `./.venv-kernel/bin/python`, then `python3` —
290
+ see `test/helpers.ts`.
291
+
292
+ ## Relationship to upstream
293
+
294
+ Structure mirrors `@deepseek-ai/dsh-agent-tool-presentation` and the Code
295
+ Mode half of `@deepseek-ai/dsh-tools` (0.1.1-rc.2), re-pointed at the
296
+ vendored `replRuntime` Service Definition. See the module docs in
297
+ `src/index.ts` for the deliberate deltas (`eval` vs `run_code`, ordinary
298
+ scoped registration, guard-based collapse, mirrored `tools/code-dispatch-log`
299
+ waterfall).
@@ -0,0 +1,37 @@
1
+ ## The DASHR REPL interface
2
+
3
+ This agent has TWO ways to act:
4
+
5
+ 1. **Direct tool calls** — call native tools (`read`/`write`/`edit`/`bash`/…) as ordinary function calls. Use these for payload-shaped work: one long read, one edit, one command.
6
+ 2. **`eval` cells** — one `eval` call runs one Python program on a session-persistent scripting pad. Use it when you need logic: loops, conditions, fan-out, or composing many tool results into one step.
7
+
8
+ `eval` takes two required arguments: `cell` (one Python program; top-level `await` works; top-level `return` is a SyntaxError — the cell runs in module scope; variables/imports/definitions from earlier cells are still alive) and `description` (a short summary).
9
+
10
+ ## Tools inside a cell
11
+
12
+ Inside a cell, every native tool is a member of the `tool` object, called as `await tool.name({...})` with ONE positional arguments object — `await tool.read({"file_path": "x"})`, never `tool.read(file_path="x")`. A failed call raises `ToolCallError`. Tool names that are not plain identifiers (non-identifier characters, e.g. hyphens) have no `tool.<name>` member — call those as direct tool calls. Delegation: `agent` is the unified agent-spawn entry; `subagent` is its native alias — both delegate through the same runtime, so call either.
13
+
14
+ ```python
15
+ # One step cell
16
+ print(await tool.read({"file_path": "docs/README.md"}))
17
+
18
+ # shell is another tool
19
+ r = await tool.bash({"command": "ls -la src/", "description": "List source directory"})
20
+ print(r["stdout"]["text"])
21
+
22
+ # fan-out with gather
23
+ import asyncio
24
+ matches, files = await asyncio.gather(
25
+ tool.grep({"pattern": "TODO", "path": "src"}),
26
+ tool.glob({"pattern": "**/*.ts", "path": "src"}),
27
+ )
28
+
29
+ # variables persist across cells and turns
30
+ cfg = await tool.read({"file_path": "config.yaml"}) # cfg stays alive in later cells
31
+ ```
32
+
33
+ ## Rules
34
+
35
+ - Payload-shaped work (a long read, a big write, a single command) → direct tool call. Logic-shaped work (loops, conditions, composition) → an `eval` cell.
36
+ - Only print or return what you need next; everything else stays in the scripting pad.
37
+ - Variables persist across cells and turns, but they live in the pad's process: keep durable state in files.
@@ -0,0 +1,53 @@
1
+ # DASHR bundle patch: mounts `dashr-repl` on the HOST plane. Its `eval` tool
2
+ # and persistent-kernel REPL runtime land in the global scope layer, so every
3
+ # agent in every preset sees them (`agent → preset → global`). One
4
+ # process-global runtime keys one kernel per Session/Agent, torn down on
5
+ # `agent/disposed` — the promotion of the v0.1.5 DashrDaemon shell.
6
+ #
7
+ # The tool is an ordinary registration: upstream's `code` preset still applies
8
+ # its own `tools:code-only` rule (a direct `eval` call in a PTC session resolves
9
+ # to UNKNOWN_TOOL). DASHR ships no PTC mode, so that rule never bites here.
10
+ #
11
+ # `python` is only a hint: the runtime OWNS the kernel environment. An
12
+ # explicit interpreter (DASHR_KERNEL_PYTHON) is verified; the bare
13
+ - insert:
14
+ - id: dashr-repl
15
+ name: 'better-dsh'
16
+ config:
17
+ python: !!js process.env.DASHR_KERNEL_PYTHON ?? 'python3'
18
+ # NOTE (v0.2.2a): trustedPageAuthorities' DERIVED default lives in the
19
+ # plugin's Config schema (src/index.ts), NOT here — a profile/home
20
+ # layer row with this id whole-row-overrides this config object, which
21
+ # would wipe any default declared at the bundle layer (empirically
22
+ # pinned on 4999). Schema defaults fill per-key at plugin load and
23
+ # survive every overlay layer; the default derives from the
24
+ # DSH_TRUSTED_HOSTS environment — the same single source the fence
25
+ # leg reads first. An explicit value in any later layer still wins.
26
+
27
+ # Native compaction at the host plane: the base bundle ships the engine, the
28
+ # `/compact` command, and the tool-result pruner as host rows; dsh-web-app
29
+ # disables them to move compaction behind per-session presets. DASHR is
30
+ # host-plane, so re-enable them here (upstream defaults: threshold 0.8, retain
31
+ # 0.16). Applied last in bundle order, overriding dsh-web-app's disable.
32
+ - id: compaction-basic
33
+ disabled: false
34
+ - id: command-compact
35
+ disabled: false
36
+ - id: tool-result-pruner
37
+ disabled: false
38
+
39
+ # Web-trust fence authorities (v0.2.1f): override the connection row the
40
+ # dsh-web-app bundle declares (same id), restating its full shape per the
41
+ # patch-layer contract, with `trustedHosts` merging the DSH_TRUSTED_HOSTS
42
+ # environment (whitespace-separated, operator-declared serving authorities)
43
+ # ahead of the upstream webRuntime-derived ones (LAN literals from an
44
+ # all-interface bind plus --trusted-host extras). Unset/empty env ⇒ identical
45
+ # behavior to the unpatched row. Malformed entries fail plugin load loudly
46
+ # (upstream assertTrustedAuthority). The browser-side twin (page-authority
47
+ # loopback verdict) is the plugin's webserver/index-inject boot script —
48
+ # see src/web-trust.ts.
49
+ - id: connection
50
+ name: '@deepseek-ai/dsh-client-connection'
51
+ inject: [webRuntime]
52
+ config:
53
+ trustedHosts: !!js (process.env.DSH_TRUSTED_HOSTS ?? '').split(/\s+/).filter(Boolean).concat(ctx.webRuntime.trustedHosts)
@@ -0,0 +1,14 @@
1
+ # Bridge the tool layer, not the service layer
2
+
3
+ DASHR's `rlm()` family dispatches upstream delegation tools through the tool registry (nested sub-dispatch), not `ctx.subagents` service methods. The tool layer carries the deployment's enforcement surface — approval pipeline, sandbox policy, per-instance config (maxDepth, backgroundMode, persona) — that a direct service call would silently bypass. The cost: per-call model selection is impossible, because the tool schema exposes no `model` parameter; `rlm(model=...)` from 0.1.4 is dropped, and `subagentModel` degrades to a static `agentOptions.model` in the preset patch.
4
+
5
+ ## Considered Options
6
+
7
+ - **Service layer direct** (0.1.4's approach): `ctx.subagents.start()` with a hand-built request. Full control over request fields (including `agentOptions.model` and `maxDepth`), but every policy the tool instance would have applied must be re-implemented or lost.
8
+ - **Tool layer nested dispatch** (chosen): `rlm("spawn")` executes the registry's `subagent` tool with a parent token. Upstream policy is inherited wholesale; the schema boundary is the tool's own contract.
9
+
10
+ ## Consequences
11
+
12
+ - `rlm(mode, prompt, *, label, run_in_background)` — no `model` kwarg. Parent-model inheritance is the default; a different child model requires a preset patch, not a call argument.
13
+ - Depth enforcement comes from the tool instance's `maxDepth` config, patched to 10 in the preset (see `dev/kernel-refactoring/V0.1.5-development-plan.md` Q22/Q23).
14
+ - The upstream delegation tools (`subagent`, `subagent_fork`, `interrupt_agent`) must stay registered and executable even though the model never sees their names — which is why masking is presentation-only (ADR-0002).
@@ -0,0 +1,15 @@
1
+ # Masking is presentation-only
2
+
3
+ Hiding exactly two upstream A2A tool names (`send_message` — the parent→child downlink — and `report` — the child→parent uplink) from the model is done by excluding them from the generated Tool Catalog text and from the kernel binding names — nothing else. The two tools stay registered, executable, and dispatchable; the single `send_message` bridge dispatches them internally. Every OTHER upstream delegation tool (`subagent`, `subagent_fork`, `list_agents`, `interrupt_agent`, `workflow`, `ralph`) is exposed directly as a native `tool.*` member — no re-wrapping (v0.1.9).
4
+
5
+ ## Considered Options
6
+
7
+ - **`restrict()` at runtime**: hides names in the registry's model-facing view, but validates against the live view at call time — ordering hazards against late tool registration can fail the whole preset mount.
8
+ - **`disabled: true` include patches**: physically unregisters the tool, which also removes the bridge's dispatch target. Masking must not break the bridge.
9
+ - **Presentation-only exclusion** (chosen): the registry is never touched. The model's surface (wire schema collapse to `eval`, Tool Catalog text, kernel bindings) is entirely DASHR-generated, so exclusion happens at the two points DASHR owns.
10
+
11
+ ## Consequences
12
+
13
+ - The masked tools remain in the registry and are reachable via nested sub-dispatch with a parent token, which passes the model-direct guard.
14
+ - **Field-verified (v0.1.8d, `both` presentation mode)**: reachable by MODEL-DIRECT native call too — no parent token needed. A probe calling the masked `skill({"name":…})` through the API function-call surface executed in full. Cause: the mask registers no visibility filter, so the name stays in `view(scope).visible`, and `resolveExecution` collapses model-direct calls only under `code` mode. The mask is an ADVERTISING cut, not an enforcement point; the hard gate is the REPL binding allowlist (`unknown binding`). If a deployment ever needs true model-direct rejection, the mechanisms are a visibility-layer `restrict()` (rejected here for ordering hazards) or a `code`-mode collapse — both are deployment-level decisions, not mask-level ones.
15
+ - Zero upstream mutation means zero interference with host-plane modules that enumerate or interact with the delegation tools.