@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.
- package/README.md +22 -5
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/meta/_journal.json +21 -0
- package/db/schema.ts +80 -30
- package/images/standard/Dockerfile +36 -10
- package/images/standard/README.md +63 -18
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +308 -38
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/lib/agent-cli.ts +69 -4
- package/lib/agent.ts +46 -7
- package/lib/agents/claude.ts +13 -8
- package/lib/agents/codex.ts +11 -6
- package/lib/agents/cursor.ts +39 -29
- package/lib/agents/durable-proc.ts +20 -27
- package/lib/agents/factory.ts +9 -25
- package/lib/agents/grok.ts +43 -30
- package/lib/agents/kimi.ts +44 -29
- package/lib/agents/opencode.ts +43 -31
- package/lib/agents/proc.ts +149 -114
- package/lib/agents/transport.ts +62 -50
- package/lib/agents/types.ts +6 -4
- package/lib/apple-runtime-recycle.ts +236 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/browser-testing.ts +233 -93
- package/lib/codex-auth.ts +40 -6
- package/lib/command-db.ts +20 -0
- package/lib/container-runtime.ts +1338 -0
- package/lib/db.ts +1 -0
- package/lib/docker-exec.ts +87 -5
- package/lib/engine-accounts.ts +68 -5
- package/lib/engine-login.ts +1952 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +155 -0
- package/lib/env.ts +4 -0
- package/lib/git-diff.ts +98 -32
- package/lib/git-identity.ts +199 -87
- package/lib/github-tokens.ts +202 -91
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-logs.ts +962 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +49 -0
- package/lib/managed-runtime.ts +3644 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-gateway.ts +450 -23
- package/lib/orchestrator.ts +3070 -223
- package/lib/preview-sidecar.ts +57 -13
- package/lib/release-manifest.ts +708 -0
- package/lib/release-trust.ts +28 -0
- package/lib/runtime-activation-tail.ts +232 -0
- package/lib/runtime-archive.ts +1086 -0
- package/lib/runtime-authority.ts +79 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-state.ts +232 -12
- package/lib/skills.ts +24 -3
- package/lib/ssh.ts +18 -0
- package/lib/standard-image.ts +1104 -141
- package/lib/stopped-task-status-queue.ts +44 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-diff.ts +66 -46
- package/lib/task-environment/apple-container.ts +757 -0
- package/lib/task-environment/docker.ts +945 -0
- package/lib/task-environment/index.ts +364 -0
- package/lib/task-environment/legacy-adoption.ts +443 -0
- package/lib/task-environment/registry.ts +58 -0
- package/lib/task-environment/types.ts +408 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +585 -0
- package/lib/tunnel-registry.ts +135 -19
- package/lib/tunnel-runtime.ts +235 -0
- package/package.json +1 -1
- package/scripts/agent/_common.sh +123 -3
- package/scripts/agent/task-down.sh +146 -38
- package/scripts/agent/task-status.sh +19 -3
- package/scripts/agent/task-up.sh +1463 -109
- package/scripts/install/darwin.ts +848 -50
- package/scripts/install/linux.ts +838 -35
- package/scripts/install/types.ts +43 -0
- package/scripts/install/util.ts +215 -8
- package/scripts/install/win.ts +12 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/cli.ts +1464 -72
- package/src/event-outbox.ts +83 -4
- package/src/index.ts +871 -50
- package/src/main.ts +1398 -255
- package/src/paths.ts +17 -1
- package/src/protocol.ts +695 -1
- package/src/runtime-bootstrap.ts +165 -0
- package/src/ui/server.ts +46 -10
- package/src/ui/types.ts +37 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** ADR-106 boot wiring kept side-effect free and directly testable. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
appleContainerRuntimeBinding,
|
|
5
|
+
containerRuntimeMachineIdentity,
|
|
6
|
+
rejectContainerRuntimeSelection,
|
|
7
|
+
reprobeContainerRuntimeMachineIdentity,
|
|
8
|
+
setAppleContainerRuntime,
|
|
9
|
+
setAppleContainerSelectionGuard,
|
|
10
|
+
type AppleRuntimeBinding,
|
|
11
|
+
} from "../lib/container-runtime";
|
|
12
|
+
import {
|
|
13
|
+
resolveBundledRuntimeFromEnvironment,
|
|
14
|
+
type ResolvedBundledRuntime,
|
|
15
|
+
} from "../lib/managed-runtime";
|
|
16
|
+
import {
|
|
17
|
+
adoptLegacyTaskEnvironments,
|
|
18
|
+
hostTaskEnvironmentsAllowMachineSelection,
|
|
19
|
+
hostTaskEnvironmentsMatchMachine,
|
|
20
|
+
setAppleContainerBinding,
|
|
21
|
+
type HostMachineIdentity,
|
|
22
|
+
type LegacyTaskEnvironmentAdoptionVerdict,
|
|
23
|
+
type TaskEnvironmentBackendVerdict,
|
|
24
|
+
} from "../lib/task-environment";
|
|
25
|
+
|
|
26
|
+
export interface RuntimeBootstrapDeps {
|
|
27
|
+
readonly resolveRuntime?: () => Promise<ResolvedBundledRuntime | null>;
|
|
28
|
+
readonly registerRuntime?: (binding: AppleRuntimeBinding | null) => void;
|
|
29
|
+
readonly registerSelectionGuard?: (
|
|
30
|
+
guard: ((endpoint: string) => boolean | Promise<boolean>) | null,
|
|
31
|
+
) => void;
|
|
32
|
+
readonly allowMachineSelection?: (machine: {
|
|
33
|
+
backend: "apple-container";
|
|
34
|
+
endpoint: string;
|
|
35
|
+
}) => TaskEnvironmentBackendVerdict;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register the bundled Apple container toolchain from the active signed
|
|
40
|
+
* generation, if one exists (ADR-106). Registration is fail-closed: seams are
|
|
41
|
+
* cleared first so a thrown validation cannot leave a stale binding, and the
|
|
42
|
+
* selection guard keeps `auto` off the bundled runtime while durable task
|
|
43
|
+
* rows still belong to another backend. An unmanaged checkout, Linux archive,
|
|
44
|
+
* or rolling v1 generation registers nothing; there is deliberately no PATH
|
|
45
|
+
* or inferred-layout fallback.
|
|
46
|
+
*/
|
|
47
|
+
export async function configureBundledContainerRuntime(
|
|
48
|
+
deps: RuntimeBootstrapDeps = {},
|
|
49
|
+
): Promise<ResolvedBundledRuntime | null> {
|
|
50
|
+
const registerRuntime = deps.registerRuntime ?? setAppleContainerRuntime;
|
|
51
|
+
const registerGuard =
|
|
52
|
+
deps.registerSelectionGuard ?? setAppleContainerSelectionGuard;
|
|
53
|
+
registerRuntime(null);
|
|
54
|
+
registerGuard(null);
|
|
55
|
+
|
|
56
|
+
const bundled = await (
|
|
57
|
+
deps.resolveRuntime ?? resolveBundledRuntimeFromEnvironment
|
|
58
|
+
)();
|
|
59
|
+
if (bundled === null) return null;
|
|
60
|
+
|
|
61
|
+
const allowSelection =
|
|
62
|
+
deps.allowMachineSelection ?? hostTaskEnvironmentsAllowMachineSelection;
|
|
63
|
+
registerGuard(
|
|
64
|
+
(endpoint) =>
|
|
65
|
+
allowSelection({ backend: "apple-container", endpoint }).ok,
|
|
66
|
+
);
|
|
67
|
+
registerRuntime({
|
|
68
|
+
containerCliPath: bundled.containerCliPath,
|
|
69
|
+
kernelPath: bundled.kernelPath,
|
|
70
|
+
});
|
|
71
|
+
return bundled;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface RuntimeCompatibilityDeps {
|
|
75
|
+
readonly machineIdentity?: () => HostMachineIdentity | null;
|
|
76
|
+
readonly matchMachine?: (
|
|
77
|
+
machine: HostMachineIdentity,
|
|
78
|
+
) => TaskEnvironmentBackendVerdict;
|
|
79
|
+
readonly adoptLegacy?: (
|
|
80
|
+
machine: HostMachineIdentity,
|
|
81
|
+
) => Promise<LegacyTaskEnvironmentAdoptionVerdict>;
|
|
82
|
+
readonly reprobeRuntime?: () => Promise<HostMachineIdentity | null>;
|
|
83
|
+
readonly rejectSelection?: (message: string, action?: string) => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Final database-backed fence before maintenance/recovery touches a selected
|
|
88
|
+
* daemon. A missing exact endpoint and every malformed/newer/mismatched task
|
|
89
|
+
* locator are quarantines, never retry hints against the current machine.
|
|
90
|
+
*/
|
|
91
|
+
export async function selectedRuntimeMatchesPersistedTaskEnvironments(
|
|
92
|
+
deps: RuntimeCompatibilityDeps = {},
|
|
93
|
+
): Promise<boolean> {
|
|
94
|
+
const reject = deps.rejectSelection ?? rejectContainerRuntimeSelection;
|
|
95
|
+
const machine = (
|
|
96
|
+
deps.machineIdentity ?? containerRuntimeMachineIdentity
|
|
97
|
+
)();
|
|
98
|
+
if (machine === null) {
|
|
99
|
+
reject(
|
|
100
|
+
"The selected container runtime has no durable local machine identity.",
|
|
101
|
+
);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const reprobe =
|
|
105
|
+
deps.reprobeRuntime ?? reprobeContainerRuntimeMachineIdentity;
|
|
106
|
+
const liveMachine = await reprobe();
|
|
107
|
+
if (
|
|
108
|
+
liveMachine === null ||
|
|
109
|
+
liveMachine.backend !== machine.backend ||
|
|
110
|
+
liveMachine.endpoint !== machine.endpoint ||
|
|
111
|
+
liveMachine.engineId !== machine.engineId
|
|
112
|
+
) {
|
|
113
|
+
reject(
|
|
114
|
+
"The selected container runtime daemon identity changed during validation.",
|
|
115
|
+
);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
// Legacy compose rows can only ever be adopted by a Docker daemon; any
|
|
119
|
+
// other active backend makes the candidate check inside adoption fail
|
|
120
|
+
// closed with its repair guidance, so the machine is narrowed lazily.
|
|
121
|
+
// Bind (or unbind) the Apple provider to the proven identity before any
|
|
122
|
+
// maintenance can reconstruct a task environment. The binding carries the
|
|
123
|
+
// exact machine the fence just re-probed, so provider reconstruction and
|
|
124
|
+
// this admission decision can never disagree about identity.
|
|
125
|
+
if (deps.machineIdentity === undefined) {
|
|
126
|
+
const binding = appleContainerRuntimeBinding();
|
|
127
|
+
if (machine.backend === "apple-container" && binding !== null) {
|
|
128
|
+
const appleMachine = {
|
|
129
|
+
backend: "apple-container" as const,
|
|
130
|
+
endpoint: machine.endpoint,
|
|
131
|
+
engineId: machine.engineId,
|
|
132
|
+
};
|
|
133
|
+
setAppleContainerBinding({
|
|
134
|
+
containerCliPath: () => binding.containerCliPath,
|
|
135
|
+
machineIdentity: () => appleMachine,
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
setAppleContainerBinding(null);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const adoption = await (
|
|
143
|
+
deps.adoptLegacy ??
|
|
144
|
+
((selected: HostMachineIdentity) =>
|
|
145
|
+
adoptLegacyTaskEnvironments(selected, {
|
|
146
|
+
reprobeMachine: reprobe,
|
|
147
|
+
}))
|
|
148
|
+
)(machine);
|
|
149
|
+
if (!adoption.ok) {
|
|
150
|
+
reject(
|
|
151
|
+
"Uai Host could not safely adopt persisted legacy task resources" +
|
|
152
|
+
(adoption.detail ? `: ${adoption.detail}` : "."),
|
|
153
|
+
);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const verdict = (
|
|
157
|
+
deps.matchMachine ?? hostTaskEnvironmentsMatchMachine
|
|
158
|
+
)(machine);
|
|
159
|
+
if (verdict.ok) return true;
|
|
160
|
+
reject(
|
|
161
|
+
"The selected container runtime does not match persisted task environments" +
|
|
162
|
+
(verdict.detail ? `: ${verdict.detail}` : "."),
|
|
163
|
+
);
|
|
164
|
+
return false;
|
|
165
|
+
}
|
package/src/ui/server.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { getOrchestrator } from "../../lib/orchestrator";
|
|
|
29
29
|
import { parsePreviewPortRuntimes } from "../../lib/preview-ports";
|
|
30
30
|
import { getCloudState } from "../../lib/cloud-state";
|
|
31
31
|
import { dockerCli } from "../../lib/docker-exec";
|
|
32
|
+
import type { ContainerRuntimeState } from "../../lib/container-runtime";
|
|
32
33
|
import {
|
|
33
34
|
connectEngine,
|
|
34
35
|
disconnectEngine,
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
import { ensureStandardImage } from "../../lib/standard-image";
|
|
48
49
|
import {
|
|
49
50
|
CloudResponse,
|
|
51
|
+
ContainerRuntimeResponse,
|
|
50
52
|
EngineOpResponse,
|
|
51
53
|
EnginesResponse,
|
|
52
54
|
EventsResponse,
|
|
@@ -58,6 +60,7 @@ import {
|
|
|
58
60
|
|
|
59
61
|
const HOST = "127.0.0.1";
|
|
60
62
|
const MAX_PORT_ATTEMPTS = 10;
|
|
63
|
+
export const RUNTIME_RECHECK_CONTROL_HEADER = "runtime-recheck-v1";
|
|
61
64
|
|
|
62
65
|
export interface UiServerOptions {
|
|
63
66
|
/** Host runtime DB (read-only here). */
|
|
@@ -93,6 +96,12 @@ export interface UiServerOptions {
|
|
|
93
96
|
* endpoint never spawns a login shell (which is bounded, but real, I/O).
|
|
94
97
|
*/
|
|
95
98
|
engineSeams?: Partial<EngineSeams>;
|
|
99
|
+
/** ADR-101 local control/read surface. Optional for embedding/tests. */
|
|
100
|
+
runtimeState?: () => ContainerRuntimeState;
|
|
101
|
+
recheckRuntime?: () => Promise<ContainerRuntimeState>;
|
|
102
|
+
/** False while the selected backend is unavailable or still recovering.
|
|
103
|
+
* Credential edits remain usable, but must not kick Docker maintenance. */
|
|
104
|
+
containersOperational?: () => boolean;
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
export interface UiServerHandle {
|
|
@@ -179,6 +188,26 @@ async function handle(
|
|
|
179
188
|
return await handleEngineAccountRemove(req, res, opts);
|
|
180
189
|
case "/api/tasks/stop":
|
|
181
190
|
return await handleTaskStop(req, res, opts);
|
|
191
|
+
case "/api/runtime/recheck": {
|
|
192
|
+
// Loopback is not a browser CSRF boundary. Requiring a non-simple
|
|
193
|
+
// header is deliberate even though its value is not a secret:
|
|
194
|
+
// hostile pages cannot attach it without a CORS preflight, and this
|
|
195
|
+
// server grants no CORS permission. The local CLI is the caller.
|
|
196
|
+
if (
|
|
197
|
+
req.headers["x-uai-local-control"] !==
|
|
198
|
+
RUNTIME_RECHECK_CONTROL_HEADER
|
|
199
|
+
) {
|
|
200
|
+
return sendError(res, 403, "local control header required");
|
|
201
|
+
}
|
|
202
|
+
if (!opts.recheckRuntime) {
|
|
203
|
+
return sendError(res, 501, "runtime recheck is unavailable");
|
|
204
|
+
}
|
|
205
|
+
return sendJson(
|
|
206
|
+
res,
|
|
207
|
+
ContainerRuntimeResponse,
|
|
208
|
+
await opts.recheckRuntime(),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
182
211
|
}
|
|
183
212
|
return sendError(res, 404, `no such endpoint: ${path}`);
|
|
184
213
|
}
|
|
@@ -196,6 +225,7 @@ async function handle(
|
|
|
196
225
|
hostName: hostname(),
|
|
197
226
|
hostId: opts.hostId,
|
|
198
227
|
agentMode: agentMode(),
|
|
228
|
+
containerRuntime: opts.runtimeState?.(),
|
|
199
229
|
});
|
|
200
230
|
case "/api/cloud":
|
|
201
231
|
return sendJson(res, CloudResponse, getCloudState());
|
|
@@ -320,14 +350,16 @@ async function handleEngineConnect(
|
|
|
320
350
|
// Re-advertise so the cloud picker offers the engine; rebuild the image so
|
|
321
351
|
// an optional engine's CLI (kimi/grok/cursor) is installed. Best-effort.
|
|
322
352
|
opts.readvertise?.();
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
353
|
+
if (opts.containersOperational?.() !== false) {
|
|
354
|
+
void ensureStandardImage();
|
|
355
|
+
// A re-login only reaches a RUNNING task once its agents re-spawn — the
|
|
356
|
+
// credential is injected at spawn (Claude's token via `docker exec -e`).
|
|
357
|
+
// Refresh running agents of this kind so a reconnect self-heals tasks
|
|
358
|
+
// stuck on a revoked/expired token, instead of needing Stop/Resume.
|
|
359
|
+
void getOrchestrator()
|
|
360
|
+
.refreshEngineAccountAgents(kind)
|
|
361
|
+
.catch(() => {});
|
|
362
|
+
}
|
|
331
363
|
}
|
|
332
364
|
emit({ done: true, ok: result.ok, message: result.message });
|
|
333
365
|
res.end();
|
|
@@ -370,7 +402,9 @@ async function handleEngineAccountAdd(
|
|
|
370
402
|
const result = addEngineAccount(kind, label, { apiKey, token });
|
|
371
403
|
if (result.ok) {
|
|
372
404
|
opts.readvertise?.();
|
|
373
|
-
|
|
405
|
+
if (opts.containersOperational?.() !== false) {
|
|
406
|
+
void ensureStandardImage();
|
|
407
|
+
}
|
|
374
408
|
}
|
|
375
409
|
return sendJson(res, EngineOpResponse, {
|
|
376
410
|
ok: result.ok,
|
|
@@ -424,7 +458,9 @@ async function handleEngineAccountConnect(
|
|
|
424
458
|
}
|
|
425
459
|
if (result.ok) {
|
|
426
460
|
opts.readvertise?.();
|
|
427
|
-
|
|
461
|
+
if (opts.containersOperational?.() !== false) {
|
|
462
|
+
void ensureStandardImage();
|
|
463
|
+
}
|
|
428
464
|
}
|
|
429
465
|
emit({ done: true, ok: result.ok, message: result.message });
|
|
430
466
|
res.end();
|
package/src/ui/types.ts
CHANGED
|
@@ -8,6 +8,42 @@
|
|
|
8
8
|
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
|
|
11
|
+
const RuntimePreference = z.enum(["auto", "docker", "apple-container"]);
|
|
12
|
+
export const ContainerRuntimeResponse = z.union([
|
|
13
|
+
z
|
|
14
|
+
.object({
|
|
15
|
+
status: z.literal("checking"),
|
|
16
|
+
preference: RuntimePreference,
|
|
17
|
+
detail: z.string().trim().min(1).max(300).optional(),
|
|
18
|
+
})
|
|
19
|
+
.strict(),
|
|
20
|
+
z.union([
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
23
|
+
status: z.literal("ready"),
|
|
24
|
+
preference: z.enum(["auto", "docker"]),
|
|
25
|
+
provider: z.literal("docker"),
|
|
26
|
+
})
|
|
27
|
+
.strict(),
|
|
28
|
+
z
|
|
29
|
+
.object({
|
|
30
|
+
status: z.literal("ready"),
|
|
31
|
+
preference: z.enum(["auto", "apple-container"]),
|
|
32
|
+
provider: z.literal("apple-container"),
|
|
33
|
+
})
|
|
34
|
+
.strict(),
|
|
35
|
+
]),
|
|
36
|
+
z
|
|
37
|
+
.object({
|
|
38
|
+
status: z.literal("no-runtime"),
|
|
39
|
+
preference: RuntimePreference,
|
|
40
|
+
message: z.string().trim().min(1).max(1_000),
|
|
41
|
+
action: z.string().trim().min(1).max(200),
|
|
42
|
+
})
|
|
43
|
+
.strict(),
|
|
44
|
+
]);
|
|
45
|
+
export type ContainerRuntimeResponse = z.infer<typeof ContainerRuntimeResponse>;
|
|
46
|
+
|
|
11
47
|
// GET /api/status — process + config facts.
|
|
12
48
|
export const StatusResponse = z.object({
|
|
13
49
|
pid: z.number().int(),
|
|
@@ -22,6 +58,7 @@ export const StatusResponse = z.object({
|
|
|
22
58
|
// such field — `npm i -g` swaps the CLI before `uai-host restart` swaps the
|
|
23
59
|
// service, so that window is normal, not exotic.
|
|
24
60
|
agentMode: z.enum(["real", "mock"]).optional(),
|
|
61
|
+
containerRuntime: ContainerRuntimeResponse.optional(),
|
|
25
62
|
});
|
|
26
63
|
export type StatusResponse = z.infer<typeof StatusResponse>;
|
|
27
64
|
|