home-hosted 0.7.20 → 0.7.21

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 CHANGED
@@ -191,7 +191,9 @@ nobody reads is worse than a long file.
191
191
  commit*. A stale doc is a bug like stale code.
192
192
  - **UIs move together.** `uis/stock` is not the only client: a change to it — or to a shared contract
193
193
  it reads — lands in every other UI under `uis/`, and each altered UI bumps its `ui.json` (see
194
- [`.agentDocs/ARCHITECTURE.md`](.agentDocs/ARCHITECTURE.md)).
194
+ [`.agentDocs/ARCHITECTURE.md`](.agentDocs/ARCHITECTURE.md)). `src/shared/**` is a UI build input, so
195
+ a contract a UI *calls* at runtime ships in its bundle and needs the bump — the pre-commit hook only
196
+ inspects `uis/` paths and stays silent on that commit.
195
197
 
196
198
  ## Releasing
197
199
 
package/dist/cli.js CHANGED
@@ -1077,7 +1077,14 @@ maxRssBytes: "number.integer >= 0 = 0" }).onUndeclaredKey("reject");
1077
1077
  logBufferLines: logBufferLinesSchema.optional()
1078
1078
  }).onUndeclaredKey("reject");
1079
1079
  editableFields = {
1080
- label: "string?",
1080
+ /**
1081
+ * `null` clears it, the same spelling `serverSchema` accepts and every display's `label ?? id`
1082
+ * fallback needs — an empty string satisfies neither. `serverPatchSchema` is what the editor
1083
+ * validates its payload against and what `PATCH /api/servers/:id` checks, so without this a
1084
+ * cleared Label was refused with "label must be a string (was null)" and could not be saved at all
1085
+ * (`1f58a5d` fixed `applyPatch` and the *control* patch schema, and cited that one in its message).
1086
+ */
1087
+ label: labelOrNullSchema.optional(),
1081
1088
  enabled: "boolean?",
1082
1089
  autostart: "boolean?",
1083
1090
  persistent: "boolean?",
@@ -8336,6 +8343,7 @@ var init_proxy$1 = __esmMin((() => {
8336
8343
  init_types();
8337
8344
  init_nanny$2();
8338
8345
  init_port();
8346
+ init_proc();
8339
8347
  init_proxy$2();
8340
8348
  init_proxy_config();
8341
8349
  init_tls$1();
@@ -9731,7 +9739,9 @@ var init_proxy$1 = __esmMin((() => {
9731
9739
  const statePath = nannyStatePath(this.options.stateDir, PROXY_ID);
9732
9740
  const state = readNannyState(statePath);
9733
9741
  if (state !== null && state.serverId === PROXY_ID) {
9734
- const pids = [state.nannyPid, state.childPid].filter((pid) => pid !== null && pid > 0);
9742
+ const nanny = await nannyIsAlive(state, PROXY_ID);
9743
+ const child = state.childPid !== null && state.childPid > 0 && await processCarriesServerId(state.childPid, PROXY_ID);
9744
+ const pids = [...nanny ? [state.nannyPid] : [], ...nanny || child ? [state.childPid] : []].filter((pid) => pid !== null && pid > 0);
9735
9745
  for (const pid of pids) {
9736
9746
  if (!isProcessAlive(pid)) continue;
9737
9747
  try {
@@ -9742,10 +9752,12 @@ var init_proxy$1 = __esmMin((() => {
9742
9752
  }
9743
9753
  const deadline = Date.now() + (this.options.stopGraceMs ?? 1e4);
9744
9754
  while (Date.now() < deadline && pids.some((pid) => isProcessAlive(pid))) await new Promise((resolve) => setTimeout(resolve, 100));
9745
- for (const pid of pids.filter(isProcessAlive)) try {
9755
+ const survivors = pids.filter(isProcessAlive);
9756
+ for (const pid of survivors) try {
9746
9757
  process.kill(pid, "SIGKILL");
9747
9758
  } catch {}
9748
- fs.rmSync(statePath, { force: true });
9759
+ if (survivors.some(isProcessAlive)) logger.warn(`proxy: pid ${survivors.join(", ")} ignored SIGKILL — keeping ${path.basename(statePath)} so a later boot can still stop it`);
9760
+ else fs.rmSync(statePath, { force: true });
9749
9761
  }
9750
9762
  this.nanny = null;
9751
9763
  this.admin = null;
@@ -11962,28 +11974,24 @@ function signalPid(pid, signal, killGroup) {
11962
11974
  process.kill(pid, signal);
11963
11975
  } catch {}
11964
11976
  }
11965
- function alive(pid) {
11966
- try {
11967
- process.kill(pid, 0);
11968
- return true;
11969
- } catch {
11970
- return false;
11971
- }
11972
- }
11973
11977
  /**
11974
11978
  * The same shutdown a supervised child gets, for a process we adopted instead of
11975
11979
  * spawned: a detached successor is not our child, so it is signalled by pid (and
11976
11980
  * by process group when asked) and its exit is polled rather than awaited.
11981
+ *
11982
+ * Liveness comes from `isProcessAlive`, never a local signal-0 copy: a second definition returned
11983
+ * `false` for `EPERM`, so a process that was there but not ours to signal read as "exited" and
11984
+ * `'force-killed'` was returned for one still running. One predicate, one answer.
11977
11985
  */
11978
11986
  async function terminatePid(pid, options) {
11979
- if (!alive(pid)) return "exited";
11987
+ if (!isProcessAlive(pid)) return "exited";
11980
11988
  signalPid(pid, options.signal, options.killGroup);
11981
11989
  const deadline = Date.now() + Math.max(0, options.graceMs);
11982
- while (Date.now() < deadline && alive(pid)) await delay$1(50);
11983
- if (!alive(pid)) return "exited";
11990
+ while (Date.now() < deadline && isProcessAlive(pid)) await delay$1(50);
11991
+ if (!isProcessAlive(pid)) return "exited";
11984
11992
  signalPid(pid, "SIGKILL", options.killGroup);
11985
11993
  const hardDeadline = Date.now() + 2e3;
11986
- while (Date.now() < hardDeadline && alive(pid)) await delay$1(50);
11994
+ while (Date.now() < hardDeadline && isProcessAlive(pid)) await delay$1(50);
11987
11995
  return "force-killed";
11988
11996
  }
11989
11997
  function delay$1(ms) {
@@ -12007,6 +12015,7 @@ function waitForExit$1(child, timeoutMs) {
12007
12015
  var execFileAsync$1, WINDOWS_SHIM_EXTENSIONS;
12008
12016
  var init_process = __esmMin((() => {
12009
12017
  init_paths();
12018
+ init_port();
12010
12019
  execFileAsync$1 = promisify(execFile);
12011
12020
  WINDOWS_SHIM_EXTENSIONS = [
12012
12021
  ".cmd",
@@ -12823,6 +12832,7 @@ var init_supervisor = __esmMin((() => {
12823
12832
  nannyExit: null,
12824
12833
  starting: false,
12825
12834
  stopping: false,
12835
+ removed: false,
12826
12836
  bootstrapDone: !config.bootstrap,
12827
12837
  logs: new LogBuffer(config.logBufferLines),
12828
12838
  echoed: /* @__PURE__ */ new Set(),
@@ -12841,6 +12851,7 @@ var init_supervisor = __esmMin((() => {
12841
12851
  this.relay.unfollow(id);
12842
12852
  this.options.logFiles.clear(id);
12843
12853
  this.options.history.forget(id);
12854
+ entry.removed = true;
12844
12855
  this.stopEntry(entry).catch((error) => {
12845
12856
  logger.error(`could not stop the removed server ${id}`, error);
12846
12857
  });
@@ -13026,7 +13037,7 @@ var init_supervisor = __esmMin((() => {
13026
13037
  entry.responseMs = null;
13027
13038
  entry.exitCode = null;
13028
13039
  entry.exitSignal = null;
13029
- this.options.history.record(entry.config.id, {
13040
+ if (!entry.removed) this.options.history.record(entry.config.id, {
13030
13041
  type: "exit",
13031
13042
  detail,
13032
13043
  runtimeMs: ranForMs
@@ -13425,7 +13436,7 @@ var init_supervisor = __esmMin((() => {
13425
13436
  at: Date.now()
13426
13437
  });
13427
13438
  const ranForMs = nannyExit?.runtimeMs ?? (entry.startedAt === null ? 0 : Date.now() - entry.startedAt);
13428
- this.options.history.record(entry.config.id, {
13439
+ if (!entry.removed) this.options.history.record(entry.config.id, {
13429
13440
  type: "exit",
13430
13441
  detail,
13431
13442
  runtimeMs: ranForMs
@@ -13616,6 +13627,10 @@ var init_supervisor = __esmMin((() => {
13616
13627
  };
13617
13628
  }
13618
13629
  log(entry, stream, text) {
13630
+ if (entry.removed) {
13631
+ logger.debug(`[${entry.config.id}] ${text}`);
13632
+ return;
13633
+ }
13619
13634
  const line = {
13620
13635
  ts: Date.now(),
13621
13636
  stream,
@@ -15695,6 +15710,7 @@ var init_log_files = __esmMin((() => {
15695
15710
  return lines.slice(-tail);
15696
15711
  }
15697
15712
  clear(serverId) {
15713
+ this.pending.delete(serverId);
15698
15714
  for (const file of this.rotateTargets(serverId)) try {
15699
15715
  fs.rmSync(file, { force: true });
15700
15716
  } catch {}