@versot/vaguspi 0.1.3 → 0.1.4

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/dist/bin.js CHANGED
@@ -8,7 +8,8 @@ import { parseArgs } from "node:util";
8
8
  import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { homedir as homedir2 } from "node:os";
11
- import { join as join8, normalize, resolve as resolve3, sep as sep2 } from "node:path";
11
+ import { join as join8, normalize, resolve as resolve3, sep as sep3 } from "node:path";
12
+ import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
12
13
 
13
14
  // packages/host/config/dist/config-store.js
14
15
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -301,8 +302,9 @@ var VagModelsStore = class {
301
302
  // packages/host/engine/dist/vagus-engine.js
302
303
  import { createAgentSession, DefaultResourceLoader, ModelRuntime, SessionManager as SessionManager2, SettingsManager } from "@earendil-works/pi-coding-agent";
303
304
  import { homedir } from "node:os";
304
- import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, resolve } from "node:path";
305
- import { appendFileSync as appendFileSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync, rmSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
305
+ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, resolve, sep } from "node:path";
306
+ import { randomUUID as randomUUID2 } from "node:crypto";
307
+ import { appendFileSync as appendFileSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
306
308
 
307
309
  // packages/host/engine/dist/display-utils.js
308
310
  import * as Diff from "diff";
@@ -1696,9 +1698,17 @@ var VagusEngine = class {
1696
1698
  closeSessionForCwd(cwd) {
1697
1699
  for (const [sid, session] of this.sessions) {
1698
1700
  if (session.sessionManager?.getCwd() === cwd) {
1699
- session.dispose();
1701
+ process.stderr.write(`vagus: disposing session ${sid} (cwd=${cwd})
1702
+ `);
1703
+ try {
1704
+ session.dispose();
1705
+ } catch (err) {
1706
+ process.stderr.write(`vagus: dispose threw: ${err instanceof Error ? err.stack : String(err)}
1707
+ `);
1708
+ }
1700
1709
  this.sessions.delete(sid);
1701
- void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid });
1710
+ void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid }).catch(() => {
1711
+ });
1702
1712
  }
1703
1713
  }
1704
1714
  }
@@ -1760,6 +1770,37 @@ var VagusEngine = class {
1760
1770
  }
1761
1771
  }
1762
1772
  /** Restores an archived project: moves its session files back under `sessions/`. */
1773
+ /** Restores an archived project by its encoded dir name (unique identity). */
1774
+ async unarchiveProjectByDir(dirKey) {
1775
+ if (!dirKey || dirKey.includes("/") || dirKey.includes("\\") || dirKey.includes(".."))
1776
+ return;
1777
+ const agentDir = this.agentDirPath();
1778
+ const src = join5(agentDir, "archived", dirKey);
1779
+ if (!existsSync3(src))
1780
+ return;
1781
+ let cwd;
1782
+ try {
1783
+ cwd = this.sessionCwd(readdirSync2(src).filter((f) => f.endsWith(".jsonl"))[0] ? join5(src, readdirSync2(src).filter((f) => f.endsWith(".jsonl"))[0]) : "");
1784
+ } catch {
1785
+ cwd = void 0;
1786
+ }
1787
+ if (cwd) {
1788
+ this.closeSessionForCwd(cwd);
1789
+ const dst = join5(agentDir, "sessions", this.encodeCwd(cwd));
1790
+ mkdirSync4(dst, { recursive: true });
1791
+ for (const entry of readdirSync2(src)) {
1792
+ if (!entry.endsWith(".jsonl"))
1793
+ continue;
1794
+ const from = join5(src, entry);
1795
+ try {
1796
+ if (statSync(from).isFile())
1797
+ renameSync(from, join5(dst, entry));
1798
+ } catch {
1799
+ }
1800
+ }
1801
+ this.rmDirSafe(src);
1802
+ }
1803
+ }
1763
1804
  async restoreProject(cwd) {
1764
1805
  const agentDir = this.agentDirPath();
1765
1806
  const src = join5(agentDir, "archived", this.encodeCwd(cwd));
@@ -1778,16 +1819,99 @@ var VagusEngine = class {
1778
1819
  } catch {
1779
1820
  }
1780
1821
  }
1781
- rmSync(src, { recursive: true, force: true });
1822
+ this.rmDirSafe(src);
1823
+ }
1824
+ /** Permanently deletes an archived project dir by its encoded dir name.
1825
+ * dirKey (not cwd) is the identity — cwds can repeat across archive dirs. */
1826
+ async deleteArchivedProjectByDir(dirKey) {
1827
+ if (!dirKey || dirKey.includes("/") || dirKey.includes("\\") || dirKey.includes(".."))
1828
+ return;
1829
+ const agentDir = this.agentDirPath();
1830
+ const dir = join5(agentDir, "archived", dirKey);
1831
+ process.stderr.write(`vagus: deleteArchivedProjectByDir dirKey=${dirKey}
1832
+ `);
1833
+ const prefix = dir + sep;
1834
+ for (const [sid, session] of this.sessions) {
1835
+ const file = session.sessionManager?.getSessionFile();
1836
+ if (file && (file === dir || file.startsWith(prefix))) {
1837
+ try {
1838
+ session.dispose();
1839
+ } catch {
1840
+ }
1841
+ this.sessions.delete(sid);
1842
+ void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid }).catch(() => {
1843
+ });
1844
+ }
1845
+ }
1846
+ process.stderr.write(`vagus: sessions closed, removing dir...
1847
+ `);
1848
+ this.rmDirSafe(dir);
1849
+ process.stderr.write(`vagus: archived dir removed OK
1850
+ `);
1782
1851
  }
1783
1852
  /** Permanently deletes an archived project's session dir (JSONL). */
1784
1853
  async deleteArchivedProject(cwd) {
1785
1854
  const agentDir = this.agentDirPath();
1786
1855
  const dir = join5(agentDir, "archived", this.encodeCwd(cwd));
1856
+ process.stderr.write(`vagus: deleteArchivedProject cwd=${cwd} dir=${dir}
1857
+ `);
1787
1858
  this.closeSessionForCwd(cwd);
1788
- rmSync(dir, { recursive: true, force: true });
1859
+ process.stderr.write(`vagus: sessions closed, removing dir...
1860
+ `);
1861
+ this.rmDirSafe(dir);
1862
+ process.stderr.write(`vagus: archived dir removed OK
1863
+ `);
1864
+ }
1865
+ /**
1866
+ * rmSync(recursive) has crashed the daemon natively (0xC0000409 fastfail)
1867
+ * on Windows when deleting archived dirs with non-ASCII names — JS guards
1868
+ * cannot catch a native abort. Delete file-by-file instead: plain unlink
1869
+ * calls are individually catchable and skip whatever is locked/unreadable.
1870
+ *
1871
+ * Iterative (explicit stack, deepest-first) — no JS recursion limit, so
1872
+ * arbitrarily deep trees are safe.
1873
+ */
1874
+ rmDirSafe(root) {
1875
+ const pendingDirs = [{ path: root, expanded: false }];
1876
+ while (pendingDirs.length > 0) {
1877
+ const top = pendingDirs[pendingDirs.length - 1];
1878
+ if (top.expanded) {
1879
+ pendingDirs.pop();
1880
+ try {
1881
+ rmdirSync(top.path);
1882
+ } catch (err) {
1883
+ process.stderr.write(`vagus: rmDirSafe rmdir failed ${top.path}: ${err instanceof Error ? err.message : String(err)}
1884
+ `);
1885
+ }
1886
+ continue;
1887
+ }
1888
+ let entries;
1889
+ try {
1890
+ entries = readdirSync2(top.path);
1891
+ } catch {
1892
+ pendingDirs.pop();
1893
+ continue;
1894
+ }
1895
+ top.expanded = true;
1896
+ for (const entry of entries) {
1897
+ const full = join5(top.path, entry);
1898
+ try {
1899
+ if (statSync(full).isDirectory()) {
1900
+ pendingDirs.push({ path: full, expanded: false });
1901
+ } else {
1902
+ unlinkSync(full);
1903
+ }
1904
+ } catch (err) {
1905
+ process.stderr.write(`vagus: rmDirSafe skipped ${full}: ${err instanceof Error ? err.message : String(err)}
1906
+ `);
1907
+ }
1908
+ }
1909
+ }
1789
1910
  }
1790
1911
  /** Lists archived projects with their sessions (scans the `archived/` dir). */
1912
+ /** Lists archived projects with their sessions (scans the `archived/` dir).
1913
+ * dirKey is the encoded directory name — the unique identity of an archived
1914
+ * group (cwd from session headers can repeat across dirs). */
1791
1915
  async listArchivedProjects() {
1792
1916
  const agentDir = this.agentDirPath();
1793
1917
  const root = join5(agentDir, "archived");
@@ -1805,6 +1929,7 @@ var VagusEngine = class {
1805
1929
  continue;
1806
1930
  out.push({
1807
1931
  cwd,
1932
+ dirKey: entry,
1808
1933
  sessions: infos.map((i) => ({
1809
1934
  id: i.id,
1810
1935
  path: i.path,
@@ -1887,6 +2012,54 @@ var VagusEngine = class {
1887
2012
  const result = await session.navigateTree(targetId, options);
1888
2013
  return { editorText: result.editorText, cancelled: result.cancelled };
1889
2014
  }
2015
+ /**
2016
+ * Creates a new session forked from a specific user message.
2017
+ * The new session contains only the conversation up to and including that
2018
+ * user message — everything after it is dropped. The original session is
2019
+ * untouched. Returns the new session's info (sessionId, sessionFile, cwd).
2020
+ */
2021
+ async forkSession(sessionId, entryId) {
2022
+ const session = this.requireSession(sessionId);
2023
+ const sourceFile = session.sessionManager?.getSessionFile();
2024
+ if (!sourceFile)
2025
+ throw new Error("source session has no file path");
2026
+ if (!existsSync3(sourceFile))
2027
+ throw new Error(`source session file not found: ${sourceFile}`);
2028
+ const raw = readFileSync4(sourceFile, "utf8");
2029
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
2030
+ const entries = lines.map((l) => JSON.parse(l));
2031
+ const header = entries[0];
2032
+ if (!header || header.type !== "session")
2033
+ throw new Error("source session has no header");
2034
+ const targetIdx = entries.findIndex((e) => e.id === entryId);
2035
+ if (targetIdx < 0)
2036
+ throw new Error(`entry ${entryId} not found in source session`);
2037
+ let endIdx = targetIdx - 1;
2038
+ if (endIdx < 1)
2039
+ endIdx = 1;
2040
+ const cwd = session.sessionManager.getCwd() || this.options.cwd;
2041
+ const sessionDir = dirname3(sourceFile);
2042
+ mkdirSync4(sessionDir, { recursive: true });
2043
+ const newSessionId = randomUUID2();
2044
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2045
+ const fileTimestamp = timestamp.replace(/[:.]/g, "-");
2046
+ const newSessionFile = join5(sessionDir, `${fileTimestamp}_${newSessionId}.jsonl`);
2047
+ const newHeader = {
2048
+ type: "session",
2049
+ version: header.version ?? 1,
2050
+ id: newSessionId,
2051
+ timestamp,
2052
+ cwd,
2053
+ parentSession: sourceFile
2054
+ };
2055
+ const outLines = [JSON.stringify(newHeader)];
2056
+ for (let i = 1; i <= endIdx; i++) {
2057
+ outLines.push(JSON.stringify(entries[i]));
2058
+ }
2059
+ writeFileSync3(newSessionFile, outLines.join("\n") + "\n", "utf8");
2060
+ const manager = SessionManager2.open(newSessionFile);
2061
+ return this.startSession({ cwd, sessionManager: manager });
2062
+ }
1890
2063
  /**
1891
2064
  * Exports the active branch to JSONL (/export). Returns the output path.
1892
2065
  */
@@ -6562,7 +6735,7 @@ import { WebSocketServer as WsServer } from "ws";
6562
6735
 
6563
6736
  // packages/host/rpc/dist/static-server.js
6564
6737
  import { createReadStream, existsSync as existsSync4, statSync as statSync2 } from "node:fs";
6565
- import { extname, join as join6, resolve as resolve2, sep } from "node:path";
6738
+ import { extname, join as join6, resolve as resolve2, sep as sep2 } from "node:path";
6566
6739
  var MIME_TYPES = {
6567
6740
  ".html": "text/html; charset=utf-8",
6568
6741
  ".js": "text/javascript; charset=utf-8",
@@ -6581,7 +6754,7 @@ function resolveStaticFile(rootDir, pathname) {
6581
6754
  const root = resolve2(rootDir);
6582
6755
  const name = pathname === "/" ? "index.html" : pathname;
6583
6756
  const candidate = resolve2(join6(root, name));
6584
- if (candidate !== root && !candidate.startsWith(root + sep)) {
6757
+ if (candidate !== root && !candidate.startsWith(root + sep2)) {
6585
6758
  return void 0;
6586
6759
  }
6587
6760
  if (existsSync4(candidate)) {
@@ -6906,6 +7079,28 @@ function requireString(value, label) {
6906
7079
  }
6907
7080
  return value;
6908
7081
  }
7082
+ function applyHttpProxy() {
7083
+ try {
7084
+ let proxy;
7085
+ const settingsFile = join8(piAgentDir(), "settings.json");
7086
+ if (existsSync6(settingsFile)) {
7087
+ const s = JSON.parse(readFileSync6(settingsFile, "utf8"));
7088
+ if (typeof s.httpProxy === "string" && s.httpProxy.trim()) proxy = s.httpProxy.trim();
7089
+ }
7090
+ proxy ??= process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY;
7091
+ if (proxy) {
7092
+ const normalized = /^https?:\/\//.test(proxy) ? proxy : `http://${proxy}`;
7093
+ process.env.HTTPS_PROXY = normalized;
7094
+ process.env.HTTP_PROXY = normalized;
7095
+ setGlobalDispatcher(new EnvHttpProxyAgent());
7096
+ process.stderr.write(`vagus: http proxy enabled \u2192 ${normalized}
7097
+ `);
7098
+ }
7099
+ } catch (err) {
7100
+ process.stderr.write(`vagus: proxy setup skipped: ${err instanceof Error ? err.message : String(err)}
7101
+ `);
7102
+ }
7103
+ }
6909
7104
  function probeRequestFor(apiType, url, apiKey, modelId) {
6910
7105
  const headers = { "Content-Type": "application/json" };
6911
7106
  const base = url.replace(/\/chat\/completions$/, "");
@@ -6939,6 +7134,7 @@ function builtinExtensionPaths() {
6939
7134
  return out;
6940
7135
  }
6941
7136
  async function runDaemon() {
7137
+ applyHttpProxy();
6942
7138
  const stateDir = defaultStateDir();
6943
7139
  mkdirSync6(stateDir, { recursive: true });
6944
7140
  const config = new ConfigStore({ dir: stateDir });
@@ -6997,19 +7193,21 @@ async function runDaemon() {
6997
7193
  return host.archiveProject(cwd);
6998
7194
  });
6999
7195
  srv.registerMethod("project.unarchive", (params) => {
7000
- const cwd = requireString(params?.cwd, "cwd");
7001
- return host.restoreProject(cwd);
7196
+ const { cwd, dirKey } = params ?? {};
7197
+ if (typeof dirKey === "string" && dirKey) return host.unarchiveProjectByDir(dirKey);
7198
+ return host.restoreProject(requireString(cwd, "cwd"));
7002
7199
  });
7003
7200
  srv.registerMethod("project.archived", () => host.listArchivedProjects());
7004
7201
  srv.registerMethod("project.delete", (params) => {
7005
- const cwd = requireString(params?.cwd, "cwd");
7006
- return host.deleteArchivedProject(cwd);
7202
+ const { cwd, dirKey } = params ?? {};
7203
+ if (typeof dirKey === "string" && dirKey) return host.deleteArchivedProjectByDir(dirKey);
7204
+ return host.deleteArchivedProject(requireString(cwd, "cwd"));
7007
7205
  });
7008
7206
  srv.registerMethod("project.roots", () => {
7009
7207
  const home = homedir2();
7010
7208
  const places = [
7011
7209
  { name: "Home", path: home, isDirectory: true },
7012
- { name: "Root", path: sep2, isDirectory: true }
7210
+ { name: "Root", path: sep3, isDirectory: true }
7013
7211
  ];
7014
7212
  const candidates = ["Desktop", "Downloads", "Documents", "Pictures", "Music", "Videos"];
7015
7213
  for (const name of candidates) {
@@ -7382,6 +7580,10 @@ async function runDaemon() {
7382
7580
  const { sessionId } = params ?? {};
7383
7581
  return host.listForkPoints(requireString(sessionId, "sessionId"));
7384
7582
  });
7583
+ srv.registerMethod("session.fork", async (params) => {
7584
+ const { sessionId, entryId } = params ?? {};
7585
+ return host.forkSession(requireString(sessionId, "sessionId"), requireString(entryId, "entryId"));
7586
+ });
7385
7587
  srv.registerMethod("session.tree", (params) => {
7386
7588
  const { sessionId } = params ?? {};
7387
7589
  return host.getSessionTree(requireString(sessionId, "sessionId"));
@@ -7484,9 +7686,12 @@ async function runDaemon() {
7484
7686
  wsHost.broadcast(event);
7485
7687
  });
7486
7688
  let shuttingDown = false;
7487
- const shutdown = () => {
7689
+ const shutdown = (signal) => {
7488
7690
  if (shuttingDown) return;
7489
7691
  shuttingDown = true;
7692
+ process.stderr.write(`vagus: daemon shutting down (${signal})
7693
+ ${new Error("shutdown trace").stack}
7694
+ `);
7490
7695
  void (async () => {
7491
7696
  try {
7492
7697
  wsHost.close();
@@ -7497,11 +7702,27 @@ async function runDaemon() {
7497
7702
  }
7498
7703
  })();
7499
7704
  };
7500
- process.on("SIGINT", shutdown);
7501
- process.on("SIGTERM", shutdown);
7705
+ process.on("SIGINT", () => shutdown("SIGINT"));
7706
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
7707
+ process.on("beforeExit", (code) => {
7708
+ process.stderr.write(`vagus: daemon beforeExit (event loop empty), code=${code}
7709
+ `);
7710
+ });
7711
+ process.on("exit", (code) => {
7712
+ process.stderr.write(`vagus: daemon exit, code=${code}
7713
+ `);
7714
+ });
7502
7715
  transport.start();
7503
7716
  process.stderr.write(`pi-web daemon ready (state: ${stateDir})
7504
7717
  `);
7718
+ process.on("uncaughtException", (err) => {
7719
+ process.stderr.write(`vagus: uncaught exception: ${err?.stack ?? String(err)}
7720
+ `);
7721
+ });
7722
+ process.on("unhandledRejection", (reason) => {
7723
+ process.stderr.write(`vagus: unhandled rejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}
7724
+ `);
7725
+ });
7505
7726
  return new Promise(() => {
7506
7727
  });
7507
7728
  }
@@ -7514,8 +7735,38 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
7514
7735
 
7515
7736
  // apps/cli/src/daemon.ts
7516
7737
  import { spawn } from "node:child_process";
7738
+ import { execSync } from "node:child_process";
7517
7739
  import { existsSync as existsSync7 } from "node:fs";
7518
7740
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7741
+ function detectSystemProxy() {
7742
+ try {
7743
+ if (process.platform === "win32") {
7744
+ const key = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
7745
+ const out = execSync(`reg query "${key}" /v ProxyEnable`, { encoding: "utf8", timeout: 2e3 });
7746
+ if (!/0x1/.test(out)) return void 0;
7747
+ const srv = execSync(`reg query "${key}" /v ProxyServer`, { encoding: "utf8", timeout: 2e3 });
7748
+ const m = srv.match(/ProxyServer\s+REG_SZ\s+(\S+)/);
7749
+ if (!m?.[1]) return void 0;
7750
+ let addr;
7751
+ if (m[1].includes("=")) {
7752
+ const https = m[1].split(";").find((p) => p.startsWith("https="));
7753
+ addr = (https ?? m[1].split(";")[0])?.split("=")[1] || void 0;
7754
+ } else {
7755
+ addr = m[1];
7756
+ }
7757
+ return addr && /^https?:\/\//.test(addr) ? addr : addr ? `http://${addr}` : void 0;
7758
+ }
7759
+ if (process.platform === "darwin") {
7760
+ const out = execSync("scutil --proxy", { encoding: "utf8", timeout: 2e3 });
7761
+ const enabled = /HTTPSEnable\s*:\s*1/.test(out);
7762
+ const host = out.match(/HTTPSProxy\s*:\s*(\S+)/)?.[1];
7763
+ const port = out.match(/HTTPSPort\s*:\s*(\d+)/)?.[1];
7764
+ return enabled && host && port ? `http://${host}:${port}` : void 0;
7765
+ }
7766
+ } catch {
7767
+ }
7768
+ return void 0;
7769
+ }
7519
7770
  function daemonEntryPath() {
7520
7771
  const base = fileURLToPath2(new URL("./bin", import.meta.url));
7521
7772
  const source = `${base}.ts`;
@@ -7524,9 +7775,27 @@ function daemonEntryPath() {
7524
7775
  function spawnDaemon(options = {}) {
7525
7776
  const entry = daemonEntryPath();
7526
7777
  const args = entry.endsWith(".ts") ? ["--import", "tsx", entry, "daemon"] : [entry, "daemon"];
7778
+ const injected = {};
7779
+ injected.NODE_USE_ENV_PROXY = "1";
7780
+ if (!process.env.HTTPS_PROXY && !process.env.HTTP_PROXY) {
7781
+ const sysProxy = detectSystemProxy();
7782
+ if (sysProxy) {
7783
+ injected.HTTPS_PROXY = sysProxy;
7784
+ injected.HTTP_PROXY = sysProxy;
7785
+ injected.NO_PROXY = process.env.NO_PROXY ?? "localhost,127.0.0.1";
7786
+ process.stderr.write(`vagus: following system proxy \u2192 ${sysProxy}
7787
+ `);
7788
+ } else {
7789
+ process.stderr.write(`vagus: no system proxy detected (direct connections)
7790
+ `);
7791
+ }
7792
+ } else if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY) {
7793
+ process.stderr.write(`vagus: proxy env already set (HTTPS_PROXY=${process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY})
7794
+ `);
7795
+ }
7527
7796
  return spawn(process.execPath, args, {
7528
7797
  stdio: options.stdio ?? ["pipe", "pipe", "pipe"],
7529
- env: { ...process.env, ...options.env }
7798
+ env: { ...process.env, ...injected, ...options.env }
7530
7799
  });
7531
7800
  }
7532
7801
 
@@ -7579,6 +7848,9 @@ async function runWeb() {
7579
7848
  child.on("exit", (code, signal) => {
7580
7849
  if (!opened && code !== 0) {
7581
7850
  process.stderr.write(`pi-web: daemon exited (code ${code ?? "?"}, signal ${signal ?? "none"}) before the UI was ready.
7851
+ `);
7852
+ } else {
7853
+ process.stderr.write(`pi-web: daemon exited (code ${code ?? "?"}, signal ${signal ?? "none"}).
7582
7854
  `);
7583
7855
  }
7584
7856
  process.exit(code ?? 0);