@deepseek-insight/dsi 0.6.15 → 0.6.17
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/.agents/skills/dsi-ports/SKILL.md +61 -0
- package/.agents/skills/dsi-restart/SKILL.md +85 -0
- package/bin/dsi.mjs +216 -51
- package/bin/lib/dev-mode.mjs +147 -0
- package/bin/lib/plugin-chain.mjs +144 -0
- package/build/client/_app/immutable/assets/{0.BNydQv_U.css → 0.KhAZv9mf.css} +1 -1
- package/build/client/_app/immutable/assets/2.yPxdQ-n0.css +1 -0
- package/build/client/_app/immutable/chunks/BwzVWUYp.js +1 -0
- package/build/client/_app/immutable/chunks/{Dz22rCMX.js → CBpybevN.js} +2 -2
- package/build/client/_app/immutable/chunks/{Cq1i4fPn.js → CuWK-Is9.js} +1 -1
- package/build/client/_app/immutable/chunks/gD0YbXxV.js +1 -0
- package/build/client/_app/immutable/entry/{app.BzO5CDQp.js → app.rnAj22Bt.js} +2 -2
- package/build/client/_app/immutable/entry/start.BZMX9WpT.js +1 -0
- package/build/client/_app/immutable/nodes/{1.DUMUcf6V.js → 1.CEJdtANo.js} +1 -1
- package/build/client/_app/immutable/nodes/{2.DibXuWjr.js → 2.D-He2bY-.js} +219 -219
- package/build/client/_app/version.json +1 -1
- package/build/handler.js +4 -4
- package/build/index.js +4 -4
- package/build/server/chunks/chunks/{dsh-connection.js-DRtqPR_j.js → dsh-connection.js-DvncrWM4.js} +5 -2
- package/build/server/chunks/chunks/{git-probe.js-Daf6r7Z9.js → git-probe.js-S9w9gGXE.js} +2 -2
- package/build/server/chunks/chunks/{internal2.js-CFvE_2qT.js → internal2.js-Uh9eeZMM.js} +2 -2
- package/build/server/chunks/chunks/restart-chain.js-KZO2ISmM.js +112 -0
- package/build/server/chunks/chunks/{state.js-BFJMxIOB.js → state.js-5SpXEMQf.js} +2 -2
- package/build/server/chunks/chunks/{watcher.js-CoR53lwR.js → watcher.js-BfZOp5td.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/a2a/{_server.ts.js-BKlF4XGH.js → _server.ts.js-D9vpMh8K.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/a2a/register/{_server.ts.js-BpEhlidV.js → _server.ts.js-HPJEmfbv.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/dsh/directory/{_server.ts.js-Dmh7f-hU.js → _server.ts.js-BaAmRXrU.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/pick-directory/{_server.ts.js-BEci0ZAx.js → _server.ts.js-D-Ftq5ei.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/presets/{_server.ts.js-OXFzXBSp.js → _server.ts.js-C0_j53OW.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/attachment/{_server.ts.js-x44GKy4R.js → _server.ts.js-CxMvUVdS.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/cancel/{_server.ts.js-Da5ZqeJ7.js → _server.ts.js-CfjRtKMp.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/catalog/{_server.ts.js-BACv9hW-.js → _server.ts.js-D7uC5b_j.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/changes/{_server.ts.js-BtiUS3iD.js → _server.ts.js-TeoUfrIl.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/command/{_server.ts.js-BOOyd1LI.js → _server.ts.js-uqUb-EdM.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/events/{_server.ts.js-D0GwQbdF.js → _server.ts.js-C_DF0fAc.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/fork/{_server.ts.js-Bd0JYMlf.js → _server.ts.js-BfkqDQXU.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/goal/{_server.ts.js-DqAV7OOv.js → _server.ts.js-vCfxCACS.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/history/{_server.ts.js-Cz4P-68E.js → _server.ts.js-DJlpW4hl.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/models/{_server.ts.js-C4fXXBIM.js → _server.ts.js-Cqn5ZMGh.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/permission/{_server.ts.js-BiqNZiiN.js → _server.ts.js-ChB_0ZdM.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/prompt/{_server.ts.js-Bdx9V4oY.js → _server.ts.js-BVHypcWF.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/rename/{_server.ts.js-CuBqoV3c.js → _server.ts.js-BwDVfVrR.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/respond/{_server.ts.js-CXPFI_Pn.js → _server.ts.js-DRmd83Ea.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/select-model/{_server.ts.js-CRcaw4Yo.js → _server.ts.js-hc61Nb64.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/sessions/{_server.ts.js-dYrtVHDP.js → _server.ts.js-xkp4IT7y.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspace-file/{_server.ts.js-BtlfuMjf.js → _server.ts.js-BvPKBiTg.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspace-tree/{_server.ts.js-D12kAP9B.js → _server.ts.js-BSsF01K6.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspaces/{_server.ts.js-C9JHp1WU.js → _server.ts.js-DNLxFs1R.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/delete/{_server.ts.js-bLF4Fbgn.js → _server.ts.js-5jaRPaKn.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/rename/{_server.ts.js-BSw_zD-0.js → _server.ts.js-A-hvBqyy.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/floor/mode/_server.ts.js-CzBvebv2.js +25 -0
- package/build/server/chunks/entries/endpoints/api/floor/restart/_server.ts.js-DKsahdJH.js +30 -0
- package/build/server/chunks/entries/endpoints/api/plugins/apply/{_server.ts.js-BNEUWbaf.js → _server.ts.js-NZ9IorXC.js} +5 -56
- package/build/server/chunks/entries/endpoints/api/plugins/host-bounce/_server.ts.js-ELTA8Cv_.js +49 -0
- package/build/server/chunks/entries/endpoints/api/workspace/file-write/{_server.ts.js-CqyKA9g2.js → _server.ts.js-C0x68iMD.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/workspace/git-events/{_server.ts.js-yu695YoE.js → _server.ts.js-DzdGKZdV.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/workspace/git-file-head/{_server.ts.js-BhDAvM6y.js → _server.ts.js-BC1jb3zy.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/workspace/git-map/{_server.ts.js-BRUgPrJ2.js → _server.ts.js-BE9XJyhN.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/workspace/git-status/{_server.ts.js-C7VJ2G4_.js → _server.ts.js-CWlGtOu8.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/workspace/write-gate/{_server.ts.js-BH5BRA_d.js → _server.ts.js-CgcggNrH.js} +3 -3
- package/build/server/chunks/entries/pages/{_error.svelte.js-Cz-l6MbQ.js → _error.svelte.js-5nTeAv_j.js} +3 -3
- package/build/server/chunks/entries/pages/{_page.server.ts.js-CityLNBO.js → _page.server.ts.js-CbzURHkB.js} +2 -2
- package/build/server/chunks/entries/pages/{_page.svelte.js-J7dfR5ON.js → _page.svelte.js-DhwtRb8h.js} +135 -21
- package/build/server/chunks/{handler-Ctb3OEZ3.js → handler-Bx0HQRi_.js} +3 -3
- package/build/server/chunks/{index.js-DZ5R_e-K.js → index.js-DlqGbs2e.js} +2 -2
- package/build/server/chunks/{manifest.js-D3cyG_zP.js → manifest.js-C5Epl_0n.js} +59 -38
- package/build/server/chunks/nodes/{0.js-CedNgbP9.js → 0.js-zSkx80Fq.js} +3 -3
- package/build/server/chunks/nodes/1.js-BBFJCoA_.js +9 -0
- package/build/server/chunks/nodes/{2.js-B5xHvNNG.js → 2.js-rGsgHmCd.js} +6 -6
- package/package.json +1 -1
- package/template/.dsi/prompts.sqlite +0 -0
- package/template/.dsi/prompts.sqlite-shm +0 -0
- package/template/.dsi/prompts.sqlite-wal +0 -0
- package/template/.dsi/resources/plugins-reff.md +7 -6
- package/.agents/skills/dsi-thin/SKILL.md +0 -140
- package/.agents/skills/dsi-thin/references/ADR-0019-snapshot.md +0 -363
- package/.agents/skills/dsi-thin/references/authority-adr.md +0 -28
- package/.agents/skills/dsi-thin/references/input-contract.md +0 -42
- package/.agents/skills/dsi-thin/references/proven-gates.md +0 -61
- package/.agents/skills/dsi-thin/references/two-lane-contract.md +0 -53
- package/build/client/_app/immutable/assets/2.tyWDc_pW.css +0 -1
- package/build/client/_app/immutable/chunks/DlCR4rv7.js +0 -1
- package/build/client/_app/immutable/chunks/syBMXJiJ.js +0 -1
- package/build/client/_app/immutable/entry/start.DPtsd8of.js +0 -1
- package/build/server/chunks/nodes/1.js-BJtXg-yj.js +0 -9
- /package/build/client/_app/immutable/nodes/{0.DJ0pyst0.js → 0.Jl8y_yU6.js} +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsi-ports
|
|
3
|
+
description: Fast feedback-loop port inventory — run /dsi-ports (or ask "what's running / which ports are open / what's eating memory") to list every TCP listener with pid, process, memory usage, and its purpose (DSH host, DSI UI, Playwright, inspector, test floors). Backed by the Port Ledger KB.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsi-ports — the Port Ledger, live
|
|
7
|
+
|
|
8
|
+
Companion KB: `dev/kb/open-ports/2026-10-12 - The Port Ledger — Who Opens What, Why, and How Test Floors Stay Isolated.md` — read it for WHY each port exists. This skill is the LOOK-path: one command, one table, no judgment.
|
|
9
|
+
|
|
10
|
+
## Run
|
|
11
|
+
|
|
12
|
+
Execute this single pipeline (macOS; safe, read-only). AGENT HYGIENE: write
|
|
13
|
+
the block to a temp file (`/tmp/dsi-ports.sh`) and run `sh` on it — inlining
|
|
14
|
+
shell `$-expansions` inside a JS template string breaks the tool call
|
|
15
|
+
(observed 2026-10-12); the failure is the caller's, not the pipeline's.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
for pid_port in $(lsof -nP -iTCP -sTCP:LISTEN | awk 'NR>1 {match($9, /[^:]*$/); print substr($9, RSTART) "/" $2}' | sort -u); do
|
|
19
|
+
port=${pid_port%%/*}; pid=${pid_port##*/}
|
|
20
|
+
rss=$(ps -o rss= -p "$pid" 2>/dev/null | awk '{printf "%.0f MB", $1/1024}')
|
|
21
|
+
cmd=$(ps -o command= -p "$pid" 2>/dev/null | cut -c1-70)
|
|
22
|
+
echo "$port | $pid | ${rss:-?} | $cmd"
|
|
23
|
+
done | sort -n
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Format the output
|
|
27
|
+
|
|
28
|
+
Present EXACTLY this table (port ascending), then the purpose line:
|
|
29
|
+
|
|
30
|
+
| Port | PID | Process | Memory (RSS) | Purpose |
|
|
31
|
+
|---|---|---|---|---|
|
|
32
|
+
| 3080 | 1234 | node …/vite.js dev | 412 MB | **DSH host** — browser RPC + token announce |
|
|
33
|
+
| 5175 | 1234 | node …/vite.js dev | 388 MB | **DSI dev UI** — the page the operator uses |
|
|
34
|
+
|
|
35
|
+
Purpose lookup (from the Port Ledger KB — first match wins):
|
|
36
|
+
|
|
37
|
+
| Port | Purpose |
|
|
38
|
+
|---|---|
|
|
39
|
+
| 3080 | DSH host — browser RPC, token announce |
|
|
40
|
+
| 5174 | DSI built UI (production page) |
|
|
41
|
+
| 5175 | DSI dev UI (Vite, HMR) |
|
|
42
|
+
| 5176 | Playwright webServer (vite preview, e2e backend) |
|
|
43
|
+
| 9229 | Node inspector (D7 — one debugger for DSI + embedded host) |
|
|
44
|
+
| 1933 | OpenViking memory server |
|
|
45
|
+
| 34xx | test-floor DSH host (isolated) |
|
|
46
|
+
| 51xx (5180+) | test-floor UI (isolated) |
|
|
47
|
+
| anything else | **unknown — name the binary and ask** (never guess a purpose) |
|
|
48
|
+
|
|
49
|
+
## Reading the table (tell the operator, one line each)
|
|
50
|
+
|
|
51
|
+
- Same PID on TWO ports (e.g. 3080 + 5175) → the **in-process floor** (one process, D7).
|
|
52
|
+
- 5175 up but 3080 down → UI-only floor; the page will show "Failed to fetch".
|
|
53
|
+
- 3080 up but no 5175/5174 → bare host (tandem's other half missing, or runCli host only).
|
|
54
|
+
- Two PIDs on the same port → TWO listeners — list both rows, flag it.
|
|
55
|
+
- Biggest RSS → name it; vite dev floors routinely sit at 300–500 MB (normal).
|
|
56
|
+
|
|
57
|
+
## Boundaries
|
|
58
|
+
|
|
59
|
+
- Read-only: `lsof`, `ps`, `sort`. Never kill, never open, never edit — remediation is always a separate, confirmed step.
|
|
60
|
+
- IPv6 vs IPv4 duplicates: same port with different PIDs means TWO listeners — list both rows, flag it.
|
|
61
|
+
- The table is a snapshot; ports move between runs. Re-run, don't cache.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsi-restart
|
|
3
|
+
description: Restart the DSI/DSH floor with a before/after report — /dsi-restart snapshots every listener's port+pid+RSS, restarts (or waits for the operator's restart), re-snapshots, and prints the delta table (which ports restarted, memory freed/claimed). Answers "floor feels heavy, restart and show me what changed".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsi-restart — restart with receipts
|
|
7
|
+
|
|
8
|
+
Companion: `dsi-ports` skill (the snapshot pipeline) and the Port Ledger KB (`dev/kb/open-ports/`). This skill adds the DELTA: before, restart, after, memory moved.
|
|
9
|
+
|
|
10
|
+
## 0. Identify how the floor is running (do not skip)
|
|
11
|
+
|
|
12
|
+
**Facts first (2026-10-12, hard rule):** classify launch shape ONLY from a full
|
|
13
|
+
ancestry walk — `ps -o pid,ppid,lstart,command` repeated up the ppid chain
|
|
14
|
+
until you reach pid 1 or an interactive `zsh`/`login`. Record each hop. An
|
|
15
|
+
immediate-parent glance is not detection: the same zsh can own both a dead
|
|
16
|
+
floor's corpse and the replacement the OPERATOR launched by hand (precedent
|
|
17
|
+
2026-10-12: the assistant claimed a "detached relaunch" that was actually the
|
|
18
|
+
operator re-running the command in their own terminal). **Never narrate a
|
|
19
|
+
relaunch you did not perform and cannot cite a pid + start time for.**
|
|
20
|
+
|
|
21
|
+
| Launch shape | Detection (full ancestry walk) | Who may restart it |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Operator's foreground terminal | the `bin/dsi.mjs` chain's top parent is an interactive `zsh`/`login` (walk ppids; lstart proves which launch is which) | **the operator only** — or the Restart Verb with the operator's go (§2) |
|
|
24
|
+
| Detached chain (plugin-rack / verb bounce) | the chain's top parent is pid 1 (launchd) | the skill, via the Restart Verb or `--sync-kills` + relaunch |
|
|
25
|
+
|
|
26
|
+
If detection is ambiguous → **ask the operator** before killing anything (precedent: pid 37084, 2026-10-12 — the assistant killed the operator's live floor during cleanup).
|
|
27
|
+
|
|
28
|
+
**Exit-143 rule (2026-10-12, corrected):** when the floor dies — Ctrl-C, kill,
|
|
29
|
+
or the Restart Verb hitting a foreground-terminal floor — the terminal prints
|
|
30
|
+
`ELIFECYCLE Command failed with exit code 143` and the floor is **DOWN. It
|
|
31
|
+
does not come back by itself.** Only the detached restart chain (the verb's
|
|
32
|
+
spawned one-shot) or a human re-running the command brings it back. Never
|
|
33
|
+
write or say "the floor came back detached" without a pid + `lstart` you just
|
|
34
|
+
read from `ps` — an unverified rebirth story cost the operator a dead floor
|
|
35
|
+
and a wrong incident report (2026-10-12).
|
|
36
|
+
|
|
37
|
+
## 1. Snapshot BEFORE
|
|
38
|
+
|
|
39
|
+
Run the dsi-ports pipeline (see dsi-ports skill for the script) and save:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
sh /tmp/dsi-ports.sh > /tmp/floor-before.txt
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 2. Restart
|
|
46
|
+
|
|
47
|
+
- **PREFERRED, every shape: the Restart Verb (shipped — ADR "The Restart Verb,
|
|
48
|
+
One Button, One Route, Receipts", 2026-10).** With the operator's go, click
|
|
49
|
+
the power button in the sidebar header (or `POST /api/floor/restart`). It
|
|
50
|
+
spawns the same detached chain plugin-apply uses, so the floor re-serves in
|
|
51
|
+
the same mode (`--sync --dev in-process` fidelity) even when the previous
|
|
52
|
+
floor lived in the operator's terminal. Receipts still apply: BEFORE/AFTER
|
|
53
|
+
snapshots are mandatory.
|
|
54
|
+
- **Fallback, detached/agent-owned floor:** `dsi dsh --sync-kills`, then
|
|
55
|
+
relaunch the exact previous command (copy it from `ps` BEFORE killing).
|
|
56
|
+
- **Fallback, operator foreground floor (operator declines the verb):** print
|
|
57
|
+
the restart command and WAIT for the operator to run it (their Ctrl-C, their
|
|
58
|
+
terminal). Remember: once it dies, it stays dead until someone starts it.
|
|
59
|
+
|
|
60
|
+
## 3. Snapshot AFTER (wait for both listeners first)
|
|
61
|
+
|
|
62
|
+
Poll until the expected ports answer (host + UI), then:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
sh /tmp/dsi-ports.sh > /tmp/floor-after.txt
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 4. The delta report (this is the deliverable)
|
|
69
|
+
|
|
70
|
+
Present exactly this table — join BEFORE and AFTER on port purpose (not pid: pids always change):
|
|
71
|
+
|
|
72
|
+
| Port | Purpose | RSS before | RSS after | Delta | PID before → after |
|
|
73
|
+
|---|---|---|---|---|---|
|
|
74
|
+
| 3080 | DSH host | 3262 MB | 1489 MB | **−1773 MB** | 38237 → 41200 |
|
|
75
|
+
|
|
76
|
+
Then one summary line: total floor RSS before → after, and the verdict:
|
|
77
|
+
- large negative delta + all expected ports up → **restart healthy**
|
|
78
|
+
- a port missing after → **name it loud** (that half of the floor did not come back)
|
|
79
|
+
- RSS after ≥ before → say so honestly; restart did not reclaim (possible leak — offer dsi-rca)
|
|
80
|
+
|
|
81
|
+
## Boundaries
|
|
82
|
+
|
|
83
|
+
- Never kill a process the operator owns without their confirmed go (§0).
|
|
84
|
+
- Always restore the EXACT previous launch command/flags/env — a restart that changes the floor's shape is a config change, not a restart.
|
|
85
|
+
- The report is the point: a restart without before/after receipts is just downtime.
|
package/bin/dsi.mjs
CHANGED
|
@@ -28,7 +28,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
28
28
|
import { parse, stringify } from 'yaml';
|
|
29
29
|
|
|
30
30
|
import { mirrorSkills } from './lib/mirror-skills.mjs';
|
|
31
|
-
import { dshLauncherCmd, parsePluginArgs } from './lib/plugin-chain.mjs';
|
|
31
|
+
import { dshLauncherCmd, parsePluginArgs, takeoverFloorPorts, hostBounceDecision, floorUiPort, parseDevFloorPids, launchUrl, tokenFromSettings } from './lib/plugin-chain.mjs';
|
|
32
|
+
import { parseDevArg, devModeEnv, modeFromBoolean, resolveBounceMode, extractInspectArgs, parseInspectArgsFromEnv } from './lib/dev-mode.mjs';
|
|
32
33
|
|
|
33
34
|
// Layman-facing help: wrapped near 76 columns, continuation lines indented,
|
|
34
35
|
// the recommended one-liner first. --zai is optional on purpose — no GLM
|
|
@@ -56,7 +57,11 @@ const USAGE = [
|
|
|
56
57
|
' store the dsh launch token as dsh.authToken in',
|
|
57
58
|
' ~/.dsi/settings.yaml, then serve',
|
|
58
59
|
' --dev serve the Vite dev server instead of the built page (takes',
|
|
59
|
-
' port 5175; needs a full checkout: pnpm install)',
|
|
60
|
+
' port 5175; needs a full checkout: pnpm install). Optional',
|
|
61
|
+
' value: --dev in-process boots the DSH host INSIDE the dev',
|
|
62
|
+
' server process (one process, one Ctrl-C; host still on',
|
|
63
|
+
' port 3080; token via in-process intercept). Bare --dev',
|
|
64
|
+
' keeps the two-port pair; --dev pair is the same spelled out.',
|
|
60
65
|
' --sync-kills',
|
|
61
66
|
' kill orphaned dsh/dsi processes left by an earlier run',
|
|
62
67
|
' --ov install the OpenViking memory plugin into the active dsh',
|
|
@@ -195,15 +200,16 @@ if (command === 'dsh') {
|
|
|
195
200
|
// HMR surface as `pnpm dev`, beside the same synced host.
|
|
196
201
|
const rest = process.argv.slice(4);
|
|
197
202
|
const withZai = rest.includes('--zai');
|
|
198
|
-
const withDev = rest.includes('--dev');
|
|
199
203
|
const withOv = rest.includes('--ov');
|
|
200
204
|
if (withZai) addZaiProvider();
|
|
201
205
|
if (withOv) runOvPlugin();
|
|
202
206
|
runSkillsMirror();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
+
// --dev parse (D4): 'in-process' | 'pair' consumed as a value; bare
|
|
208
|
+
// --dev stays 'pair'. dsi-side flags stripped before the host spawn.
|
|
209
|
+
const { mode, rest: hostArgs0 } = parseDevArg(rest);
|
|
210
|
+
// D7: inspector flags ride the vite child's execArgv, never the host.
|
|
211
|
+
const { rest: hostArgs, execArgv: inspectExecArgv } = extractInspectArgs(hostArgs0);
|
|
212
|
+
runSyncedPair(hostArgs, mode, inspectExecArgv);
|
|
207
213
|
} else if (flag === '--plugin' || flag === '--plugin-restart') {
|
|
208
214
|
// The Plugin Rack ADR (2026-09-27, D3/D5): the dsh CLI is the writer,
|
|
209
215
|
// dsi owns the bounce. Install/remove delegates to the launcher rule
|
|
@@ -240,17 +246,67 @@ if (command === 'dsh') {
|
|
|
240
246
|
// before the marker existed, or from plain `pnpm dev`, carries none.
|
|
241
247
|
// The vite dev process itself is visible to pgrep (unlike the npx
|
|
242
248
|
// shims — different argv rewriting), so ask the process table.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
249
|
+
// D6 (2026-10-11): the boolean is no longer enough — an in-process
|
|
250
|
+
// floor re-served as 'pair' would double-spawn the host. Read the
|
|
251
|
+
// alive vite's DSI_DEV_MODE via ps -E BEFORE the sweep, resolve the
|
|
252
|
+
// enum, restore exactly what was running.
|
|
253
|
+
const floorEnvDump = readFloorEnv(devFloorPid());
|
|
254
|
+
const mode = resolveBounceMode({
|
|
255
|
+
flagDev: parsed.dev === true,
|
|
256
|
+
devFloorAlive: isDevFloorAlive(),
|
|
257
|
+
floorEnv: floorEnvDump
|
|
258
|
+
});
|
|
259
|
+
// The Faithful Bounce (ADR 2026-10-13, D3): the SAME floor env that
|
|
260
|
+
// records the mode records the inspector flags — re-forward them so
|
|
261
|
+
// the bounce boot comes back debuggable when it left that way.
|
|
262
|
+
const bounceExecArgv = parseInspectArgsFromEnv(floorEnvDump);
|
|
263
|
+
// ADR-0025 D1/D3 — the bounce fork: an in-process floor with a live
|
|
264
|
+
// bounce verb bounces AT THE HOST via the running floor's route; the
|
|
265
|
+
// vite process never dies and the sweep is SKIPPED. Pair/build floors,
|
|
266
|
+
// a missing verb, or an unreachable route take the sweep below.
|
|
267
|
+
const sweepAndReserve = async () => {
|
|
268
|
+
killOrphans();
|
|
269
|
+
// The pkill sweep cannot see every floor shape (RCA 2026-09-27: the
|
|
270
|
+
// npx shims — 'npm exec @deepseek-ai/dsh…' and '.bin/dsh web' — are
|
|
271
|
+
// invisible to pkill -f on macOS, so the dsh host survived holding
|
|
272
|
+
// 3080 and preflightPort exited 64, killing the detached chain
|
|
273
|
+
// silently). The bounce chain owns the DSI ports by contract, so
|
|
274
|
+
// free them by listener, not by name.
|
|
275
|
+
await freeFloorPorts(floorPortSet());
|
|
276
|
+
// Restore the SAME UI mode the floor was serving. The bounce's
|
|
277
|
+
// boolean signal normalizes to 'pair'/'build' inside runSyncedPair;
|
|
278
|
+
// 3.5 teaches the bounce the full enum via DSI_DEV_MODE.
|
|
279
|
+
runSyncedPair([], mode, bounceExecArgv);
|
|
280
|
+
};
|
|
281
|
+
const bounceVerdict = await hostBounceDecision({
|
|
282
|
+
mode,
|
|
283
|
+
post: async () => {
|
|
284
|
+
// ADR-0025 Wave 3 gate finding (2026-10-14): the route is a DSI
|
|
285
|
+
// (vite) route - POST the vite UI port, not the DSH host port.
|
|
286
|
+
const uiPort = floorUiPort({
|
|
287
|
+
floorEnv: floorEnvDump,
|
|
288
|
+
command: floorCommand(devFloorPid())
|
|
289
|
+
});
|
|
290
|
+
const res = await fetch('http://127.0.0.1:' + uiPort + '/api/plugins/host-bounce', { method: 'POST' });
|
|
291
|
+
return res.ok;
|
|
292
|
+
},
|
|
293
|
+
sweep: sweepAndReserve,
|
|
294
|
+
});
|
|
295
|
+
if (bounceVerdict === 'host') {
|
|
296
|
+
console.log('dsi dsh: host-level bounce done via the running floor — vite never died, sweep skipped (ADR-0025)');
|
|
297
|
+
// D4 amended (2026-10-14): the DSH launch token is minted inside the
|
|
298
|
+
// host process (dsh-client-connection processLaunchToken, a WeakMap on
|
|
299
|
+
// the cordis root — no preset seam), so the bounce CANNOT re-boot with
|
|
300
|
+
// the old token. The bounce's announce intercept lands the NEW token in
|
|
301
|
+
// settings.yaml; hand the operator a URL that works on first click.
|
|
302
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
303
|
+
const fresh = tokenFromSettings(readFileSync(dsiConfigPath(), 'utf-8'));
|
|
304
|
+
const url = launchUrl({ port: process.env.DSH_HOST_PORT ?? 3080, token: fresh });
|
|
305
|
+
if (url) console.log('dsi dsh: fresh launch URL after the bounce (loaded pages keep working — durable cookie):\n\t ' + url);
|
|
306
|
+
} else {
|
|
307
|
+
console.log('dsi dsh: host bounce unavailable — whole-floor sweep fallback (ADR-0025 D3)');
|
|
308
|
+
await sweepAndReserve();
|
|
309
|
+
}
|
|
254
310
|
} else if (flag === '--sync-kills') {
|
|
255
311
|
killOrphans();
|
|
256
312
|
process.exit(0);
|
|
@@ -367,56 +423,80 @@ function openBrowser(url, delayMs = 2000) {
|
|
|
367
423
|
* the Vite dev server (`pnpm dev`'s surface) — as the UI. The pair shares
|
|
368
424
|
* one lifecycle — a signal or one child's exit tears down the other, so no
|
|
369
425
|
* orphan survives a Ctrl-C or a crash. */
|
|
370
|
-
async function runSyncedPair(extraArgs, dev =
|
|
426
|
+
async function runSyncedPair(extraArgs, dev = 'build', execArgv = []) {
|
|
427
|
+
// dev accepts the D4 mode enum ('build' | 'pair' | 'in-process'); the
|
|
428
|
+
// legacy boolean true is normalized so older call sites keep working.
|
|
429
|
+
const mode = typeof dev === 'boolean' ? modeFromBoolean(dev) : dev;
|
|
371
430
|
const script = path.join(packageRoot, 'scripts', 'dsh-web-synced.sh');
|
|
372
|
-
|
|
431
|
+
const inProcess = mode === 'in-process';
|
|
432
|
+
if (!inProcess && !existsSync(script)) {
|
|
373
433
|
console.error(`dsi dsh: no sync script at ${script}`);
|
|
374
434
|
process.exit(66);
|
|
375
435
|
}
|
|
376
436
|
// UI port: the built server takes 5174 — the same port `dsi web` uses,
|
|
377
437
|
// so plain `dsi dsh --sync` listens on ONE port. --dev's Vite dev server
|
|
378
438
|
// takes 5175 instead (vite.config.ts pins 5174 strictPort, so pair mode
|
|
379
|
-
// passes 5175 explicitly), giving the two-port dev shape.
|
|
380
|
-
|
|
439
|
+
// passes 5175 explicitly), giving the two-port dev shape. in-process is
|
|
440
|
+
// also a vite floor: same 5175.
|
|
441
|
+
const uiPort = process.env.PORT ?? (mode === 'build' ? '5174' : '5175');
|
|
381
442
|
// The host half takes --port <n> when forwarded (a 3080 held by a
|
|
382
443
|
// dsh web this CLI does not own — e.g. another live conversation
|
|
383
444
|
// floor — is exactly the case the preflight must not kill). The
|
|
384
445
|
// forwarded flag goes to `dsh web` untouched; DSI follows via
|
|
385
446
|
// DSH_BASE_URL so the page dials the host wherever it landed.
|
|
447
|
+
// IN-PROCESS: no host child will be spawned, so preflighting 3080 is
|
|
448
|
+
// pointless — the plugin boots the host on 3080 AFTER vite is up, and
|
|
449
|
+
// a stale 3080 from a foreign floor is that floor's business (the
|
|
450
|
+
// embedded boot fails loud in the plugin, killing nothing).
|
|
386
451
|
warnIfFirstRunDownload();
|
|
387
|
-
|
|
452
|
+
// IN-PROCESS: the plugin reads its port from DSH_HOST_PORT (default 3080)
|
|
453
|
+
// — the UI half MUST dial the same port, so DSH_HOST_PORT participates in
|
|
454
|
+
// the dshBase derivation exactly like a forwarded --port flag. (Smoke
|
|
455
|
+
// bug 2026-10-11: DSH_HOST_PORT=3477 booted the host there while the
|
|
456
|
+
// page dialed 3080 — the production floor — → "Failed to fetch".)
|
|
457
|
+
const pluginPort = inProcess && process.env.DSH_HOST_PORT ? parseInt(process.env.DSH_HOST_PORT, 10) : undefined;
|
|
458
|
+
const hostPort = hostPortFromArgs(extraArgs) ?? pluginPort ?? 3080;
|
|
388
459
|
const dshBase = hostPort === 3080 ? undefined : `http://127.0.0.1:${hostPort}`;
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
460
|
+
if (inProcess) {
|
|
461
|
+
await preflightPort(uiPort, 'DSI page');
|
|
462
|
+
} else {
|
|
463
|
+
await preflightPort(hostPort, 'dsh host');
|
|
464
|
+
await preflightPort(uiPort, 'DSI page');
|
|
465
|
+
}
|
|
466
|
+
// IN-PROCESS: no sync script, no host child — the vite plugin owns the
|
|
467
|
+
// host lifecycle (vite close hook stops it; one Ctrl-C, no orphan).
|
|
468
|
+
const host = inProcess ? null : spawn('/bin/sh', [script, ...extraArgs], {
|
|
395
469
|
stdio: 'inherit',
|
|
396
470
|
env: { ...process.env, DSH_WEB_VERSION: dshWebVersion() }
|
|
397
471
|
});
|
|
398
|
-
const ui =
|
|
472
|
+
const ui = mode === 'build' ? startDsiServer(uiPort, dshBase) : startDsiDevServer(uiPort, dshBase, mode, execArgv);
|
|
399
473
|
// The browser follows the UI: the built page (5174) — or, in --dev mode,
|
|
400
474
|
// the dev server (5175).
|
|
401
475
|
openBrowser(`http://localhost:${uiPort}/`);
|
|
402
476
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
403
477
|
process.on(signal, () => {
|
|
404
|
-
host.kill(signal);
|
|
478
|
+
if (host) host.kill(signal); // in-process: no host child — vite close hook stops it
|
|
405
479
|
ui.kill(signal);
|
|
406
480
|
});
|
|
407
481
|
}
|
|
408
482
|
const teardown = (code, signal) => {
|
|
409
|
-
host.kill('SIGTERM');
|
|
483
|
+
if (host) host.kill('SIGTERM');
|
|
410
484
|
ui.kill('SIGTERM');
|
|
411
485
|
process.exit(signal ? 1 : (code ?? 1));
|
|
412
486
|
};
|
|
413
|
-
host.on('exit', teardown);
|
|
487
|
+
if (host) host.on('exit', teardown);
|
|
414
488
|
ui.on('exit', teardown);
|
|
415
489
|
}
|
|
416
490
|
|
|
417
491
|
/** Kill orphaned dsh/dsi leftovers — a pair whose parent died before it
|
|
418
492
|
* could tear down its sibling, or a previous run the terminal lost. */
|
|
419
493
|
function killOrphans() {
|
|
494
|
+
// Test-floor isolation (Port Ledger 34xx/51xx): a scratch floor's bounce
|
|
495
|
+
// must never pkill the production floor's shapes.
|
|
496
|
+
if (process.env.DSI_SKIP_ORPHAN_SWEEP === '1') {
|
|
497
|
+
console.log('dsi dsh: orphan sweep skipped (DSI_SKIP_ORPHAN_SWEEP=1)');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
420
500
|
for (const pattern of ORPHAN_PATTERNS) {
|
|
421
501
|
try {
|
|
422
502
|
spawnSync('pkill', ['-f', pattern], { stdio: 'pipe' });
|
|
@@ -428,31 +508,103 @@ function killOrphans() {
|
|
|
428
508
|
console.log('dsi dsh: orphan sweep done (dsh-web-synced.sh, dsh bin, dsi server)');
|
|
429
509
|
}
|
|
430
510
|
|
|
431
|
-
/**
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
511
|
+
/** The DSI ports the bounce owns. DSI_FLOOR_PORTS overrides for isolated
|
|
512
|
+
* test floors (Port Ledger: 34xx host, 5180+ UI) so a scratch bounce never
|
|
513
|
+
* touches the production ports. */
|
|
514
|
+
function floorPortSet() {
|
|
515
|
+
const raw = process.env.DSI_FLOOR_PORTS;
|
|
516
|
+
if (!raw) return [3080, 5174, 5175];
|
|
517
|
+
const ports = raw.split(',').map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0);
|
|
518
|
+
return ports.length ? ports : [3080, 5174, 5175];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** SIGTERM whatever LISTENS on these ports, then WAIT until they actually
|
|
522
|
+
* free (The Port Takeover ADR 2026-10-12, D2/D3/D5): the fire-and-forget
|
|
523
|
+
* kill of RCA 2026-09-27 raced the successor's bind — the dying in-process
|
|
524
|
+
* host still held 3080 two seconds later and the new floor died on
|
|
525
|
+
* EADDRINUSE (incident: tmp/bounce.log 2026-10-12T03:49:06Z). Every kill
|
|
526
|
+
* and every freed port gets a receipt line; a port still held at the
|
|
527
|
+
* budget exits 64 naming the port and the holder. Only the bounce uses
|
|
528
|
+
* this — the click is the consent (ADR D1); a manual --sync preflight
|
|
529
|
+
* still REFUSES a busy port it does not own. */
|
|
530
|
+
async function freeFloorPorts(ports) {
|
|
531
|
+
const listListeners = (port) => {
|
|
439
532
|
try {
|
|
440
|
-
spawnSync('/bin/sh', ['-c', 'lsof -ti tcp:' + port + ' -sTCP:LISTEN
|
|
533
|
+
const probe = spawnSync('/bin/sh', ['-c', 'lsof -ti tcp:' + port + ' -sTCP:LISTEN 2>/dev/null || true'], { stdio: 'pipe', encoding: 'utf8' });
|
|
534
|
+
return (probe.stdout || '').split('\n').map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0);
|
|
441
535
|
} catch {
|
|
442
|
-
// no lsof (or no
|
|
443
|
-
//
|
|
536
|
+
// no lsof (or no sh): nothing to signal; the wait loop below will
|
|
537
|
+
// report the port honestly if it never frees.
|
|
538
|
+
return [];
|
|
444
539
|
}
|
|
540
|
+
};
|
|
541
|
+
const result = await takeoverFloorPorts(ports, {
|
|
542
|
+
listListeners,
|
|
543
|
+
terminate: (pid) => {
|
|
544
|
+
try { process.kill(pid, 'SIGTERM'); } catch { /* already gone — the wait decides */ }
|
|
545
|
+
},
|
|
546
|
+
portFree: (port) => portInUse(port).then((busy) => !busy),
|
|
547
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
548
|
+
now: () => Date.now(),
|
|
549
|
+
log: (line) => console.log(line)
|
|
550
|
+
});
|
|
551
|
+
if (!result.ok) {
|
|
552
|
+
console.error('dsi dsh: port ' + result.port + ' is still in use by a process the takeover does not own (pid ' + result.pid + ') — free it yourself, then re-run');
|
|
553
|
+
process.exit(64);
|
|
445
554
|
}
|
|
446
555
|
}
|
|
447
556
|
|
|
448
557
|
/** True when a vite dev server of THIS checkout is alive. Only the
|
|
449
558
|
* checkout's own vite matches — a foreign project's dev server never
|
|
450
|
-
* carries the deepseek-insight path segment.
|
|
451
|
-
*
|
|
452
|
-
*
|
|
559
|
+
* carries the deepseek-insight path segment. ADR-0025 Wave 3 (2026-10-14):
|
|
560
|
+
* the probe rides devFloorPid's ps -eo discovery — pgrep -f went blind to
|
|
561
|
+
* the vite process on the live floor, so this boolean read false-negative
|
|
562
|
+
* and the bounce chain swept floors it could have host-bounced. */
|
|
453
563
|
function isDevFloorAlive() {
|
|
454
|
-
|
|
455
|
-
|
|
564
|
+
return devFloorPid() !== null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** The alive dev floor's vite pid (first match), or null. Same match rule
|
|
568
|
+
* as isDevFloorAlive — the pid feeds the D6 mode read (ps -E). */
|
|
569
|
+
function devFloorPid() {
|
|
570
|
+
|
|
571
|
+
// ADR-0025 Wave 3 gate finding (2026-10-14): pgrep -f went BLIND to the
|
|
572
|
+
// vite process on the live floor (exit 1 while ps listed it), so the
|
|
573
|
+
// chain misread the floor as absent and swept a bounceable in-process
|
|
574
|
+
// floor. ps -eo pid,command sees what pgrep does not; the parse is the
|
|
575
|
+
// pure parseDevFloorPids twin in plugin-chain.mjs.
|
|
576
|
+
const probe = spawnSync('ps', ['-eo', 'pid=,command='], { stdio: 'pipe', timeout: 5000 });
|
|
577
|
+
if ((probe.status ?? 1) !== 0) return null;
|
|
578
|
+
const first = parseDevFloorPids(probe.stdout?.toString() || '')[0];
|
|
579
|
+
return first ?? null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** The floor process's command line (ps -o command), or '' on any failure -
|
|
583
|
+
* feeds floorUiPort's --port arm (the launcher passes the vite port as an
|
|
584
|
+
* argument, not as env). */
|
|
585
|
+
function floorCommand(pid) {
|
|
586
|
+
if (!pid) return '';
|
|
587
|
+
try {
|
|
588
|
+
const ps = spawnSync('ps', ['-o', 'command', '-p', String(pid)], { stdio: 'pipe', timeout: 5000 });
|
|
589
|
+
if (ps.status !== 0) return '';
|
|
590
|
+
const lines = (ps.stdout?.toString() || '').split('\n').filter(Boolean);
|
|
591
|
+
return lines.length > 1 ? lines.slice(1).join('\n') : '';
|
|
592
|
+
} catch {
|
|
593
|
+
return '';
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** The floor's DSI_DEV_MODE via ps -E (macOS: own processes only). Returns
|
|
598
|
+
* '' when the read fails for any reason — resolveBounceMode then falls back
|
|
599
|
+
* to the boolean arms, the pre-D6 behavior, never a wrong restore. */
|
|
600
|
+
function readFloorEnv(pid) {
|
|
601
|
+
if (!pid) return '';
|
|
602
|
+
try {
|
|
603
|
+
const ps = spawnSync('ps', ['-E', '-p', String(pid)], { stdio: 'pipe', timeout: 5000 });
|
|
604
|
+
return ps.status === 0 ? ps.stdout?.toString() || '' : '';
|
|
605
|
+
} catch {
|
|
606
|
+
return '';
|
|
607
|
+
}
|
|
456
608
|
}
|
|
457
609
|
|
|
458
610
|
/** Add the ZAI provider and default model to ~/.dsh/settings.yaml when
|
|
@@ -573,7 +725,7 @@ function runSkillsMirror() {
|
|
|
573
725
|
* sources and devDependencies exist — a published tarball (bin + build)
|
|
574
726
|
* refuses loudly instead of half-booting. The --port CLI flag overrides the
|
|
575
727
|
* config's server.port while strictPort keeps the fail-loud contract. */
|
|
576
|
-
function startDsiDevServer(defaultPort = '5175', dshBase = undefined) {
|
|
728
|
+
function startDsiDevServer(defaultPort = '5175', dshBase = undefined, mode = 'pair', execArgv = []) {
|
|
577
729
|
const viteBin = path.join(packageRoot, 'node_modules', 'vite', 'bin', 'vite.js');
|
|
578
730
|
if (!existsSync(viteBin)) {
|
|
579
731
|
console.error(`dsi dsh --dev: no vite at ${viteBin} — --dev needs a full DSI checkout (pnpm install); use the built server instead`);
|
|
@@ -581,7 +733,13 @@ function startDsiDevServer(defaultPort = '5175', dshBase = undefined) {
|
|
|
581
733
|
}
|
|
582
734
|
// --host matches the built server's 127.0.0.1 bind: vite's default
|
|
583
735
|
// 'localhost' can resolve ::1-only and strand a 127.0.0.1 client.
|
|
584
|
-
return spawn(process.execPath, [viteBin, 'dev', '--host', process.env.HOST ?? '127.0.0.1', '--port', process.env.PORT ?? defaultPort], {
|
|
736
|
+
return spawn(process.execPath, [...execArgv, viteBin, 'dev', '--host', process.env.HOST ?? '127.0.0.1', '--port', process.env.PORT ?? defaultPort], {
|
|
737
|
+
// D7: inspector flags lead the child argv — node consumes options before
|
|
738
|
+
// the script path, so `node --inspect vite.js dev` debugs THIS process
|
|
739
|
+
// (vite + the embedded DSH host share one heap). The spawn execArgv
|
|
740
|
+
// OPTION was tried first and empirically never activated the inspector
|
|
741
|
+
// (silent no-op, probed 2026-10-11); leading args are the reliable form.
|
|
742
|
+
// NODE_OPTIONS is banned — it leaks to every child and collides on port.
|
|
585
743
|
stdio: 'inherit',
|
|
586
744
|
cwd: packageRoot,
|
|
587
745
|
env: {
|
|
@@ -592,6 +750,13 @@ function startDsiDevServer(defaultPort = '5175', dshBase = undefined) {
|
|
|
592
750
|
// this from the API server's env to re-serve --dev instead of the
|
|
593
751
|
// stale build. Nothing else may set it.
|
|
594
752
|
DSI_DEV: '1',
|
|
753
|
+
// D4/D6: the mode rides as env, never past the CLI to the host.
|
|
754
|
+
...devModeEnv(mode),
|
|
755
|
+
// The Faithful Bounce (ADR 2026-10-13, D1): the inspector flags are
|
|
756
|
+
// part of the launch record — stamped so every restorer (the bounce
|
|
757
|
+
// boot via ps -E, the delayed re-serve via inherited env) can put
|
|
758
|
+
// them back exactly as launched.
|
|
759
|
+
...(execArgv.length ? { DSI_INSPECT_ARGS: execArgv.join(' ') } : {}),
|
|
595
760
|
...(dshBase !== undefined ? { DSH_BASE_URL: dshBase } : {})
|
|
596
761
|
}
|
|
597
762
|
});
|