@runuai/host 0.9.141 → 0.9.142

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.141",
3
+ "version": "0.9.142",
4
4
  "description": "Uai host — runs ephemeral AI tasks in containers on a machine you control.",
5
5
  "license": "MIT",
6
6
  "author": "Uai Tech <team@runuai.com>",
package/src/main.ts CHANGED
@@ -813,8 +813,13 @@ void connect();
813
813
  // Local browser UI (ADR-028) — same single process, alongside the WSS client.
814
814
  // Best-effort: a UI bind failure must not take the host service down.
815
815
  // A task machine has no operator sitting at it (ADR-133): no local UI, no
816
- // Mac vnode watch.
817
- if (!sidecarMode() && !signInBoxMode()) void startLocalUi();
816
+ // Mac vnode watch. The sign-in box (ADR-136) has no operator either, but it
817
+ // keeps a monitor-only status listener: `uai-host status` reads it, and so
818
+ // does the managed installer's post-start check — without it the box read
819
+ // as "service not running" and the first managed install rolled itself back
820
+ // (live 2026-09-14), leaving the box on an npm install that no auto-update
821
+ // reaches.
822
+ if (!sidecarMode()) void startLocalUi();
818
823
  // ADR-118 follow-up: vnode-pressure observability (macOS/apple-container
819
824
  // hosts). Logs only on band transitions; the sensor caches its sysctl read.
820
825
  if (!sidecarMode() && !signInBoxMode()) {
@@ -834,6 +839,7 @@ async function startLocalUi(): Promise<void> {
834
839
  cloudUrl: bridgeUrl,
835
840
  hostId,
836
841
  logPath: serviceLogPath(),
842
+ monitorOnly: signInBoxMode(),
837
843
  taskMemory: async (composeProject) =>
838
844
  containerRuntimeProblem()
839
845
  ? null
@@ -865,7 +871,11 @@ async function startLocalUi(): Promise<void> {
865
871
  }
866
872
  },
867
873
  });
868
- console.log(`[host-agent] local UI on http://127.0.0.1:${handle.port}`);
874
+ console.log(
875
+ signInBoxMode()
876
+ ? `[host-agent] local status endpoint on http://127.0.0.1:${handle.port} (monitor only)`
877
+ : `[host-agent] local UI on http://127.0.0.1:${handle.port}`,
878
+ );
869
879
  } catch (err) {
870
880
  console.warn(
871
881
  `[host-agent] local UI failed to start: ${err instanceof Error ? err.message : String(err)}`,
package/src/ui/server.ts CHANGED
@@ -69,6 +69,15 @@ export interface UiServerOptions {
69
69
  uiDir: string;
70
70
  /** File to persist the chosen port to (`$UAI_DATA_DIR/host.port`). */
71
71
  portFilePath: string;
72
+ /**
73
+ * ADR-136 sign-in box: answer only the read-only status trio
74
+ * (`/api/status`, `/api/cloud`, `/api/tasks`) so `uai-host status` — and
75
+ * the managed installer's post-start check, which parses it — can see the
76
+ * box come up and connect. Every write and every other read (engines,
77
+ * users, events, the static UI) is 404: the box holds no credential at
78
+ * rest and runs no tasks, and nobody sits at it.
79
+ */
80
+ monitorOnly?: boolean;
72
81
  /** First port to try (5876 or UAI_HOST_UI_PORT). 0 lets the OS pick (tests). */
73
82
  startPort: number;
74
83
  // Facts for /api/status:
@@ -110,6 +119,13 @@ export interface UiServerHandle {
110
119
  close: () => Promise<void>;
111
120
  }
112
121
 
122
+ /** The GET routes a `monitorOnly` server still answers (see UiServerOptions). */
123
+ const MONITOR_ONLY_ROUTES: ReadonlySet<string> = new Set([
124
+ "/api/status",
125
+ "/api/cloud",
126
+ "/api/tasks",
127
+ ]);
128
+
113
129
  /** Start the local UI server, binding to 127.0.0.1 with port fallback. */
114
130
  export async function startUiServer(
115
131
  opts: UiServerOptions,
@@ -170,6 +186,9 @@ async function handle(
170
186
  const path = (req.url ?? "/").split("?")[0] ?? "/";
171
187
  const method = req.method ?? "GET";
172
188
  try {
189
+ if (opts.monitorOnly && !MONITOR_ONLY_ROUTES.has(path)) {
190
+ return sendError(res, 404, `no such endpoint: ${path}`);
191
+ }
173
192
  // POST — engine connect/disconnect (the only writes; localhost-bound, so
174
193
  // the loopback binding is the perimeter, ADR-028).
175
194
  if (method === "POST") {