@runuai/host 0.9.14 → 0.9.43

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.
Files changed (97) hide show
  1. package/README.md +22 -5
  2. package/db/migrations/0014_host_inventory_event_index.sql +1 -0
  3. package/db/migrations/0015_host_settings.sql +9 -0
  4. package/db/migrations/0016_task_environment.sql +2 -0
  5. package/db/migrations/meta/_journal.json +21 -0
  6. package/db/schema.ts +80 -30
  7. package/images/standard/Dockerfile +36 -10
  8. package/images/standard/README.md +63 -18
  9. package/images/standard/container/corepack-version +1 -0
  10. package/images/standard/container/uai-init +308 -38
  11. package/images/standard/container/uai-materialize-runtimes +1527 -0
  12. package/lib/agent-cli.ts +33 -2
  13. package/lib/agent.ts +46 -7
  14. package/lib/agents/claude.ts +13 -8
  15. package/lib/agents/codex.ts +11 -6
  16. package/lib/agents/cursor.ts +39 -29
  17. package/lib/agents/durable-proc.ts +20 -27
  18. package/lib/agents/factory.ts +9 -25
  19. package/lib/agents/grok.ts +43 -30
  20. package/lib/agents/kimi.ts +44 -29
  21. package/lib/agents/opencode.ts +43 -31
  22. package/lib/agents/proc.ts +149 -114
  23. package/lib/agents/transport.ts +62 -50
  24. package/lib/agents/types.ts +6 -4
  25. package/lib/apple-runtime-recycle.ts +236 -0
  26. package/lib/apple-uninstall-teardown.ts +224 -0
  27. package/lib/browser-testing.ts +233 -93
  28. package/lib/codex-auth.ts +40 -6
  29. package/lib/command-db.ts +20 -0
  30. package/lib/container-runtime.ts +1338 -0
  31. package/lib/db.ts +1 -0
  32. package/lib/docker-exec.ts +87 -5
  33. package/lib/engine-accounts.ts +68 -5
  34. package/lib/engine-login.ts +1952 -0
  35. package/lib/enrollment-state.ts +251 -0
  36. package/lib/env-file.ts +155 -0
  37. package/lib/env.ts +4 -0
  38. package/lib/git-diff.ts +98 -32
  39. package/lib/git-identity.ts +199 -87
  40. package/lib/github-tokens.ts +202 -91
  41. package/lib/host-cloud-url.ts +62 -0
  42. package/lib/host-config.ts +279 -0
  43. package/lib/host-logs.ts +962 -0
  44. package/lib/keyed-promise-tail.ts +23 -0
  45. package/lib/legacy-runtime-v1.fixture.ts +627 -0
  46. package/lib/managed-activation-watcher.ts +72 -0
  47. package/lib/managed-install-owner-watcher.ts +55 -0
  48. package/lib/managed-operation-drain.ts +49 -0
  49. package/lib/managed-runtime.ts +3644 -0
  50. package/lib/managed-update-scheduler.ts +125 -0
  51. package/lib/mcp-gateway.ts +450 -23
  52. package/lib/orchestrator.ts +3051 -200
  53. package/lib/preview-sidecar.ts +68 -14
  54. package/lib/release-manifest.ts +708 -0
  55. package/lib/release-trust.ts +28 -0
  56. package/lib/runtime-activation-tail.ts +232 -0
  57. package/lib/runtime-archive.ts +1086 -0
  58. package/lib/runtime-authority.ts +79 -0
  59. package/lib/runtime-guard.ts +36 -0
  60. package/lib/runtime-provider-state.ts +169 -0
  61. package/lib/runtime-state.ts +232 -12
  62. package/lib/skills.ts +24 -3
  63. package/lib/ssh.ts +18 -0
  64. package/lib/standard-image.ts +1104 -141
  65. package/lib/stopped-task-status-queue.ts +44 -0
  66. package/lib/task-container-cli.ts +269 -0
  67. package/lib/task-diff.ts +66 -46
  68. package/lib/task-environment/apple-container.ts +757 -0
  69. package/lib/task-environment/docker.ts +956 -0
  70. package/lib/task-environment/index.ts +364 -0
  71. package/lib/task-environment/legacy-adoption.ts +459 -0
  72. package/lib/task-environment/registry.ts +58 -0
  73. package/lib/task-environment/types.ts +408 -0
  74. package/lib/task-identity.ts +19 -0
  75. package/lib/task-inventory.ts +585 -0
  76. package/lib/tunnel-registry.ts +135 -19
  77. package/lib/tunnel-runtime.ts +235 -0
  78. package/package.json +1 -1
  79. package/scripts/agent/_common.sh +123 -3
  80. package/scripts/agent/task-down.sh +146 -38
  81. package/scripts/agent/task-status.sh +19 -3
  82. package/scripts/agent/task-up.sh +1405 -107
  83. package/scripts/install/darwin.ts +848 -50
  84. package/scripts/install/linux.ts +838 -35
  85. package/scripts/install/types.ts +43 -0
  86. package/scripts/install/util.ts +215 -8
  87. package/scripts/install/win.ts +12 -0
  88. package/src/apple-tunnel-route.ts +104 -0
  89. package/src/cli.ts +1464 -72
  90. package/src/event-outbox.ts +83 -4
  91. package/src/index.ts +766 -42
  92. package/src/main.ts +1398 -255
  93. package/src/paths.ts +17 -1
  94. package/src/protocol.ts +695 -1
  95. package/src/runtime-bootstrap.ts +165 -0
  96. package/src/ui/server.ts +46 -10
  97. package/src/ui/types.ts +37 -0
@@ -9,18 +9,30 @@
9
9
  * (always local — no registry pull) under `node` with a ~5-line `net` TCP proxy;
10
10
  * node streams give backpressure + handle WebSockets (Metro/HMR).
11
11
  *
12
- * Hot path is cache-only (NO docker call per request that thrashed under a
13
- * browser's concurrent requests). The cached port is trusted until a tunnel
14
- * connect fails, which invalidates it (mirrors the container-IP cache). Declared
15
- * ports keep their published-port path; only ad-hoc ports use a sidecar.
12
+ * The cache deduplicates creation, but every tunnel open re-proves the exact
13
+ * sidecar container and its current Docker mapping. A process-lifetime cached
14
+ * localhost port is unsafe across an unnoticed daemon restart: another process
15
+ * can inherit that port before the bridge reconnects and triggers recovery.
16
16
  */
17
17
  import net from "node:net";
18
18
 
19
19
  import { dockerCli } from "./docker-exec";
20
+ import { inspectTunnelContainer } from "./tunnel-runtime";
20
21
 
21
22
  /** Port the proxy listens on inside the sidecar (published to a random host port). */
22
23
  const INNER_PORT = 9000;
23
- const LABEL = "com.runuai.preview-sidecar.task";
24
+ /**
25
+ * Exported for every "which container is the task's APP" scan: sidecars can
26
+ * legitimately wear the task's compose project/service labels (teardown
27
+ * finds them by project), so any container carrying THIS label must be
28
+ * excluded before counting app containers. Live 2026-08-18: one browser
29
+ * sidecar with compose labels made legacy adoption count two app containers
30
+ * and fail the whole runtime selection closed — every task on the host read
31
+ * as "still starting".
32
+ */
33
+ export const PREVIEW_SIDECAR_TASK_LABEL = "com.runuai.preview-sidecar.task";
34
+ const LABEL = PREVIEW_SIDECAR_TASK_LABEL;
35
+ const TARGET_LABEL = "com.runuai.preview-sidecar.target";
24
36
 
25
37
  /** (taskId,name,containerPort) -> published 127.0.0.1 host port of a live sidecar. */
26
38
  const cache = new Map<string, number>();
@@ -135,13 +147,18 @@ async function imageOf(appContainer: string): Promise<string | null> {
135
147
  return r.status === 0 && r.stdout.trim() ? r.stdout.trim() : null;
136
148
  }
137
149
 
138
- /** `docker port <name>` -> the published 127.0.0.1 host port, or null. */
150
+ /** Live Docker proof -> the published 127.0.0.1 host port and labels, or null. */
151
+ async function publishedSidecar(
152
+ name: string,
153
+ ): Promise<{ port: number; labels: Record<string, string> } | null> {
154
+ const proof = await inspectTunnelContainer(name, INNER_PORT);
155
+ return proof.kind === "running" && proof.published?.host === "127.0.0.1"
156
+ ? { port: proof.published.port, labels: proof.labels }
157
+ : null;
158
+ }
159
+
139
160
  async function publishedPort(name: string): Promise<number | null> {
140
- const r = await dockerCli(["port", name, String(INNER_PORT)], {
141
- timeoutMs: 5_000,
142
- });
143
- const m = r.stdout.match(/127\.0\.0\.1:(\d+)/);
144
- return r.status === 0 && m ? Number(m[1]) : null;
161
+ return (await publishedSidecar(name))?.port ?? null;
145
162
  }
146
163
 
147
164
  async function createSidecar(args: {
@@ -152,6 +169,7 @@ async function createSidecar(args: {
152
169
  }): Promise<number | null> {
153
170
  const name = sidecarName(args.taskId, args.name);
154
171
  const appContainer = `${args.composeProject}-app-1`;
172
+ const target = `${appContainer}:${args.containerPort}`;
155
173
 
156
174
  // Reuse a still-running sidecar from a previous host process (--rm keeps it up
157
175
  // across a host restart; the in-memory cache was cleared). `docker port` is an
@@ -163,9 +181,20 @@ async function createSidecar(args: {
163
181
  // sidecar serves `socket hang up` forever: the recovery path invalidates the
164
182
  // CACHE on a failed connect, then lands right back here and returns the same
165
183
  // corpse. Nothing could break the cycle short of removing it by hand.
166
- const existing = await publishedPort(name);
184
+ const existing = await publishedSidecar(name);
167
185
  if (existing !== null) {
168
- if (await probeSidecar(existing)) return existing;
186
+ // The Docker name is stable per (task, preview name), while a task-scoped
187
+ // preview may later point that name at another container port. The
188
+ // process-local cache key carries the port, but it vanishes on host
189
+ // restart; without a durable target label a healthy old proxy would be
190
+ // reused and silently route the authenticated request to the wrong port.
191
+ // Legacy unlabeled sidecars rebuild once.
192
+ if (
193
+ existing.labels[TARGET_LABEL] === target &&
194
+ (await probeSidecar(existing.port))
195
+ ) {
196
+ return existing.port;
197
+ }
169
198
  await dockerCli(["rm", "-f", name], { timeoutMs: 10_000 });
170
199
  }
171
200
 
@@ -179,6 +208,7 @@ async function createSidecar(args: {
179
208
  "--name", name,
180
209
  "--network", `${args.composeProject}_default`,
181
210
  "--label", `${LABEL}=${args.taskId}`,
211
+ "--label", `${TARGET_LABEL}=${target}`,
182
212
  "-p", `127.0.0.1::${INNER_PORT}`,
183
213
  "--entrypoint", "node",
184
214
  image,
@@ -224,7 +254,31 @@ export async function ensurePreviewSidecar(args: {
224
254
  }): Promise<number | null> {
225
255
  const key = cacheKey(args.taskId, args.name, args.containerPort);
226
256
  const cached = cache.get(key);
227
- if (cached !== undefined) return cached;
257
+ if (cached !== undefined) {
258
+ const current = await publishedSidecar(sidecarName(args.taskId, args.name));
259
+ const expectedTarget =
260
+ `${args.composeProject}-app-1:${args.containerPort}`;
261
+ const app =
262
+ current?.port === cached &&
263
+ current.labels[TARGET_LABEL] === expectedTarget
264
+ ? await inspectTunnelContainer(
265
+ `${args.composeProject}-app-1`,
266
+ args.containerPort,
267
+ )
268
+ : null;
269
+ // The sidecar's PID is the proxy itself, and it resolves the app by stable
270
+ // Compose DNS name for each connection. Proving both exact containers are
271
+ // running avoids the old 150ms socket-settle penalty on every browser asset
272
+ // while still rejecting a localhost port inherited after daemon restart.
273
+ if (
274
+ current?.port === cached &&
275
+ current.labels[TARGET_LABEL] === expectedTarget &&
276
+ app?.kind === "running"
277
+ ) {
278
+ return cached;
279
+ }
280
+ cache.delete(key);
281
+ }
228
282
 
229
283
  let pending = inflight.get(key);
230
284
  if (!pending) {