mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(app-boot): release the terminal before a fatal load exit
A dsh launch whose config failed validation returned the user to a broken shell: typing was invisible and the next command was mangled by a stray Device Attributes reply (1;2;4cecho ...). The Loader mounts entries concurrently, so ui-tui can already hold the terminal (raw mode, bracketed paste, keyboard protocol, plus an in-flight DA query) when a sibling entry rejects on its own config. installFailLoud wrote its diagnostic and exited immediately, so nothing disposed the tree and ProcessTerminal.stop() never ran. Give installFailLoud an optional release teardown, awaited between the diagnostic and the exit and bounded by FAIL_LOUD_RELEASE_TIMEOUT_MS. The TUI launcher passes one that disposes the root context, reaching the same shutdown() the /exit path already uses (drainInput() + ui.stop()). The context is captured in boot()'s prepare hook because the rejection arrives while boot() is still in flight. Bins that pass no release keep the previous behavior exactly.
This commit is contained in:
@@ -113,7 +113,6 @@ export async function runTui(
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
installFailLoud(NAME)
|
||||
// The bin already loaded the invoking directory's .env, and that is the
|
||||
// whole environment: $DSH_HOME/.env is credentials-local's writable store,
|
||||
// and hoisting it would make every stored key read as a read-only ambient
|
||||
@@ -140,6 +139,17 @@ export async function runTui(
|
||||
const entry = process.argv[1]
|
||||
const execve = process.execve?.bind(process)
|
||||
const app: { current?: Context } = {}
|
||||
// The Loader mounts entries concurrently, so `ui-tui` can already hold the
|
||||
// terminal (raw mode, bracketed paste, keyboard protocol) when a sibling
|
||||
// entry rejects — and that rejection arrives while `boot` is still in
|
||||
// flight. Disposing the tree runs the TUI's own shutdown, which stops the
|
||||
// terminal and hands the shell back; without it a failed boot returns to a
|
||||
// corrupted prompt. `app.current` is captured from boot's `prepare` hook, so
|
||||
// it holds the root context for the whole mounting window rather than only
|
||||
// after boot resolves.
|
||||
installFailLoud(NAME, process, async () => {
|
||||
await app.current?.fiber.dispose()
|
||||
})
|
||||
// Resume always enters the default surface because experimental-meta rejects
|
||||
// parent options, including `--resume`. The resumed session already persists
|
||||
// its cwd.
|
||||
@@ -216,6 +226,10 @@ export async function runTui(
|
||||
bootConfig,
|
||||
patches,
|
||||
(hostCtx) => {
|
||||
// Runs after the Loader installs and before any config-tree entry mounts,
|
||||
// so the fail-loud release hook can reach the tree for the whole window in
|
||||
// which an entry may reject.
|
||||
app.current = hostCtx
|
||||
// The launcher owns session identity and the exit line: a config-mounted
|
||||
// app bundle reads both from these slots, so no cordis.yml key can drop
|
||||
// resume.
|
||||
|
||||
Reference in New Issue
Block a user