# DSH Safe Restart Plugin Design **Date:** 2026-08-22 **Status:** Approved **Target:** macOS, DSH 0.1.0-rc.8 ## Goal Build a DSH plugin that exposes an Agent tool named `restart_dsh`. The tool safely restarts the current DSH host through a detached helper, automatically restores the browser without user interaction, resumes the original persisted session, and starts exactly one new turn to verify the restart and continue the interrupted task. ## Scope ### V1 includes - One Agent tool: `restart_dsh`. - macOS support only. - Detached helper process that survives DSH termination. - Safe PID and command validation; never use `killall`. - HTTP health verification after restart. - Durable restart journal and single-delivery continuation. - Browser recovery through DSH's native reconnect/resync loop; add an invisible forced-reload client fallback only if end-to-end testing proves native recovery insufficient. - Same-session continuation through a new Agent turn. - Diagnostic logs, lock, cooldown, timeout, and loop prevention. ### V1 excludes - Windows and Linux. - Visible Web buttons or settings UI. - Resuming the exact interrupted LLM stream. - Remote or multi-host DSH. - DSH upgrades. - Codex or MCP dependencies. ## Validated Feasibility A throwaway spike verified both layers on this machine: 1. A Node parent used `spawn({ detached: true, stdio: 'ignore' }).unref()` to launch a helper. After the parent exited, the helper survived with `PPID=1` and its own process group. 2. A real detached helper restarted DSH: old PID `89538` stopped, new PID `93477` started, and `http://127.0.0.1:3081` returned HTTP 200. No Codex or manual fallback was involved. The spike files live under `/tmp` and are not production code. ## Architecture ### Server plugin Registers `restart_dsh`, validates the environment, writes the durable journal atomically, acquires a restart lock, and spawns the detached helper. ### Detached restart helper Waits briefly for the tool invocation to settle, validates and stops the exact DSH listener process, relaunches DSH with the current Node executable, arguments, environment, and working directory, polls the health endpoint, and records success or failure. ### Recovery coordinator On new-host startup, reads an unfinished journal and waits for DSH's native browser/API reconnect path to resume the original persisted session with its preset and model selection. When the matching Agent is published, it sends one plugin-sourced continuation message and marks the restart consumed. ### Browser recovery DSH's native `ConnectionController` already reconnects with exponential backoff, performs a readiness handshake, and republishes the session baseline after connection reset. V1 relies on this built-in behavior. An invisible client fallback that performs one bounded `location.reload()` is added only if the end-to-end restart test demonstrates that native reconnect cannot restore the active session. ## Tool Interface ```ts restart_dsh({ continue_after_restart?: boolean, // default true continuation?: string // optional task-specific instruction }) ``` The target session is always the invoking Agent's current session. The caller cannot specify another session ID. If `continuation` is omitted, the default instruction is: > Verify that DSH recovered successfully, then continue the pre-restart task using the current session and durable workspace state. ## Durable Journal Suggested storage location: ```text ~/.dsh/storages/dsh-safe-restart/ ``` A record contains: ```json { "version": 1, "restartId": "uuid", "sessionId": "current-session-id", "phase": "scheduled", "continueAfterRestart": true, "continuation": "task-specific instruction", "requestedAt": "ISO timestamp", "oldPid": 12345, "attempts": 0 } ``` Allowed lifecycle: ```text scheduled -> restarting -> ready -> delivering -> consumed | | +-------> failed delivering -> delivery-uncertain ``` Writes use a temporary file followed by atomic rename. ## Continuation Semantics The interrupted LLM stream is not resumed. DSH's session repair closes the orphaned turn as interrupted. The plugin then resumes the same persisted session and starts a new turn with source metadata: ```ts { kind: 'plugin', plugin: 'dsh-safe-restart', form: 'notice', summary: 'Continue after successful DSH restart' } ``` The continuation includes the unique `restartId`. Before delivery, the coordinator checks the session log for that ID. It never impersonates a direct user message. ## Safety and Failure Handling - Validate macOS and reject unsupported platforms clearly. - Validate that the target PID is the exact listener and its command is DSH. - Kill only that PID; never use `killall`. - Reuse the current process executable, argv, environment, cwd, profile, and actual Web port. - Allow only one active restart lock. - Apply a 60-second restart cooldown. - Execute each `restartId` at most once. - Poll startup for at most 30 seconds. - Never retry a failed restart in a loop. - Mark an ambiguous continuation window as `delivery-uncertain` instead of blindly redelivering. - Do not persist secrets in the journal or log. - Clean up lock state on terminal success or failure. ## Browser Recovery V1 first exercises DSH's built-in browser reconnection and baseline replay. The acceptance boundary is behavioral: the active browser must return to the same session without user action. If that test fails, add a minimal invisible client contribution that polls once per second, reloads once after health returns, preserves the session route, and stops after a bounded timeout without creating reload loops. ## Testing Strategy ### Unit tests - Journal validation and state transitions. - Atomic storage operations. - Lock, cooldown, timeout, and restartId deduplication. - PID/command validation. - Continuation construction and plugin message source. ### Process integration tests - Detached helper survives parent exit. - Helper replaces a test service and observes health recovery. - Failed starts terminate without loops. - A completed restartId is never executed again. ### DSH end-to-end test - Invoke `restart_dsh` from an active session. - Confirm old PID exits and new PID starts. - Confirm HTTP 200. - Confirm browser recovers without manual refresh. - Confirm the original session reopens. - Confirm exactly one automatic continuation turn appears. - Confirm `continue_after_restart=false` performs no continuation. - Confirm a later page refresh does not duplicate continuation. ## Acceptance Criteria - Agent can call `restart_dsh`. - User performs no manual page refresh. - User sends no manual “continue” message. - Original persisted session is restored. - Continuation occurs exactly once. - Pure restart mode works. - Failures cannot cause an infinite restart loop. - Logs identify the restart ID, old/new PID, health result, and continuation outcome. - Unsupported platforms are rejected without side effects.