@runuai/host 0.9.14 → 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 +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 +3060 -218
  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 +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
@@ -0,0 +1,364 @@
1
+ import {
2
+ agent,
3
+ type TaskDownResult,
4
+ type TaskLaunchInput,
5
+ type TaskUpCredentials,
6
+ type TaskUpResult,
7
+ } from "../agent";
8
+ import { pinnedContainerRuntimeMachineIdentity } from "../container-runtime";
9
+ import { getDb, schema, type HostTask } from "../db";
10
+ import { taskDir } from "../env";
11
+ import {
12
+ APPLE_TASK_ENVIRONMENT_PROVIDER,
13
+ type AppleTaskEnvironmentRecoveryDriver,
14
+ createAppleContainerTaskEnvironmentProvider,
15
+ parseAppleTaskEnvironmentLocator,
16
+ type AppleMachineIdentity,
17
+ } from "./apple-container";
18
+ import {
19
+ createDockerTaskEnvironmentProvider,
20
+ DOCKER_TASK_ENVIRONMENT_PROVIDER,
21
+ parseDockerTaskEnvironmentLocator,
22
+ type DockerTaskEnvironmentRecoveryDriver,
23
+ type DockerMachineIdentity,
24
+ } from "./docker";
25
+ import { TaskEnvironmentRegistry } from "./registry";
26
+ import {
27
+ parseTaskEnvironmentLocator,
28
+ type TaskEnvironmentHandle,
29
+ type TaskEnvironmentLocator,
30
+ type TaskEnvironmentProvisioned,
31
+ } from "./types";
32
+
33
+ export {
34
+ adoptLegacyTaskEnvironments,
35
+ type LegacyTaskEnvironmentAdoptionDeps,
36
+ type LegacyTaskEnvironmentAdoptionVerdict,
37
+ } from "./legacy-adoption";
38
+
39
+ const registry = new TaskEnvironmentRegistry<
40
+ TaskLaunchInput,
41
+ TaskUpCredentials,
42
+ TaskUpResult,
43
+ TaskDownResult
44
+ >();
45
+
46
+ let dockerRecoveryDriver: DockerTaskEnvironmentRecoveryDriver | null = null;
47
+
48
+ const dockerProvider = createDockerTaskEnvironmentProvider({
49
+ machineIdentity: requireDockerMachineIdentity,
50
+ taskUp: agent.taskUp,
51
+ taskEnvironmentDown: agent.taskEnvironmentDown,
52
+ recoveryDriver: () => dockerRecoveryDriver,
53
+ });
54
+
55
+ registry.register(dockerProvider);
56
+
57
+ /** Machine identity union across every registered backend. */
58
+ export type HostMachineIdentity = DockerMachineIdentity | AppleMachineIdentity;
59
+
60
+ export interface AppleContainerBinding {
61
+ /** Absolute path of the authenticated bundled `container` CLI. */
62
+ containerCliPath: () => string;
63
+ machineIdentity: () => AppleMachineIdentity;
64
+ }
65
+
66
+ let appleBinding: AppleContainerBinding | null = null;
67
+
68
+ /** Bind the Apple container provider to the authenticated bundle + minted
69
+ * installation identity. Selection wiring installs this exactly when the
70
+ * apple-container backend is active; a null binding keeps reconstruction of
71
+ * apple locators fail-closed. */
72
+ export function setAppleContainerBinding(
73
+ binding: AppleContainerBinding | null,
74
+ ): void {
75
+ appleBinding = binding;
76
+ }
77
+
78
+ function requireAppleBinding(): AppleContainerBinding {
79
+ if (!appleBinding) {
80
+ throw new Error("the apple-container runtime is not active on this host");
81
+ }
82
+ return appleBinding;
83
+ }
84
+
85
+ let appleRecoveryDriver: AppleTaskEnvironmentRecoveryDriver | null = null;
86
+
87
+ /** Bind the Apple provider's recovery implementation (orchestrator-owned),
88
+ * mirroring the Docker seam so HMR reloads cannot strand stale handles. */
89
+ export function setAppleTaskEnvironmentRecoveryDriver(
90
+ driver: AppleTaskEnvironmentRecoveryDriver,
91
+ ): void {
92
+ appleRecoveryDriver = driver;
93
+ }
94
+
95
+ const appleProvider = createAppleContainerTaskEnvironmentProvider<
96
+ TaskLaunchInput,
97
+ TaskUpCredentials,
98
+ TaskUpResult
99
+ >({
100
+ containerCliPath: () => requireAppleBinding().containerCliPath(),
101
+ machineIdentity: () => requireAppleBinding().machineIdentity(),
102
+ taskEnvironmentDown: agent.taskEnvironmentDown,
103
+ recoveryDriver: () => appleRecoveryDriver,
104
+ provisioning: {
105
+ // The runtime pin published UAI_TASK_RUNTIME / UAI_CONTAINER_CLI, so the
106
+ // same task-up entrypoint drives the Apple CLI instead of Compose.
107
+ taskUp: agent.taskUp,
108
+ inputTaskId: (input) => input.task.id,
109
+ hostWorktreePath: (taskId) => taskDir(taskId),
110
+ resultWorktreePath: (result) => result.worktreePath,
111
+ },
112
+ });
113
+
114
+ registry.register(appleProvider);
115
+
116
+ /**
117
+ * Bind the Docker provider's private recovery implementation. Kept as a
118
+ * replaceable module seam so HMR can reload the orchestrator without leaving
119
+ * handles captured to an obsolete implementation.
120
+ */
121
+ export function setDockerTaskEnvironmentRecoveryDriver(
122
+ driver: DockerTaskEnvironmentRecoveryDriver,
123
+ ): void {
124
+ dockerRecoveryDriver = driver;
125
+ }
126
+
127
+ export function provisionTaskEnvironment(
128
+ input: TaskLaunchInput,
129
+ credentials: TaskUpCredentials = {},
130
+ onPrepared?: (locator: TaskEnvironmentLocator) => Promise<void>,
131
+ ): Promise<TaskEnvironmentProvisioned<TaskUpResult, TaskDownResult>> {
132
+ const provider =
133
+ requireMachineIdentity().backend === "apple-container"
134
+ ? appleProvider
135
+ : dockerProvider;
136
+ return provider.provision({
137
+ taskId: input.task.id,
138
+ input,
139
+ credentials,
140
+ onPrepared,
141
+ });
142
+ }
143
+
144
+ export function reconstructTaskEnvironment(
145
+ locator: TaskEnvironmentLocator,
146
+ ): Promise<TaskEnvironmentHandle<TaskDownResult>> {
147
+ return registry.reconstruct(locator);
148
+ }
149
+
150
+ export async function reconstructPersistedTaskEnvironment(
151
+ task: HostTask,
152
+ ): Promise<TaskEnvironmentHandle<TaskDownResult> | null> {
153
+ const locator = persistedTaskEnvironmentLocator(task, requireMachineIdentity());
154
+ if (locator === null) return null;
155
+ const handle = await registry.reconstruct(locator);
156
+ if (handle.descriptor.taskId !== task.taskId) {
157
+ throw new Error("task environment locator belongs to a different task");
158
+ }
159
+ return handle;
160
+ }
161
+
162
+ /**
163
+ * Provider metadata is authoritative. Legacy resource rows must first cross
164
+ * the read-only adoption proof; reconstruction never auto-binds them merely
165
+ * because the currently-selected endpoint happens to answer.
166
+ */
167
+ export function persistedTaskEnvironmentLocator(
168
+ task: Pick<
169
+ HostTask,
170
+ | "taskId"
171
+ | "composeProject"
172
+ | "worktreePath"
173
+ | "environmentProvider"
174
+ | "environmentLocator"
175
+ | "codeServerPort"
176
+ | "previewPorts"
177
+ >,
178
+ machine: HostMachineIdentity,
179
+ ): TaskEnvironmentLocator | null {
180
+ const provider = task.environmentProvider;
181
+ const raw = task.environmentLocator;
182
+ if ((provider === null) !== (raw === null)) {
183
+ throw new Error("task environment persistence is incomplete");
184
+ }
185
+ if (provider !== null && raw !== null) {
186
+ const locator = parseTaskEnvironmentLocator(raw);
187
+ if (locator.provider !== provider) {
188
+ throw new Error("task environment provider does not match its locator");
189
+ }
190
+ return locator;
191
+ }
192
+ if (task.composeProject === null && task.worktreePath === null) return null;
193
+ if (task.composeProject === null || task.worktreePath === null) {
194
+ throw new Error("legacy task runtime persistence is incomplete");
195
+ }
196
+ throw new Error(
197
+ machine.backend === "docker"
198
+ ? "legacy Docker resources have not passed safe adoption"
199
+ : `legacy task runtime belongs to ambient Docker and cannot move to ${machine.backend}`,
200
+ );
201
+ }
202
+
203
+ export interface TaskEnvironmentBackendVerdict {
204
+ ok: boolean;
205
+ detail?: string;
206
+ }
207
+
208
+ /**
209
+ * Machine/provider switch fence used before provisioning or maintenance. Any
210
+ * row that still names runtime resources belongs to exactly one backend. An
211
+ * unknown/newer provider or endpoint mismatch is a stop signal, never a hint
212
+ * to probe/mutate the currently-selected daemon.
213
+ */
214
+ /** Extract the (backend, endpoint, engineId) triple a locator was pinned to,
215
+ * dispatching by provider. An unknown/newer provider throws. */
216
+ export function locatorMachineIdentity(
217
+ locator: TaskEnvironmentLocator,
218
+ ): HostMachineIdentity {
219
+ if (locator.provider === DOCKER_TASK_ENVIRONMENT_PROVIDER) {
220
+ return parseDockerTaskEnvironmentLocator(locator).machine;
221
+ }
222
+ if (locator.provider === APPLE_TASK_ENVIRONMENT_PROVIDER) {
223
+ return parseAppleTaskEnvironmentLocator(locator).machine;
224
+ }
225
+ throw new Error(
226
+ `belongs to unavailable environment provider ${locator.provider}`,
227
+ );
228
+ }
229
+
230
+ export function persistedTaskEnvironmentsMatchMachine(
231
+ tasks: ReadonlyArray<
232
+ Pick<
233
+ HostTask,
234
+ | "taskId"
235
+ | "composeProject"
236
+ | "worktreePath"
237
+ | "environmentProvider"
238
+ | "environmentLocator"
239
+ | "codeServerPort"
240
+ | "previewPorts"
241
+ >
242
+ >,
243
+ machine: HostMachineIdentity,
244
+ ): TaskEnvironmentBackendVerdict {
245
+ for (const task of tasks) {
246
+ let locator: TaskEnvironmentLocator | null;
247
+ try {
248
+ locator = persistedTaskEnvironmentLocator(task, machine);
249
+ } catch (error) {
250
+ return {
251
+ ok: false,
252
+ detail: `task ${task.taskId}: ${boundedMessage(error)}`,
253
+ };
254
+ }
255
+ if (locator === null) continue;
256
+ try {
257
+ const persisted = locatorMachineIdentity(locator);
258
+ if (
259
+ persisted.backend !== machine.backend ||
260
+ persisted.endpoint !== machine.endpoint ||
261
+ persisted.engineId !== machine.engineId
262
+ ) {
263
+ return {
264
+ ok: false,
265
+ detail: `task ${task.taskId} belongs to a different container backend`,
266
+ };
267
+ }
268
+ } catch (error) {
269
+ return {
270
+ ok: false,
271
+ detail: `task ${task.taskId}: ${boundedMessage(error)}`,
272
+ };
273
+ }
274
+ }
275
+ return { ok: true };
276
+ }
277
+
278
+ export function hostTaskEnvironmentsMatchMachine(
279
+ machine: HostMachineIdentity,
280
+ ): TaskEnvironmentBackendVerdict {
281
+ const rows = getDb().select().from(schema.hostTasks).all();
282
+ return persistedTaskEnvironmentsMatchMachine(rows, machine);
283
+ }
284
+
285
+ export interface DockerMachineSelection {
286
+ backend: HostMachineIdentity["backend"];
287
+ endpoint: string;
288
+ }
289
+
290
+ /** Pre-provision provider fence. The engine identity is not knowable until
291
+ * the backend is activated, so this guard proves only that no durable row
292
+ * belongs to another provider/endpoint. The exact engine-ID fence runs
293
+ * immediately after the atomic daemon probe and before maintenance or
294
+ * recovery. */
295
+ export function hostTaskEnvironmentsAllowMachineSelection(
296
+ machine: DockerMachineSelection,
297
+ ): TaskEnvironmentBackendVerdict {
298
+ const tasks = getDb().select().from(schema.hostTasks).all();
299
+ for (const task of tasks) {
300
+ if (
301
+ (task.environmentProvider === null) !==
302
+ (task.environmentLocator === null)
303
+ ) {
304
+ return {
305
+ ok: false,
306
+ detail: `task ${task.taskId}: task environment persistence is incomplete`,
307
+ };
308
+ }
309
+ if (task.environmentProvider === null) {
310
+ if (task.composeProject === null && task.worktreePath === null) continue;
311
+ return {
312
+ ok: false,
313
+ detail:
314
+ `task ${task.taskId} has legacy ambient-Docker resources; ` +
315
+ "start that Docker daemon and run `uai-host runtime recheck` " +
316
+ "before selecting another container runtime",
317
+ };
318
+ }
319
+ try {
320
+ const locator = parseTaskEnvironmentLocator(task.environmentLocator!);
321
+ if (locator.provider !== task.environmentProvider) {
322
+ throw new Error("task environment provider does not match its locator");
323
+ }
324
+ const persisted = locatorMachineIdentity(locator);
325
+ if (
326
+ persisted.backend !== machine.backend ||
327
+ persisted.endpoint !== machine.endpoint
328
+ ) {
329
+ throw new Error("belongs to a different container backend");
330
+ }
331
+ } catch (error) {
332
+ return {
333
+ ok: false,
334
+ detail: `task ${task.taskId}: ${boundedMessage(error)}`,
335
+ };
336
+ }
337
+ }
338
+ return { ok: true };
339
+ }
340
+
341
+ function requireMachineIdentity(): HostMachineIdentity {
342
+ // The PIN, not the operational-gated identity: reconstruction must serve
343
+ // teardown while the runtime is still `checking` (the 2026-08-16 ring).
344
+ // Locator comparison still demands an exact backend/endpoint/engine match,
345
+ // so nothing loosens about WHICH machine may own a row.
346
+ const identity = pinnedContainerRuntimeMachineIdentity();
347
+ if (identity === null) {
348
+ throw new Error("container runtime does not have a durable machine identity");
349
+ }
350
+ return identity;
351
+ }
352
+
353
+ /** The Docker provider must never operate on another backend's identity. */
354
+ function requireDockerMachineIdentity(): DockerMachineIdentity {
355
+ const identity = requireMachineIdentity();
356
+ if (identity.backend !== "docker") {
357
+ throw new Error("the Docker task backend is not active on this host");
358
+ }
359
+ return identity;
360
+ }
361
+
362
+ function boundedMessage(error: unknown): string {
363
+ return (error instanceof Error ? error.message : String(error)).slice(0, 512);
364
+ }