@runuai/host 0.9.13 → 0.9.42

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 +69 -4
  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 +3070 -223
  53. package/lib/preview-sidecar.ts +57 -13
  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 +945 -0
  70. package/lib/task-environment/index.ts +364 -0
  71. package/lib/task-environment/legacy-adoption.ts +443 -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 +1463 -109
  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 +871 -50
  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,20 @@
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
24
  const LABEL = "com.runuai.preview-sidecar.task";
25
+ const TARGET_LABEL = "com.runuai.preview-sidecar.target";
24
26
 
25
27
  /** (taskId,name,containerPort) -> published 127.0.0.1 host port of a live sidecar. */
26
28
  const cache = new Map<string, number>();
@@ -135,13 +137,18 @@ async function imageOf(appContainer: string): Promise<string | null> {
135
137
  return r.status === 0 && r.stdout.trim() ? r.stdout.trim() : null;
136
138
  }
137
139
 
138
- /** `docker port <name>` -> the published 127.0.0.1 host port, or null. */
140
+ /** Live Docker proof -> the published 127.0.0.1 host port and labels, or null. */
141
+ async function publishedSidecar(
142
+ name: string,
143
+ ): Promise<{ port: number; labels: Record<string, string> } | null> {
144
+ const proof = await inspectTunnelContainer(name, INNER_PORT);
145
+ return proof.kind === "running" && proof.published?.host === "127.0.0.1"
146
+ ? { port: proof.published.port, labels: proof.labels }
147
+ : null;
148
+ }
149
+
139
150
  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;
151
+ return (await publishedSidecar(name))?.port ?? null;
145
152
  }
146
153
 
147
154
  async function createSidecar(args: {
@@ -152,6 +159,7 @@ async function createSidecar(args: {
152
159
  }): Promise<number | null> {
153
160
  const name = sidecarName(args.taskId, args.name);
154
161
  const appContainer = `${args.composeProject}-app-1`;
162
+ const target = `${appContainer}:${args.containerPort}`;
155
163
 
156
164
  // Reuse a still-running sidecar from a previous host process (--rm keeps it up
157
165
  // across a host restart; the in-memory cache was cleared). `docker port` is an
@@ -163,9 +171,20 @@ async function createSidecar(args: {
163
171
  // sidecar serves `socket hang up` forever: the recovery path invalidates the
164
172
  // CACHE on a failed connect, then lands right back here and returns the same
165
173
  // corpse. Nothing could break the cycle short of removing it by hand.
166
- const existing = await publishedPort(name);
174
+ const existing = await publishedSidecar(name);
167
175
  if (existing !== null) {
168
- if (await probeSidecar(existing)) return existing;
176
+ // The Docker name is stable per (task, preview name), while a task-scoped
177
+ // preview may later point that name at another container port. The
178
+ // process-local cache key carries the port, but it vanishes on host
179
+ // restart; without a durable target label a healthy old proxy would be
180
+ // reused and silently route the authenticated request to the wrong port.
181
+ // Legacy unlabeled sidecars rebuild once.
182
+ if (
183
+ existing.labels[TARGET_LABEL] === target &&
184
+ (await probeSidecar(existing.port))
185
+ ) {
186
+ return existing.port;
187
+ }
169
188
  await dockerCli(["rm", "-f", name], { timeoutMs: 10_000 });
170
189
  }
171
190
 
@@ -179,6 +198,7 @@ async function createSidecar(args: {
179
198
  "--name", name,
180
199
  "--network", `${args.composeProject}_default`,
181
200
  "--label", `${LABEL}=${args.taskId}`,
201
+ "--label", `${TARGET_LABEL}=${target}`,
182
202
  "-p", `127.0.0.1::${INNER_PORT}`,
183
203
  "--entrypoint", "node",
184
204
  image,
@@ -224,7 +244,31 @@ export async function ensurePreviewSidecar(args: {
224
244
  }): Promise<number | null> {
225
245
  const key = cacheKey(args.taskId, args.name, args.containerPort);
226
246
  const cached = cache.get(key);
227
- if (cached !== undefined) return cached;
247
+ if (cached !== undefined) {
248
+ const current = await publishedSidecar(sidecarName(args.taskId, args.name));
249
+ const expectedTarget =
250
+ `${args.composeProject}-app-1:${args.containerPort}`;
251
+ const app =
252
+ current?.port === cached &&
253
+ current.labels[TARGET_LABEL] === expectedTarget
254
+ ? await inspectTunnelContainer(
255
+ `${args.composeProject}-app-1`,
256
+ args.containerPort,
257
+ )
258
+ : null;
259
+ // The sidecar's PID is the proxy itself, and it resolves the app by stable
260
+ // Compose DNS name for each connection. Proving both exact containers are
261
+ // running avoids the old 150ms socket-settle penalty on every browser asset
262
+ // while still rejecting a localhost port inherited after daemon restart.
263
+ if (
264
+ current?.port === cached &&
265
+ current.labels[TARGET_LABEL] === expectedTarget &&
266
+ app?.kind === "running"
267
+ ) {
268
+ return cached;
269
+ }
270
+ cache.delete(key);
271
+ }
228
272
 
229
273
  let pending = inflight.get(key);
230
274
  if (!pending) {