home-hosted 0.7.19 → 0.7.20
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.md +6 -0
- package/README.md +1 -1
- package/dist/cli.js +202 -16
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/uis/stock/dist/ui.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -121,6 +121,12 @@ nobody reads is worse than a long file.
|
|
|
121
121
|
never recreated.
|
|
122
122
|
- **A port is only ever freed by re-listing its listeners.** `free-port` never trusts a pid quoted in
|
|
123
123
|
a message, and refuses any listener in `supervisedPids()` instead of killing it.
|
|
124
|
+
- **A live pid is not the running panel.** Pids are recycled and a zombie still answers signal 0, so
|
|
125
|
+
`up`/`status`/`down` decide with `runtimeIsAlive`/`runtimeLiveness` (pid **plus** `run.json`'s own
|
|
126
|
+
`startedAt`), never `isProcessAlive` alone — otherwise `up` refuses against a dead panel, `status`
|
|
127
|
+
reports "running, but not answering", and `down` signals an unrelated process. Only `'gone'`
|
|
128
|
+
(nothing behind the pid) deletes a record; `'recycled'` blocks a **signal** but still allows the
|
|
129
|
+
token-authenticated `/_hh/shutdown`, because the birth time is wall-clock-derived.
|
|
124
130
|
- **Only `kill` ever touches a process the panel could not identify** — which is why it logs the pids
|
|
125
131
|
it stopped. `follow` adopts this entry's own successor, `reclaim` replaces it. Ownership is the
|
|
126
132
|
entry's whole **tree**, and the panel's own tree is excluded. Full matching rules:
|
package/README.md
CHANGED
|
@@ -341,7 +341,7 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.
|
|
|
341
341
|
| `home-hosted up` | start the panel detached, and keep it alive in the background |
|
|
342
342
|
| `home-hosted down` | stop it cleanly — supervised processes included, persistent entries left running |
|
|
343
343
|
| `home-hosted restart` | `down`, then `up` — or `restart <id>` to restart one server, leaving the panel up |
|
|
344
|
-
| `home-hosted status` | pid, URL, health, uptime, state and log paths — the panel's console *and* the per-server directory, naming the `<id>.log` convention once logs exist (`--json` for scripts; stopped answers `{ running: false, initialised }`, where `initialised: false` means nothing was ever set up here) |
|
|
344
|
+
| `home-hosted status` | pid, URL, health, uptime, state and log paths — the panel's console *and* the per-server directory, naming the `<id>.log` convention once logs exist (`--json` for scripts; stopped answers `{ running: false, initialised }`, where `initialised: false` means nothing was ever set up here, plus `liveness` — `gone`, or `recycled` when the OS has handed the recorded pid to another process and the record is stale) |
|
|
345
345
|
| `home-hosted start <id>` | start one server in the default workspace — and anything it `dependsOn` (`--workspace <id>`) |
|
|
346
346
|
| `home-hosted stop <id>` | stop one server, nothing else (`--workspace <id>`) |
|
|
347
347
|
| `home-hosted restart <id>` | restart one server, nothing else (`--workspace <id>`) — the same as the panel's per-server Restart |
|
package/dist/cli.js
CHANGED
|
@@ -7301,6 +7301,125 @@ function isProcessAlive(pid) {
|
|
|
7301
7301
|
return error.code === "EPERM";
|
|
7302
7302
|
}
|
|
7303
7303
|
}
|
|
7304
|
+
/**
|
|
7305
|
+
* `/proc/<pid>/stat` field 22 is the start time in clock ticks since boot, which only
|
|
7306
|
+
* becomes an epoch by adding `btime` from `/proc/stat`. Field 3 is the state.
|
|
7307
|
+
*
|
|
7308
|
+
* The comm field is parenthesised and may itself hold spaces and parentheses, so the
|
|
7309
|
+
* fields are counted from after the **last** `)`. Counting from the first would shift
|
|
7310
|
+
* every offset for a process whose name contains a `)`, and this value decides whether a
|
|
7311
|
+
* live-looking pid is really the daemon.
|
|
7312
|
+
*/
|
|
7313
|
+
function parseProcBirth(content, btimeSeconds, hz = LINUX_HZ) {
|
|
7314
|
+
const close = content.lastIndexOf(")");
|
|
7315
|
+
if (close < 0) return null;
|
|
7316
|
+
const fields = content.slice(close + 2).split(" ");
|
|
7317
|
+
const ticks = Number.parseInt(fields[19] ?? "", 10);
|
|
7318
|
+
if (!Number.isFinite(ticks) || ticks < 0) return null;
|
|
7319
|
+
return {
|
|
7320
|
+
startMs: btimeSeconds * 1e3 + ticks / hz * 1e3,
|
|
7321
|
+
zombie: fields[0] === "Z"
|
|
7322
|
+
};
|
|
7323
|
+
}
|
|
7324
|
+
/**
|
|
7325
|
+
* One `ps -o state=,lstart=` row, e.g. `S Wed Oct 7 19:43:26 2026` (macOS, other POSIX).
|
|
7326
|
+
* The leading weekday is stripped before parsing: `Date.parse` is not obliged to accept it
|
|
7327
|
+
* and it says nothing the date does not.
|
|
7328
|
+
*/
|
|
7329
|
+
function parsePsBirth(output) {
|
|
7330
|
+
const line = output.trim().split("\n")[0]?.trim() ?? "";
|
|
7331
|
+
if (line.length === 0) return null;
|
|
7332
|
+
const [state = "", ...rest] = line.split(/\s+/);
|
|
7333
|
+
const startMs = Date.parse(rest.join(" ").replace(/^[A-Z]{3,9}\s+/i, ""));
|
|
7334
|
+
if (!Number.isFinite(startMs)) return null;
|
|
7335
|
+
return {
|
|
7336
|
+
startMs,
|
|
7337
|
+
zombie: state.startsWith("Z")
|
|
7338
|
+
};
|
|
7339
|
+
}
|
|
7340
|
+
/**
|
|
7341
|
+
* FILETIME ticks (100ns since **1601**, UTC) to a Unix epoch in ms — the shape
|
|
7342
|
+
* `DateTime.ToFileTimeUtc()` returns.
|
|
7343
|
+
*
|
|
7344
|
+
* The epoch matters: `DateTime.Ticks` counts from **0001**, not 1601, and the two differ by
|
|
7345
|
+
* 1600 years. Reading the wrong one put every Windows panel ~1600 years in the future, which
|
|
7346
|
+
* `processLiveness` then called `recycled` — a live panel that could never signal-stop itself.
|
|
7347
|
+
* The PowerShell call below therefore asks for `ToFileTimeUtc()` explicitly, and this is exported
|
|
7348
|
+
* so the conversion is pinned by a test.
|
|
7349
|
+
*/
|
|
7350
|
+
function parseFiletime(ticks) {
|
|
7351
|
+
if (!Number.isFinite(ticks) || ticks <= 0) return null;
|
|
7352
|
+
return ticks / 1e4 - FILETIME_UNIX_OFFSET_MS;
|
|
7353
|
+
}
|
|
7354
|
+
/**
|
|
7355
|
+
* When the pid started, from the platform's own process table. `null` when the platform
|
|
7356
|
+
* cannot say — an unreadable `/proc`, no `ps`, or no PowerShell — which the caller must
|
|
7357
|
+
* read as "unknown", never as "gone".
|
|
7358
|
+
*/
|
|
7359
|
+
async function processBirth(pid) {
|
|
7360
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
7361
|
+
if (process.platform === "linux") try {
|
|
7362
|
+
const [stat, system] = await Promise.all([fs.promises.readFile(`/proc/${pid}/stat`, "utf8"), fs.promises.readFile("/proc/stat", "utf8")]);
|
|
7363
|
+
const btime = /^btime (\d+)$/m.exec(system)?.[1];
|
|
7364
|
+
if (btime === void 0) return null;
|
|
7365
|
+
return parseProcBirth(stat, Number.parseInt(btime, 10));
|
|
7366
|
+
} catch {
|
|
7367
|
+
return null;
|
|
7368
|
+
}
|
|
7369
|
+
if (process.platform === "win32") try {
|
|
7370
|
+
const script = `$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToFileTimeUtc() }`;
|
|
7371
|
+
const { stdout } = await execFileAsync$2("powershell", [
|
|
7372
|
+
"-NoProfile",
|
|
7373
|
+
"-NonInteractive",
|
|
7374
|
+
"-Command",
|
|
7375
|
+
script
|
|
7376
|
+
], {
|
|
7377
|
+
timeout: 2e4,
|
|
7378
|
+
windowsHide: true
|
|
7379
|
+
});
|
|
7380
|
+
const startMs = parseFiletime(Number.parseFloat(stdout.trim()));
|
|
7381
|
+
return startMs === null ? null : {
|
|
7382
|
+
startMs,
|
|
7383
|
+
zombie: false
|
|
7384
|
+
};
|
|
7385
|
+
} catch {
|
|
7386
|
+
return null;
|
|
7387
|
+
}
|
|
7388
|
+
try {
|
|
7389
|
+
const { stdout } = await execFileAsync$2("ps", [
|
|
7390
|
+
"-p",
|
|
7391
|
+
String(pid),
|
|
7392
|
+
"-o",
|
|
7393
|
+
"state=,lstart="
|
|
7394
|
+
], { timeout: 3e3 });
|
|
7395
|
+
return parsePsBirth(stdout);
|
|
7396
|
+
} catch {
|
|
7397
|
+
return null;
|
|
7398
|
+
}
|
|
7399
|
+
}
|
|
7400
|
+
/**
|
|
7401
|
+
* A pid is **not** an identity. The OS hands a dead process's pid to an unrelated one later, and a
|
|
7402
|
+
* zombie — exited but not yet reaped by its parent — still answers signal 0. So `isProcessAlive`
|
|
7403
|
+
* alone can report a long-dead daemon as live, which is what let `up` say "already running" and
|
|
7404
|
+
* `status` say "running, but not answering" against a stale `run.json`, and what let `down` signal
|
|
7405
|
+
* whatever stranger had inherited the pid.
|
|
7406
|
+
*
|
|
7407
|
+
* `startedAt` settles it in one direction, with no threshold to guess at: the daemon writes its
|
|
7408
|
+
* record *during its own boot*, so its process is necessarily born **before** that stamp. A pid born
|
|
7409
|
+
* after it is not that process, whoever else it now is. The comparison is deliberately one-sided —
|
|
7410
|
+
* the other direction would have to tell a recycled pid from a wall clock that merely moved, and it
|
|
7411
|
+
* cannot, so a record is never called stale for being *older* than its process.
|
|
7412
|
+
*
|
|
7413
|
+
* `'unknown'` is not `'gone'`: a platform that cannot report a birth time must not have its live pid
|
|
7414
|
+
* dismissed, or a running panel's record would be cleared out from under it.
|
|
7415
|
+
*/
|
|
7416
|
+
async function processLiveness(pid, startedAt, options = {}) {
|
|
7417
|
+
if (!isProcessAlive(pid)) return "gone";
|
|
7418
|
+
const birth = await (options.readBirth ?? processBirth)(pid);
|
|
7419
|
+
if (birth === null) return "unknown";
|
|
7420
|
+
if (birth.zombie) return "gone";
|
|
7421
|
+
return birth.startMs > startedAt + (options.toleranceMs ?? 2e3) ? "recycled" : "live";
|
|
7422
|
+
}
|
|
7304
7423
|
function signalPid$1(pid, name) {
|
|
7305
7424
|
try {
|
|
7306
7425
|
process.kill(pid, name);
|
|
@@ -7384,9 +7503,11 @@ async function listPortHolders(port) {
|
|
|
7384
7503
|
function parsePids(stdout) {
|
|
7385
7504
|
return [...new Set(stdout.split(/\s+/).map((entry) => Number.parseInt(entry, 10)).filter((pid) => Number.isInteger(pid) && pid > 0 && pid !== process.pid))];
|
|
7386
7505
|
}
|
|
7387
|
-
var execFileAsync$2;
|
|
7506
|
+
var execFileAsync$2, LINUX_HZ, FILETIME_UNIX_OFFSET_MS;
|
|
7388
7507
|
var init_port = __esmMin((() => {
|
|
7389
7508
|
execFileAsync$2 = promisify(execFile);
|
|
7509
|
+
LINUX_HZ = 100;
|
|
7510
|
+
FILETIME_UNIX_OFFSET_MS = 0xa9730b66800;
|
|
7390
7511
|
}));
|
|
7391
7512
|
//#endregion
|
|
7392
7513
|
//#region src/providers/nanny.ts
|
|
@@ -11126,12 +11247,16 @@ var init_layout = __esmMin((() => {
|
|
|
11126
11247
|
//#region src/helpers/daemon.ts
|
|
11127
11248
|
var daemon_exports = /* @__PURE__ */ __exportAll({
|
|
11128
11249
|
clearRuntime: () => clearRuntime,
|
|
11250
|
+
isLiveLiveness: () => isLiveLiveness,
|
|
11129
11251
|
isProcessAlive: () => isProcessAlive,
|
|
11130
11252
|
newToken: () => newToken,
|
|
11131
11253
|
probeRuntime: () => probeRuntime,
|
|
11132
11254
|
readRuntime: () => readRuntime,
|
|
11133
11255
|
requestControl: () => requestControl,
|
|
11134
11256
|
requestShutdown: () => requestShutdown,
|
|
11257
|
+
runtimeIsAlive: () => runtimeIsAlive,
|
|
11258
|
+
runtimeIsGone: () => runtimeIsGone,
|
|
11259
|
+
runtimeLiveness: () => runtimeLiveness,
|
|
11135
11260
|
runtimeSchema: () => runtimeSchema,
|
|
11136
11261
|
writeRuntime: () => writeRuntime
|
|
11137
11262
|
});
|
|
@@ -11152,6 +11277,51 @@ function clearRuntime() {
|
|
|
11152
11277
|
function newToken() {
|
|
11153
11278
|
return randomBytes(32).toString("base64url");
|
|
11154
11279
|
}
|
|
11280
|
+
/**
|
|
11281
|
+
* What became of the daemon this record describes: the verdict `run.json`'s pid cannot give on
|
|
11282
|
+
* its own. See `processLiveness` in `providers/port.ts` for why the pid is not an identity.
|
|
11283
|
+
*/
|
|
11284
|
+
async function runtimeLiveness(runtime) {
|
|
11285
|
+
return await processLiveness(runtime.pid, runtime.startedAt);
|
|
11286
|
+
}
|
|
11287
|
+
/**
|
|
11288
|
+
* Is the daemon this record describes still the one running?
|
|
11289
|
+
*
|
|
11290
|
+
* `readRuntime()` answers "does a record exist", never "is the panel behind it alive" — and a pid
|
|
11291
|
+
* alone cannot answer that either. The OS reuses pids, and a zombie still answers signal 0, so a
|
|
11292
|
+
* bare `isProcessAlive(runtime.pid)` reported a stale `run.json` as a live panel: `up` refused with
|
|
11293
|
+
* "already running", `status` said "running, but not answering", and `down` signalled whatever
|
|
11294
|
+
* unrelated process had since inherited the pid.
|
|
11295
|
+
*
|
|
11296
|
+
* Every caller must read this one predicate, because a caller that re-derives it slightly differently
|
|
11297
|
+
* is how a `down` comes to signal a stranger.
|
|
11298
|
+
*/
|
|
11299
|
+
async function runtimeIsAlive(runtime) {
|
|
11300
|
+
return isLiveLiveness(await runtimeLiveness(runtime));
|
|
11301
|
+
}
|
|
11302
|
+
/**
|
|
11303
|
+
* Only `'live'` and `'unknown'` count as alive. `'unknown'` means the platform could not read a start
|
|
11304
|
+
* time, so the live pid is all there is — dismissing it would clear a record the running panel still
|
|
11305
|
+
* owns. `'recycled'` is emphatically **not** alive: the pid belongs to somebody else now.
|
|
11306
|
+
*/
|
|
11307
|
+
function isLiveLiveness(liveness) {
|
|
11308
|
+
return liveness === "live" || liveness === "unknown";
|
|
11309
|
+
}
|
|
11310
|
+
/**
|
|
11311
|
+
* Is the record positively pointing at nothing at all — no process behind the pid?
|
|
11312
|
+
*
|
|
11313
|
+
* This is the **only** condition under which deleting `run.json` is safe, so it is deliberately
|
|
11314
|
+
* narrower than `!runtimeIsAlive`. The start-time check can be wrong in one direction: Linux derives
|
|
11315
|
+
* a process's birth epoch from `btime`, which the kernel computes from the wall clock
|
|
11316
|
+
* (`getboottime64`: "calls to settimeofday will affect the value returned"), so a forward clock step
|
|
11317
|
+
* — common when a laptop or a WSL host wakes from sleep — makes a *live* panel look recycled. Deleting
|
|
11318
|
+
* its record over that would orphan a running panel: still supervising servers, no longer stoppable by
|
|
11319
|
+
* `down`. A record that some process may still own is left alone and simply overwritten when a new
|
|
11320
|
+
* panel finishes booting.
|
|
11321
|
+
*/
|
|
11322
|
+
async function runtimeIsGone(runtime) {
|
|
11323
|
+
return await runtimeLiveness(runtime) === "gone";
|
|
11324
|
+
}
|
|
11155
11325
|
/** A degraded panel is still answering: a 503 must not read as "not running". */
|
|
11156
11326
|
async function probeRuntime(runtime, timeoutMs = 2500) {
|
|
11157
11327
|
const answer = await localCall(runtime, "/healthz", "GET", void 0, timeoutMs);
|
|
@@ -16906,11 +17076,11 @@ function probeHostFor(bindHost) {
|
|
|
16906
17076
|
async function runControlPlane(options) {
|
|
16907
17077
|
ensureLayout();
|
|
16908
17078
|
const existing = readRuntime();
|
|
16909
|
-
if (existing !== null && existing.pid !== process.pid &&
|
|
17079
|
+
if (existing !== null && existing.pid !== process.pid && await runtimeIsAlive(existing)) {
|
|
16910
17080
|
logger.error(`already running (pid ${existing.pid}) at ${existing.url} — run \`home-hosted down\` first`);
|
|
16911
17081
|
process.exit(1);
|
|
16912
17082
|
}
|
|
16913
|
-
if (existing !== null) clearRuntime();
|
|
17083
|
+
if (existing !== null && await runtimeIsGone(existing)) clearRuntime();
|
|
16914
17084
|
const settings = new GlobalSettingsStore();
|
|
16915
17085
|
settings.load();
|
|
16916
17086
|
settings.writeJsonSchema();
|
|
@@ -17265,17 +17435,17 @@ async function runUp(flags, entry) {
|
|
|
17265
17435
|
});
|
|
17266
17436
|
return;
|
|
17267
17437
|
}
|
|
17268
|
-
const { clearRuntime,
|
|
17438
|
+
const { clearRuntime, readRuntime, runtimeIsAlive, runtimeIsGone } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
17269
17439
|
const { daemonLogPath, dataRoot, projectDir } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
17270
17440
|
const existing = readRuntime();
|
|
17271
|
-
if (existing !== null &&
|
|
17441
|
+
if (existing !== null && await runtimeIsAlive(existing)) {
|
|
17272
17442
|
process.stdout.write(`${green("already running")} home-hosted ${existing.version} (pid ${existing.pid}) at ${existing.url}\n`);
|
|
17273
17443
|
const note = versionMismatchNote(existing.version);
|
|
17274
17444
|
if (note !== null) process.stdout.write(`${dim(note)}\n`);
|
|
17275
17445
|
process.stdout.write(`${dim("stop it with `home-hosted down`")}\n`);
|
|
17276
17446
|
return;
|
|
17277
17447
|
}
|
|
17278
|
-
if (existing !== null) clearRuntime();
|
|
17448
|
+
if (existing !== null && await runtimeIsGone(existing)) clearRuntime();
|
|
17279
17449
|
fs.mkdirSync(path.dirname(daemonLogPath), { recursive: true });
|
|
17280
17450
|
rotateLog(daemonLogPath);
|
|
17281
17451
|
const log = fs.openSync(daemonLogPath, "a");
|
|
@@ -17383,13 +17553,14 @@ var down_exports = /* @__PURE__ */ __exportAll({
|
|
|
17383
17553
|
});
|
|
17384
17554
|
/** `down` stops the panel and everything it supervises. */
|
|
17385
17555
|
async function runDown() {
|
|
17386
|
-
const { clearRuntime,
|
|
17556
|
+
const { clearRuntime, isLiveLiveness, readRuntime, requestShutdown, runtimeLiveness } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
17387
17557
|
const runtime = readRuntime();
|
|
17388
17558
|
if (runtime === null) {
|
|
17389
17559
|
process.stdout.write("home-hosted is not running\n");
|
|
17390
17560
|
return;
|
|
17391
17561
|
}
|
|
17392
|
-
|
|
17562
|
+
const liveness = await runtimeLiveness(runtime);
|
|
17563
|
+
if (liveness === "gone") {
|
|
17393
17564
|
clearRuntime();
|
|
17394
17565
|
process.stdout.write(`home-hosted is not running (removed a stale run.json from home-hosted ${runtime.version})\n`);
|
|
17395
17566
|
return;
|
|
@@ -17400,8 +17571,21 @@ async function runDown() {
|
|
|
17400
17571
|
const note = versionMismatchNote(runtime.version);
|
|
17401
17572
|
if (note !== null) process.stdout.write(`${dim(note)}\n`);
|
|
17402
17573
|
};
|
|
17403
|
-
|
|
17404
|
-
if (
|
|
17574
|
+
let answered = false;
|
|
17575
|
+
if (isLiveLiveness(liveness)) {
|
|
17576
|
+
process.stdout.write(`stopping pid ${runtime.pid}…\n`);
|
|
17577
|
+
answered = await requestShutdown(runtime);
|
|
17578
|
+
if (!answered) signal(runtime.pid, "SIGTERM");
|
|
17579
|
+
} else {
|
|
17580
|
+
if (!await requestShutdown(runtime)) {
|
|
17581
|
+
process.stdout.write(`home-hosted is not running (the pid in run.json, ${runtime.pid}, belongs to another process — it was not signalled)\n`);
|
|
17582
|
+
return;
|
|
17583
|
+
}
|
|
17584
|
+
process.stdout.write(`stopped the panel on ${runtime.url} (its pid in run.json, ${runtime.pid}, was another process and was left alone)\n`);
|
|
17585
|
+
clearRuntime();
|
|
17586
|
+
await reportPersistent();
|
|
17587
|
+
return;
|
|
17588
|
+
}
|
|
17405
17589
|
if (await waitForExit(runtime.pid, 2e4)) {
|
|
17406
17590
|
clearRuntime();
|
|
17407
17591
|
reportStopped(false);
|
|
@@ -17490,10 +17674,10 @@ var server_exports = /* @__PURE__ */ __exportAll({
|
|
|
17490
17674
|
stopCommand: () => stopCommand
|
|
17491
17675
|
});
|
|
17492
17676
|
async function runServerAction(action, id, workspace) {
|
|
17493
|
-
const { clearRuntime,
|
|
17677
|
+
const { clearRuntime, readRuntime, requestControl, runtimeIsAlive, runtimeIsGone } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
17494
17678
|
const runtime = readRuntime();
|
|
17495
|
-
if (runtime === null || !
|
|
17496
|
-
if (runtime !== null) clearRuntime();
|
|
17679
|
+
if (runtime === null || !await runtimeIsAlive(runtime)) {
|
|
17680
|
+
if (runtime !== null && await runtimeIsGone(runtime)) clearRuntime();
|
|
17497
17681
|
fail("home-hosted is not running — start it with `home-hosted up`");
|
|
17498
17682
|
}
|
|
17499
17683
|
const query = workspace === void 0 || workspace.length === 0 ? "" : `?workspace=${encodeURIComponent(workspace)}`;
|
|
@@ -17590,7 +17774,7 @@ var status_exports = /* @__PURE__ */ __exportAll({
|
|
|
17590
17774
|
statusCommand: () => statusCommand
|
|
17591
17775
|
});
|
|
17592
17776
|
async function runStatus(json) {
|
|
17593
|
-
const {
|
|
17777
|
+
const { isLiveLiveness, probeRuntime, readRuntime, runtimeLiveness } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
17594
17778
|
const { UiService } = await Promise.resolve().then(() => (init_ui(), ui_exports));
|
|
17595
17779
|
const { hhDir: dataRoot, workspaceDir, DEFAULT_WORKSPACE_ID, workspacesPath } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
17596
17780
|
const runtime = readRuntime();
|
|
@@ -17605,7 +17789,8 @@ async function runStatus(json) {
|
|
|
17605
17789
|
process.exitCode = 1;
|
|
17606
17790
|
return;
|
|
17607
17791
|
}
|
|
17608
|
-
const
|
|
17792
|
+
const liveness = await runtimeLiveness(runtime);
|
|
17793
|
+
const running = isLiveLiveness(liveness);
|
|
17609
17794
|
const probe = running ? await probeRuntime(runtime) : {
|
|
17610
17795
|
reachable: false,
|
|
17611
17796
|
degraded: false
|
|
@@ -17617,6 +17802,7 @@ async function runStatus(json) {
|
|
|
17617
17802
|
const { token: _token, ...safe } = runtime;
|
|
17618
17803
|
process.stdout.write(`${JSON.stringify({
|
|
17619
17804
|
running,
|
|
17805
|
+
liveness,
|
|
17620
17806
|
answering: probe.reachable,
|
|
17621
17807
|
degraded: probe.degraded,
|
|
17622
17808
|
...safe,
|
|
@@ -17632,7 +17818,7 @@ async function runStatus(json) {
|
|
|
17632
17818
|
}
|
|
17633
17819
|
const uptime = formatDuration(Date.now() - runtime.startedAt);
|
|
17634
17820
|
const rows = [
|
|
17635
|
-
["status", !running ? paint("31", "stale (the process is gone)") : probe.degraded ? paint("33", "running — a server needs attention") : probe.reachable ? green("running") : paint("33", "running, but not answering")],
|
|
17821
|
+
["status", !running ? liveness === "recycled" ? paint("31", "stale (the pid belongs to another process now)") : paint("31", "stale (the process is gone)") : probe.degraded ? paint("33", "running — a server needs attention") : probe.reachable ? green("running") : paint("33", "running, but not answering")],
|
|
17636
17822
|
["pid", running ? `${runtime.pid} · up ${uptime}` : String(runtime.pid)],
|
|
17637
17823
|
["url", `${runtime.url} ${dim(`(${runtime.protocol})`)}`],
|
|
17638
17824
|
["version", runtime.version],
|