DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

McKayGONG /

McKayGONG/dsh-safe-restart

Verified

This plugin has no description yet.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@613c67f5

dsh-safe-restart

A macOS-only DSH host plugin that safely restarts the DSH process from inside an Agent session. It terminates only the exact DSH listener process through a detached helper, lets the browser reconnect through DSH's native ConnectionController (no manual refresh), resumes the original persisted session, and starts exactly one automatic continuation turn to verify the restart and continue the interrupted task.

The plugin registers a single Agent tool, restart_dsh. There is no Web button and no settings UI.

Design: docs/plans/2026-08-22-dsh-safe-restart-design.md

What it does

  • One Agent tool: restart_dsh — schedules a safe restart of the current DSH host, bound to the invoking Agent's current session.
  • macOS only — every other platform is rejected before any side effect.
  • Verified on DSH 0.1.0-rc.8 — the recovery coordinator relies on host internals that are still moving in the rc cycle (agent/created, ctx.agents.resume setup, installModelSelection). End-to-end restart + auto-continuation was verified on 0.1.0-rc.8 (standalone profile, macOS). The declared range is >=0.1.0-rc.8 <0.2.0; anything outside it is untested and may need adapter work. After any DSH upgrade, re-verify: pnpm test, then one real restart_dsh round — confirm PID change, HTTP 200, journal consumed, and a continuation turn whose turn/end reason is completed (not error).
  • Detached helper — the journal write is durable first, then a helper process is spawned detached and unref'd; it survives host termination, replaces the DSH process, and records the outcome.
  • Native browser reconnect — after the host comes back, the existing Web client reconnects through DSH's built-in connection/resync loop. No manual page refresh and no "continue" message are needed.
  • One automatic continuation turn — the same persisted session resumes with its preset and model selection, and exactly one plugin-sourced continuation message is delivered per restart.
  • Interrupted streams are not resumed — the exact token stream that was running when the host dropped is not continued. DSH closes that turn as interrupted; recovery starts a new turn in the same session.

Tool schema

restart_dsh({
  "continue_after_restart": true,   // boolean, default true
  "continuation": "optional instruction" // string, optional, max 2000 chars
})
Field Type Default Meaning
continue_after_restart boolean true Whether to deliver one automatic continuation turn after recovery. Set false for a pure restart with no continuation.
continuation string (optional) — Task-specific instruction for the single continuation turn (max 2000 chars). When omitted, the default instruction is: "Verify that DSH recovered successfully, then continue the pre-restart task using the current session and durable workspace state."

The target session is always the invoking Agent's current session; a caller cannot name another session. The tool returns:

{ "restartId": "<uuid>", "scheduled": true, "continueAfterRestart": true }

Installation

Install from the plugin repository root:

dsh plugin --profile standalone add /path/to/dsh-safe-restart

The plugin activates on the next DSH start — restart_dsh is not loaded until then. Use an already-validated external restart path (e.g. a codex / safe-restart script that stops and relaunches DSH and verifies HTTP 200) for that one activation restart, then verify http://127.0.0.1:<port> returns HTTP 200 before continuing.

Usage

Ask the Agent to call restart_dsh. With the default continue_after_restart: true, what the user sees is:

  1. The host connection drops briefly (the tool response may be cut short).
  2. The browser reconnects automatically through DSH's native ConnectionController — no manual refresh.
  3. The original persisted session reappears with its preset and model.
  4. Exactly one new assistant turn appears, carrying the restart ID marker [restartId: <uuid>], verifying recovery and continuing the task.

A restart with "continue_after_restart": false performs the same safe restart and browser recovery but adds no continuation turn.

Safety

  • Never killall — the helper terminates only the recorded PID, and only after verifying its exact command line against the launch identity recorded at schedule time (the current Node executable plus the DSH entry argument). An unrelated or unknown process is never touched.
  • One active restart — a second restart_dsh while a record is in flight is rejected (restart already active).
  • 60-second cooldown — a restart within 60 s of the most recent one is rejected (restart in cooldown).
  • 30-second health timeout — after spawning the replacement, the helper polls the health endpoint for at most 30 s (PID exit and port release use a 12 s budget).
  • No retry loop — each restartId is executed at most once; a record that is not scheduled is refused, and a failed restart is never retried.
  • Exactly-once delivery — before delivering the continuation, the coordinator scans the session's durable log for the restart ID marker; a previous delivery that crashed before the consumed transition is detected and consumed without re-delivering. An ambiguous window becomes delivery-uncertain instead of a blind retry.
  • No secrets persisted — the journal and log carry no credentials and no environment values.
  • Interrupted token stream is not resumed — the continuation is a fresh turn in the same session, never a replay of the interrupted generation.

Storage

All durable state lives under ~/.dsh/storages/dsh-safe-restart/:

Path Purpose
active.json The single in-flight restart record (atomic writes: temp file → fsync → rename).
.lock Exclusive restart lock, created with open(..., 'wx'); one active restart at a time.
history/<restartId>.json Terminal records, archived when a record reaches a terminal phase.
restart.log Timestamped, non-secret lifecycle lines: restartId, old/new PID, port-release result, HTTP result, terminal phase.

Phases

Phase Meaning
scheduled The tool request was persisted; the detached helper has not claimed it yet.
restarting The helper validated and terminated the old PID, spawned the replacement, and is waiting for health.
ready The new host answered HTTP 200; newPid and readyAt are set. Waiting for the resumed session's Agent.
delivering The continuation message was created and handed to the Agent; awaiting durable confirmation.
consumed Terminal: exactly one continuation delivered (or, for continueAfterRestart=false, none) and archived.
failed Terminal: the restart failed with a bounded, non-secret error message; never retried.
delivery-uncertain Terminal: the continuation window was ambiguous (e.g. the host died between delivering and confirmation). Not blindly redelivered.

Troubleshooting

  • failed record — check restart.log and the record's error field (e.g. PID validation failure, port not released, health timeout). The record is already archived; no action is required for future restarts.

  • delivery-uncertain record — the continuation may or may not have been delivered. Check the session's durable log for the marker [restartId: <uuid>]:

    • marker present → the message was delivered; the record is archived, done.
    • marker absent → it was not delivered and will not be redelivered; decide whether to re-run the task manually.
  • Stale lockless scheduled record — if the helper crashed before it could claim the record (or the claim failed), a scheduled record with no helper running and no lock can be left behind. Recovery polls it for a bounded window and then deliberately leaves it for inspection. It blocks new restarts (restart already active).

  • Ready but no Agent (blocks subsequent restarts) — if a record reaches ready but the matching session's Agent never reappears (for example the session was deleted or the browser tab was never reopened), active.json stays in ready and every later restart_dsh is rejected until the record is resolved.

    Remedy (manual, after inspection):

    1. Inspect ~/.dsh/storages/dsh-safe-restart/active.json — note its restartId and phase — and the latest lines of ~/.dsh/storages/dsh-safe-restart/restart.log to see what happened.
    2. If the continuation marker [restartId: <restartId>] already exists in the session's durable log, the continuation was delivered; archive the record as consumed: move active.json to history/<restartId>.json with "phase": "consumed" and remove active.json.
    3. If the continuation was never delivered and never will be, archive the record with a terminal phase instead ("failed" or "delivery-uncertain"), then remove active.json.
    4. Check ~/.dsh/storages/dsh-safe-restart/.lock — it records the owning restartId. Remove the lock only when its restartId matches the archived record; never delete a lock owned by a different restart.
    5. Leave history/<restartId>.json and restart.log in place — they are the audit trail. The next restart_dsh is then allowed again.

Verification script

scripts/verify-real-restart.mjs is an external, read-only evidence collector that runs outside the DSH process (plain Node ESM, zero dependencies). It never performs a restart and never touches the live listener. It reads the journal and probes the HTTP endpoint, then reports one JSON object:

{
  "oldPid": 111,            // replaced DSH PID, from the selected record
  "newPid": 222,            // healthy replacement PID (null when unknown)
  "pidChanged": true,       // newPid is set and differs from oldPid
  "http200": true,          // live GET http://127.0.0.1:<port>/ returned 200
  "phase": "consumed",      // lifecycle phase of the selected record
  "continuationCount": 1    // consumed records that delivered a continuation
}
node scripts/verify-real-restart.mjs --check-latest        # latest history record, default port 3081
node scripts/verify-real-restart.mjs --journal /path/to/journal --port 3000
node scripts/verify-real-restart.mjs --journal /tmp/fixture --port 0 --dry-run   # detection only, no HTTP

--dry-run prints the detection logic (listener PID via lsof -ti tcp:<port> and a journal contents summary) without probing HTTP, so it can be exercised without a live restart. It exits nonzero on malformed journal JSON, an unusable listener detection, or unexpected errors. continuationCount counts consumed records whose continueAfterRestart is true — per the design each such record delivered exactly one continuation turn; pure-restart records (false) delivered none and are not counted.

Uninstall

Remove the plugin with the remove counterpart of the install command, e.g.:

dsh plugin --profile standalone remove dsh-safe-restart

then restart DSH once so the tool is dropped. If you also want to remove the durable state, delete ~/.dsh/storages/dsh-safe-restart/ after confirming no restart is in flight (no active.json).

Recovery limitation

If the browser tab was closed during the restart, simply reopen it: DSH's native reconnect restores the same persisted session (same preset, model, and history). The continuation is delivered exactly once regardless of how many times the tab reconnects.

—/ 5

No ratings yet

Verified DSH bundle

Commit 613c67f5caab

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout