@runuai/host 0.9.150 → 0.9.152

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 (107) hide show
  1. package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
  2. package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
  3. package/db/migrations/meta/_journal.json +14 -0
  4. package/db/schema.ts +188 -0
  5. package/lib/agent-cli.ts +11 -13
  6. package/lib/agent.ts +177 -7
  7. package/lib/agents/durable-proc.ts +56 -44
  8. package/lib/agents/registry.ts +19 -3
  9. package/lib/agents/session-state.ts +23 -10
  10. package/lib/agents/transport.ts +101 -69
  11. package/lib/apple-one-off.ts +277 -0
  12. package/lib/apple-runtime-recycle.ts +4 -3
  13. package/lib/attachments.ts +19 -17
  14. package/lib/capture-flow.ts +319 -0
  15. package/lib/cloud-credential-cleanup-adapter.ts +123 -0
  16. package/lib/cloud-credential-host.ts +512 -0
  17. package/lib/cloud-credential-leases.ts +2544 -0
  18. package/lib/cloud-credential-runtime.ts +1035 -0
  19. package/lib/codex-auth.ts +21 -1
  20. package/lib/container-runtime.ts +76 -0
  21. package/lib/credential-cleanup-inventory.ts +405 -0
  22. package/lib/credential-consumers/engine-anchor.ts +1880 -0
  23. package/lib/credential-consumers/engines.ts +773 -0
  24. package/lib/credential-consumers/github.ts +594 -0
  25. package/lib/credential-consumers/mcp.ts +384 -0
  26. package/lib/credential-consumers/task-files.ts +127 -0
  27. package/lib/credential-store-pickup.ts +15 -2
  28. package/lib/credential-store.ts +361 -33
  29. package/lib/credentials-activation-authority.ts +546 -0
  30. package/lib/credentials-purge.ts +1006 -0
  31. package/lib/db.ts +11 -0
  32. package/lib/docker-exec.ts +3 -1
  33. package/lib/engine-accounts.ts +42 -32
  34. package/lib/engine-connect.ts +47 -2
  35. package/lib/engine-credential-env.ts +50 -0
  36. package/lib/engine-login.ts +83 -1
  37. package/lib/env-file.ts +62 -8
  38. package/lib/env.ts +5 -0
  39. package/lib/fs-aliases.ts +49 -0
  40. package/lib/git-clone-credentials.ts +334 -0
  41. package/lib/git-diff.ts +219 -177
  42. package/lib/git-identity.ts +59 -14
  43. package/lib/github-origin.ts +28 -0
  44. package/lib/github-tokens.ts +56 -21
  45. package/lib/host-env.ts +17 -4
  46. package/lib/launch-mode.ts +25 -0
  47. package/lib/legacy-runtime-retirement.ts +388 -0
  48. package/lib/machine-exec.ts +14 -3
  49. package/lib/machine-provider-aws.ts +50 -8
  50. package/lib/machine-provider-local.ts +4 -0
  51. package/lib/machine-provider.ts +4 -0
  52. package/lib/managed-operation-drain.ts +8 -0
  53. package/lib/managed-runtime.ts +371 -7
  54. package/lib/mcp-capture.ts +860 -0
  55. package/lib/mcp-connections.ts +8 -1
  56. package/lib/mcp-gateway.ts +168 -10
  57. package/lib/obs.ts +2 -6
  58. package/lib/orchestrator.ts +1837 -224
  59. package/lib/person-capture.ts +327 -0
  60. package/lib/project-env-legacy-tags.ts +130 -0
  61. package/lib/project-env-task-replica.ts +1752 -0
  62. package/lib/runner-boot.ts +10 -0
  63. package/lib/runtime-activation-tail.ts +50 -3
  64. package/lib/runtime-archive.ts +68 -1
  65. package/lib/runtime-reconnect.ts +86 -0
  66. package/lib/runtime-state.ts +429 -13
  67. package/lib/safe-http.ts +364 -0
  68. package/lib/secrets-proxy/task-trust.ts +34 -3
  69. package/lib/sidecar-resume.ts +18 -5
  70. package/lib/skills.ts +47 -8
  71. package/lib/standard-image.ts +157 -103
  72. package/lib/stopped-task-status-queue.ts +68 -10
  73. package/lib/task-credential-cleanup.ts +525 -0
  74. package/lib/task-diff-sandbox.ts +464 -0
  75. package/lib/task-diff.ts +66 -163
  76. package/lib/task-env-source.ts +68 -14
  77. package/lib/task-environment/anchor.ts +31 -0
  78. package/lib/task-environment/apple-container.ts +69 -28
  79. package/lib/task-environment/docker.ts +101 -20
  80. package/lib/task-environment/machine-environment.ts +61 -0
  81. package/lib/task-environment/machine-task-up.ts +459 -96
  82. package/lib/task-environment/machine.ts +101 -11
  83. package/lib/task-environment/self.ts +108 -50
  84. package/lib/task-environment/types.ts +89 -5
  85. package/lib/task-environment/workspace-files.ts +650 -25
  86. package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
  87. package/lib/task-gateway-recovery.ts +4 -1
  88. package/lib/task-gateway-state.ts +15 -0
  89. package/lib/task-machine-swap-safety.ts +729 -0
  90. package/lib/token-broker-adopt.ts +344 -0
  91. package/lib/token-broker-lambda.ts +56 -2
  92. package/lib/token-broker-local.ts +28 -4
  93. package/lib/token-broker-revision.ts +310 -0
  94. package/lib/token-broker-ssm.ts +368 -28
  95. package/lib/token-lanes/openai-oauth.ts +7 -3
  96. package/lib/token-lanes/xai-oauth.ts +7 -3
  97. package/lib/transcript.ts +9 -5
  98. package/package.json +6 -2
  99. package/scripts/agent/task-up.sh +242 -100
  100. package/src/cloud-credentials.ts +2399 -0
  101. package/src/event-outbox.ts +8 -6
  102. package/src/index.ts +1386 -875
  103. package/src/main.ts +1061 -139
  104. package/src/outbound-frame.ts +31 -0
  105. package/src/project-env-policy.ts +125 -10
  106. package/src/protocol.ts +699 -23
  107. package/src/ui/server.ts +48 -10
@@ -148,16 +148,55 @@ export function awsUserData(authorizedPublicKey: string): string {
148
148
  ' chmod 700 "$home/.ssh"',
149
149
  ' chmod 600 "$home/.ssh/authorized_keys"',
150
150
  "done",
151
+ // This user-data is emitted only by the task-machine provider. Owned
152
+ // runners use the host installer and never execute it, so their OS swap
153
+ // remains untouched.
154
+ //
151
155
  // Swap BEFORE any workload: an Opus agent + a bundler + Chromium on a
152
- // swapless machine hits the memory wall as a thrash (load 14, RAM 100%,
153
- // sshd/code-server accept loops starved — live 2026-08-31 on the first
154
- // real machine task; the HOSTS got their swapfile in 0.9.85, the
155
- // machines never did). Best-effort: a machine without fallocate still
156
- // boots.
156
+ // swapless machine hits the memory wall. The backing file is encrypted
157
+ // by a plain dm-crypt mapping whose key is read from /dev/urandom every
158
+ // time the mapping is created. /run's marker makes this idempotent within
159
+ // one boot; crypttab recreates the mapping (and therefore the key) after
160
+ // every boot. The persistent marker only says the backing file has been
161
+ // scrubbed once and has never subsequently held plaintext swap.
162
+ "if ! command -v cryptsetup >/dev/null 2>&1 || [ ! -x /lib/systemd/system-generators/systemd-cryptsetup-generator ]; then apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends cryptsetup; fi",
163
+ "mkdir -p /run/uai /var/lib/uai",
164
+ "chmod 700 /run/uai /var/lib/uai",
165
+ "touch /etc/crypttab /etc/fstab",
166
+ "crypttab_tmp=$(mktemp /etc/crypttab.uai.XXXXXX)",
167
+ "awk 'BEGIN { managed=0 } $0 == \"# BEGIN UAI TASK MACHINE SWAP\" { managed=1; next } $0 == \"# END UAI TASK MACHINE SWAP\" { managed=0; next } managed { next } /^[[:space:]]*#/ { print; next } $1 == \"uai-swap\" || $4 ~ /(^|,)swap(,|$)/ { print \"# uai-replaced-swap \" $0; next } { print }' /etc/crypttab > \"$crypttab_tmp\"",
168
+ "printf '%s\\n' '# BEGIN UAI TASK MACHINE SWAP' 'uai-swap /swapfile /dev/urandom swap,cipher=aes-xts-plain64,size=512' '# END UAI TASK MACHINE SWAP' >> \"$crypttab_tmp\"",
169
+ "chown root:root \"$crypttab_tmp\" && chmod 600 \"$crypttab_tmp\" && mv -f -- \"$crypttab_tmp\" /etc/crypttab",
170
+ "fstab_tmp=$(mktemp /etc/fstab.uai.XXXXXX)",
171
+ "awk 'BEGIN { managed=0 } $0 == \"# BEGIN UAI TASK MACHINE SWAP\" { managed=1; next } $0 == \"# END UAI TASK MACHINE SWAP\" { managed=0; next } managed { next } /^[[:space:]]*#/ { print; next } NF >= 3 && $3 == \"swap\" { print \"# uai-disabled-raw-swap \" $0; next } { print }' /etc/fstab > \"$fstab_tmp\"",
172
+ "printf '%s\\n' '# BEGIN UAI TASK MACHINE SWAP' '/dev/mapper/uai-swap none swap sw 0 0' '# END UAI TASK MACHINE SWAP' >> \"$fstab_tmp\"",
173
+ "chown root:root \"$fstab_tmp\" && chmod 644 \"$fstab_tmp\" && mv -f -- \"$fstab_tmp\" /etc/fstab",
174
+ "active_swap=$(awk 'NR > 1 { print $1 }' /proc/swaps)",
175
+ "if [ ! -f /run/uai/swap-ready-v1 ] || [ \"$active_swap\" != /dev/mapper/uai-swap ]; then",
176
+ // Failing to page every old raw-swap page back into RAM is fatal: the
177
+ // sidecar must not start beside durable plaintext merely to keep booting.
178
+ " swapoff -a",
179
+ " cryptsetup close uai-swap 2>/dev/null || true",
180
+ " if [ ! -f /var/lib/uai/swap-backing-initialized-v1 ]; then",
181
+ " if [ -L /swapfile ] || { [ -e /swapfile ] && [ ! -f /swapfile ]; }; then echo 'unsafe /swapfile type' >&2; exit 1; fi",
182
+ // An older task-machine release used this file as raw swap. One complete
183
+ // zero pass removes any pages it left before the file is reused solely as
184
+ // ciphertext backing. Crashes are safe: without the marker the next run
185
+ // repeats the scrub.
186
+ " if [ -f /swapfile ]; then shred --iterations=0 --zero -- /swapfile; fi",
187
+ " rm -f -- /swapfile",
188
+ " fi",
157
189
  // 16G (owner decision 2026-08-31): the graceful-degradation floor has to
158
- // absorb the whole heavy shape, not soften its first half — 4G filled
159
- // and the wall moved instead of vanishing. Disk is already paid for.
160
- "(fallocate -l 16G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile && echo '/swapfile none swap sw 0 0' >> /etc/fstab) || true",
190
+ // absorb the whole heavy shape. Every command is mandatory; failure keeps
191
+ // cloud-init and therefore the task machine unavailable.
192
+ " fallocate -l 16G /swapfile",
193
+ " chown root:root /swapfile && chmod 600 /swapfile",
194
+ " cryptsetup open --batch-mode --type plain --cipher aes-xts-plain64 --key-size 512 --key-file /dev/urandom /swapfile uai-swap",
195
+ " mkswap /dev/mapper/uai-swap >/dev/null",
196
+ " swapon /dev/mapper/uai-swap",
197
+ " touch /var/lib/uai/swap-backing-initialized-v1 /run/uai/swap-ready-v1",
198
+ " chmod 600 /var/lib/uai/swap-backing-initialized-v1 /run/uai/swap-ready-v1",
199
+ "fi",
161
200
  // The task OWNS its machine (ADR-121 — per-customer kernel isolation is
162
201
  // the whole point); denying sudo just breaks project setups that
163
202
  // container tasks handle fine (live 2026-08-31: a Hightop machine task
@@ -609,6 +648,8 @@ export function createAwsMachineProvider(
609
648
  id: machineId,
610
649
  taskLabel: instance.taskLabel,
611
650
  state,
651
+ // Still stopping: its processes run until the instance is stopped.
652
+ ...(instance.state === "stopping" ? { frozen: true as const } : {}),
612
653
  address: state === "running" ? instance.privateIp : null,
613
654
  };
614
655
  }
@@ -860,6 +901,7 @@ export function createAwsMachineProvider(
860
901
  : instance.instanceId,
861
902
  taskLabel: instance.taskLabel,
862
903
  state,
904
+ ...(instance.state === "stopping" ? { frozen: true as const } : {}),
863
905
  address: state === "running" ? instance.privateIp : null,
864
906
  };
865
907
  });
@@ -75,6 +75,7 @@ interface InspectShape {
75
75
  name: string;
76
76
  taskLabel: string | null;
77
77
  state: MachineState;
78
+ frozen?: true;
78
79
  address: string | null;
79
80
  sshPort: number | null;
80
81
  }
@@ -148,6 +149,7 @@ function parseInspect(id: string, stdout: string): InspectShape | null {
148
149
  name,
149
150
  taskLabel,
150
151
  state: stateFromDocker(stateObj?.Status),
152
+ ...(stateObj?.Status === "paused" ? { frozen: true as const } : {}),
151
153
  address,
152
154
  sshPort,
153
155
  };
@@ -171,6 +173,7 @@ export function createLocalMachineProvider(
171
173
  id,
172
174
  taskLabel: record.taskLabel,
173
175
  state: record.state,
176
+ ...(record.frozen ? { frozen: true as const } : {}),
174
177
  address: record.state === "running" ? record.address : null,
175
178
  ...(record.state === "running" && record.sshPort !== null
176
179
  ? { sshPort: record.sshPort }
@@ -322,6 +325,7 @@ export function createLocalMachineProvider(
322
325
  id: record.name,
323
326
  taskLabel: record.taskLabel,
324
327
  state: record.state,
328
+ ...(record.frozen ? { frozen: true as const } : {}),
325
329
  address: record.state === "running" ? record.address : null,
326
330
  ...(record.state === "running" && record.sshPort !== null
327
331
  ? { sshPort: record.sshPort }
@@ -48,6 +48,10 @@ export interface MachineSpec {
48
48
  export interface MachineInfo {
49
49
  /** Backend-native identifier: instance id or container id/name. */
50
50
  id: string;
51
+ /** Stopped as far as work goes, yet its processes still exist (a paused
52
+ * container, an instance still stopping): they could run or write again
53
+ * without a new start. */
54
+ frozen?: true;
51
55
  /** The recorded task ownership label, null when the machine carries none
52
56
  * (a foreign machine occupying a reserved name is never ours to touch). */
53
57
  taskLabel: string | null;
@@ -6,6 +6,7 @@
6
6
  export class ManagedOperationDrain {
7
7
  private active = 0;
8
8
  private accepting = true;
9
+ private idleWaiters: Array<() => void> = [];
9
10
 
10
11
  constructor(private readonly onSettled: () => void) {}
11
12
 
@@ -22,6 +23,12 @@ export class ManagedOperationDrain {
22
23
  this.accepting = false;
23
24
  }
24
25
 
26
+ /** Resolves once nothing tracked is under way (at once when nothing is). */
27
+ idle(): Promise<void> {
28
+ if (this.active === 0) return Promise.resolve();
29
+ return new Promise<void>((resolve) => this.idleWaiters.push(resolve));
30
+ }
31
+
25
32
  track<T>(operation: () => Promise<T>): Promise<T> {
26
33
  if (!this.accepting) {
27
34
  return Promise.reject(
@@ -44,6 +51,7 @@ export class ManagedOperationDrain {
44
51
  if (this.active < 0) {
45
52
  throw new Error("managed operation drain settled more work than it tracked");
46
53
  }
54
+ if (this.active === 0) for (const resolve of this.idleWaiters.splice(0)) resolve();
47
55
  this.onSettled();
48
56
  }
49
57
  }