@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
package/lib/db.ts CHANGED
@@ -65,4 +65,5 @@ export function getDb(): ReturnType<typeof drizzle> {
65
65
  }
66
66
 
67
67
  export type Db = ReturnType<typeof getDb>;
68
+ export type { HostTask } from "../db/schema";
68
69
  export { schema };
@@ -23,34 +23,84 @@ export interface DockerResult {
23
23
  status: number | null;
24
24
  stdout: string;
25
25
  stderr: string;
26
+ /** True when tailOutputBytes discarded earlier diagnostic output. */
27
+ outputTruncated?: boolean;
26
28
  }
27
29
 
28
30
  export function dockerCli(
29
31
  args: string[],
30
- opts: { input?: string; timeoutMs?: number } = {},
32
+ opts: {
33
+ input?: string;
34
+ timeoutMs?: number;
35
+ /** Optional aggregate stdout+stderr cap for cardinality-sensitive probes.
36
+ * Exceeding it kills the child and resolves with status:null. */
37
+ maxOutputBytes?: number;
38
+ /** Keep draining the child but retain only this many trailing bytes from
39
+ * each output stream. Use for legitimately verbose bounded operations
40
+ * whose complete compiler/install log must not accumulate in host RAM. */
41
+ tailOutputBytes?: number;
42
+ /** Selection probes can intentionally ignore this process's pinned host. */
43
+ env?: NodeJS.ProcessEnv;
44
+ } = {},
31
45
  ): Promise<DockerResult> {
32
46
  return new Promise<DockerResult>((resolve) => {
33
47
  let stdout = "";
34
48
  let stderr = "";
49
+ let stdoutTruncated = false;
50
+ let stderrTruncated = false;
35
51
  let settled = false;
36
- const child = spawn("docker", args, { stdio: ["pipe", "pipe", "pipe"] });
52
+ const child = spawn("docker", args, {
53
+ stdio: ["pipe", "pipe", "pipe"],
54
+ env: opts.env,
55
+ });
37
56
  const timer = setTimeout(() => {
38
57
  child.kill("SIGKILL");
58
+ // A SIGKILLed client normally closes at once, but an unreapable child
59
+ // (D-state, wedged pipe) must not hold this promise open forever: the
60
+ // caller's own deadline already expired, so settle as failed after a
61
+ // short grace even if `close` never fires.
62
+ const settle = setTimeout(() => {
63
+ stderr += `docker did not settle after its ${opts.timeoutMs ?? DEFAULT_TIMEOUT_MS}ms deadline`;
64
+ finish(null);
65
+ }, 5_000);
66
+ settle.unref?.();
39
67
  }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
40
68
  timer.unref?.();
41
69
  const finish = (status: number | null) => {
42
70
  if (settled) return;
43
71
  settled = true;
44
72
  clearTimeout(timer);
45
- resolve({ status, stdout, stderr });
73
+ resolve({
74
+ status,
75
+ stdout,
76
+ stderr,
77
+ outputTruncated: stdoutTruncated || stderrTruncated || undefined,
78
+ });
46
79
  };
47
80
  child.stdout?.setEncoding("utf8");
48
81
  child.stderr?.setEncoding("utf8");
49
82
  child.stdout?.on("data", (c: string) => {
50
- stdout += c;
83
+ if (settled) return;
84
+ if (outputWouldExceedLimit(stdout, stderr, c, opts.maxOutputBytes)) {
85
+ stderr += "docker output exceeded configured limit";
86
+ child.kill("SIGKILL");
87
+ finish(null);
88
+ return;
89
+ }
90
+ const tailed = appendOutputTail(stdout, c, opts.tailOutputBytes);
91
+ stdout = tailed.output;
92
+ stdoutTruncated ||= tailed.truncated;
51
93
  });
52
94
  child.stderr?.on("data", (c: string) => {
53
- stderr += c;
95
+ if (settled) return;
96
+ if (outputWouldExceedLimit(stdout, stderr, c, opts.maxOutputBytes)) {
97
+ child.kill("SIGKILL");
98
+ finish(null);
99
+ return;
100
+ }
101
+ const tailed = appendOutputTail(stderr, c, opts.tailOutputBytes);
102
+ stderr = tailed.output;
103
+ stderrTruncated ||= tailed.truncated;
54
104
  });
55
105
  child.on("error", (err: Error) => {
56
106
  stderr += err.message;
@@ -70,3 +120,35 @@ export function dockerCli(
70
120
  child.stdin?.end();
71
121
  });
72
122
  }
123
+
124
+ function appendOutputTail(
125
+ current: string,
126
+ chunk: string,
127
+ limit: number | undefined,
128
+ ): { output: string; truncated: boolean } {
129
+ if (limit === undefined) return { output: current + chunk, truncated: false };
130
+ const bounded = Math.max(0, Math.floor(limit));
131
+ const combined = Buffer.from(current + chunk);
132
+ if (combined.byteLength <= bounded) {
133
+ return { output: combined.toString("utf8"), truncated: false };
134
+ }
135
+ return {
136
+ output: combined.subarray(combined.byteLength - bounded).toString("utf8"),
137
+ truncated: true,
138
+ };
139
+ }
140
+
141
+ function outputWouldExceedLimit(
142
+ stdout: string,
143
+ stderr: string,
144
+ chunk: string,
145
+ limit: number | undefined,
146
+ ): boolean {
147
+ return (
148
+ limit !== undefined &&
149
+ Buffer.byteLength(stdout) +
150
+ Buffer.byteLength(stderr) +
151
+ Buffer.byteLength(chunk) >
152
+ limit
153
+ );
154
+ }
@@ -29,7 +29,12 @@ import { join } from "node:path";
29
29
  import { eq } from "drizzle-orm";
30
30
 
31
31
  import { getDb, schema } from "./db";
32
- import { dockerCli } from "./docker-exec";
32
+ import {
33
+ taskContainerBackend,
34
+ taskContainerCli,
35
+ } from "./task-container-cli";
36
+ import { RUNTIME_AUTHORITY_ENV, runtimeAuthorityDockerExecArgs } from "./runtime-authority";
37
+ import type { TaskEnvironmentHandle } from "./task-environment/types";
33
38
  import { openAesGcm, sealAesGcm } from "./secrets";
34
39
  import { newId } from "./ulid";
35
40
 
@@ -354,9 +359,46 @@ async function copyDirInto(
354
359
  hostDir: string,
355
360
  containerDir: string,
356
361
  container: string,
362
+ environment?: Pick<TaskEnvironmentHandle, "exec" | "copy">,
357
363
  ): Promise<void> {
364
+ if (environment) {
365
+ const run = async (argv: [string, ...string[]], step: string) => {
366
+ const result = await environment.exec({
367
+ argv,
368
+ user: "root",
369
+ env: RUNTIME_AUTHORITY_ENV,
370
+ timeoutMs: EXEC_TIMEOUT_MS,
371
+ maxOutputBytes: 64 * 1024,
372
+ });
373
+ if (result.exitCode !== 0) {
374
+ const detail = Buffer.from(result.stderr).toString("utf8").trim().slice(0, 200);
375
+ throw new Error(
376
+ `${step} failed (${result.exitCode === null ? "environment timeout" : `exit ${result.exitCode}`})` +
377
+ (detail ? `: ${detail}` : ""),
378
+ );
379
+ }
380
+ };
381
+ await run(["/bin/mkdir", "-p", containerDir], `mkdir ${containerDir}`);
382
+ await environment.copy({
383
+ direction: "into",
384
+ source: hostDir,
385
+ destination: containerDir,
386
+ });
387
+ await run(
388
+ ["/bin/chown", "-R", "node:node", containerDir],
389
+ `chown -R node:node ${containerDir}`,
390
+ );
391
+ return;
392
+ }
393
+ // Backend-aware fallback (2026-08-18 sweep + review round 4): the apple
394
+ // path normally arrives with an environment handle above; when it does
395
+ // not, the bundled CLI speaks these exec/cp verbs — but the CALLER's name
396
+ // is the Docker replica shape, which does not exist on apple.
397
+ const target = taskContainerBackend().apple
398
+ ? container.replace(/-app-1$/, "-app")
399
+ : container;
358
400
  const must = async (args: string[], step: string): Promise<void> => {
359
- const res = await dockerCli(args, { timeoutMs: EXEC_TIMEOUT_MS });
401
+ const res = await taskContainerCli(args, { timeoutMs: EXEC_TIMEOUT_MS });
360
402
  if (res.status !== 0) {
361
403
  const detail = res.stderr.trim().slice(0, 200);
362
404
  throw new Error(
@@ -366,13 +408,32 @@ async function copyDirInto(
366
408
  }
367
409
  };
368
410
  await must(
369
- ["exec", "-u", "root", container, "mkdir", "-p", containerDir],
411
+ [
412
+ "exec",
413
+ "-u",
414
+ "root",
415
+ ...runtimeAuthorityDockerExecArgs(),
416
+ target,
417
+ "/bin/mkdir",
418
+ "-p",
419
+ containerDir,
420
+ ],
370
421
  `mkdir ${containerDir}`,
371
422
  );
372
423
  // Copy the dir CONTENTS (trailing /.) into the target dir.
373
- await must(["cp", `${hostDir}/.`, `${container}:${containerDir}`], "docker cp account dir");
424
+ await must(["cp", `${hostDir}/.`, `${target}:${containerDir}`], "docker cp account dir");
374
425
  await must(
375
- ["exec", "-u", "root", container, "chown", "-R", "node:node", containerDir],
426
+ [
427
+ "exec",
428
+ "-u",
429
+ "root",
430
+ ...runtimeAuthorityDockerExecArgs(),
431
+ target,
432
+ "/bin/chown",
433
+ "-R",
434
+ "node:node",
435
+ containerDir,
436
+ ],
376
437
  `chown -R node:node ${containerDir}`,
377
438
  );
378
439
  }
@@ -393,6 +454,7 @@ export async function provisionEngineAccounts(
393
454
  container: string,
394
455
  kinds: Iterable<string>,
395
456
  seams: Partial<AccountSeams> = {},
457
+ environment?: Pick<TaskEnvironmentHandle, "exec" | "copy">,
396
458
  ): Promise<ProvisionEngineAccountsResult> {
397
459
  const s = withDefaults(seams);
398
460
  const seen = new Set<string>();
@@ -413,6 +475,7 @@ export async function provisionEngineAccounts(
413
475
  account.configDir.hostDir,
414
476
  account.configDir.containerDir,
415
477
  container,
478
+ environment,
416
479
  );
417
480
  copied += 1;
418
481
  } catch (err) {