@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,28 @@
|
|
|
1
|
+
import type { PinnedReleaseManifestKey } from "./release-manifest";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable manifest location for the public, release-artifact-only feed.
|
|
5
|
+
*
|
|
6
|
+
* The release coordinator provisions the first production key in this map in
|
|
7
|
+
* the same reviewed change that enables publishing. Keeping the empty map in
|
|
8
|
+
* source makes an unprovisioned updater fail closed; no network-provided key
|
|
9
|
+
* or environment variable can become a trust anchor.
|
|
10
|
+
*/
|
|
11
|
+
export const STABLE_RELEASE_MANIFEST_URL =
|
|
12
|
+
"https://github.com/runuai/uai-host-releases/releases/latest/download/host-runtime-manifest.json";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Deliberate public-release latch for ADR-099.
|
|
16
|
+
*
|
|
17
|
+
* Provisioning a key is necessary but not sufficient to advertise or serve
|
|
18
|
+
* the one-line installer. The release coordinator flips this only in the same
|
|
19
|
+
* reviewed change that publishes the complete, version-matched four-platform
|
|
20
|
+
* release. Keeping it false while this stack is under review makes accidental
|
|
21
|
+
* key/feed provisioning fail closed.
|
|
22
|
+
*/
|
|
23
|
+
export const HOST_INSTALLER_RELEASE_PROMOTED = false;
|
|
24
|
+
|
|
25
|
+
export const PINNED_RELEASE_MANIFEST_KEYS: ReadonlyMap<
|
|
26
|
+
string,
|
|
27
|
+
PinnedReleaseManifestKey
|
|
28
|
+
> = new Map();
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export type RuntimeActivationAttemptResult = "complete" | "retry";
|
|
2
|
+
|
|
3
|
+
type ActivationAttempt = () => Promise<RuntimeActivationAttemptResult>;
|
|
4
|
+
type RetrySchedule = (callback: () => void, delayMs: number) => () => void;
|
|
5
|
+
|
|
6
|
+
interface RuntimeRecoveryGateOptions {
|
|
7
|
+
isCurrent(): boolean;
|
|
8
|
+
recover(isCurrent: () => boolean): Promise<boolean>;
|
|
9
|
+
runtimeReady(): Promise<boolean>;
|
|
10
|
+
afterRecovery(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Keep the operational publication path strictly behind a true recovery
|
|
14
|
+
* verdict. A false verdict probes the daemon only to choose between normal
|
|
15
|
+
* no-runtime handling and a post-tail retry; it never calls `afterRecovery`. */
|
|
16
|
+
export async function runRuntimeRecoveryGate(
|
|
17
|
+
options: RuntimeRecoveryGateOptions,
|
|
18
|
+
): Promise<RuntimeActivationAttemptResult> {
|
|
19
|
+
const recovered = await options.recover(options.isCurrent);
|
|
20
|
+
if (!options.isCurrent()) return "complete";
|
|
21
|
+
if (!recovered) {
|
|
22
|
+
const ready = await options.runtimeReady();
|
|
23
|
+
return ready && options.isCurrent() ? "retry" : "complete";
|
|
24
|
+
}
|
|
25
|
+
await options.afterRecovery();
|
|
26
|
+
return "complete";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type RuntimeMaintenanceVerdict = "ready" | "retry" | "stale";
|
|
30
|
+
|
|
31
|
+
interface MaintainedRuntimeActivationOptions {
|
|
32
|
+
isCurrent(): boolean;
|
|
33
|
+
/** One-way rolling fence before the first shared-volume writer. */
|
|
34
|
+
beforeMaintenance(): Promise<RuntimeMaintenanceVerdict>;
|
|
35
|
+
maintenance(): Promise<RuntimeMaintenanceVerdict>;
|
|
36
|
+
recover(isCurrent: () => boolean): Promise<boolean>;
|
|
37
|
+
runtimeReady(): Promise<boolean>;
|
|
38
|
+
afterRecoveryBeforeFinalMaintenance(): Promise<void>;
|
|
39
|
+
publishOperational(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Keep both standard-image/shared-volume maintenance passes on the same side
|
|
44
|
+
* of the recovery and operational-publication fences. A live daemon plus a
|
|
45
|
+
* transient maintenance failure requests the activation tail's bounded retry;
|
|
46
|
+
* a stale daemon generation simply completes and lets its readiness edge own
|
|
47
|
+
* the next attempt.
|
|
48
|
+
*/
|
|
49
|
+
export async function runMaintainedRuntimeActivation(
|
|
50
|
+
options: MaintainedRuntimeActivationOptions,
|
|
51
|
+
): Promise<RuntimeActivationAttemptResult> {
|
|
52
|
+
if (!options.isCurrent()) return "complete";
|
|
53
|
+
const prepared = await options.beforeMaintenance();
|
|
54
|
+
if (prepared !== "ready") {
|
|
55
|
+
return prepared === "retry" ? "retry" : "complete";
|
|
56
|
+
}
|
|
57
|
+
if (!options.isCurrent()) return "complete";
|
|
58
|
+
const initial = await options.maintenance();
|
|
59
|
+
if (initial !== "ready") {
|
|
60
|
+
return initial === "retry" ? "retry" : "complete";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let retryAfterRecovery = false;
|
|
64
|
+
const recoveryResult = await runRuntimeRecoveryGate({
|
|
65
|
+
isCurrent: options.isCurrent,
|
|
66
|
+
recover: options.recover,
|
|
67
|
+
runtimeReady: options.runtimeReady,
|
|
68
|
+
afterRecovery: async () => {
|
|
69
|
+
await options.afterRecoveryBeforeFinalMaintenance();
|
|
70
|
+
if (!options.isCurrent()) return;
|
|
71
|
+
const final = await options.maintenance();
|
|
72
|
+
if (final !== "ready") {
|
|
73
|
+
retryAfterRecovery = final === "retry";
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await options.publishOperational();
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
return retryAfterRecovery ? "retry" : recoveryResult;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface RuntimeActivationTailOptions {
|
|
83
|
+
isCurrent(epoch: number): boolean;
|
|
84
|
+
retryDelayMs?: number;
|
|
85
|
+
maxRetries?: number;
|
|
86
|
+
schedule?: RetrySchedule;
|
|
87
|
+
onError?(error: unknown): void;
|
|
88
|
+
onExhausted?(epoch: number, attempts: number): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ActivationInFlight {
|
|
92
|
+
epoch: number;
|
|
93
|
+
promise: Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const defaultSchedule: RetrySchedule = (callback, delayMs) => {
|
|
97
|
+
const timer = setTimeout(callback, delayMs);
|
|
98
|
+
timer.unref?.();
|
|
99
|
+
return () => clearTimeout(timer);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Serialize runtime activation generations and turn a retry verdict into a
|
|
104
|
+
* bounded follow-up attempt. The retry timer is installed from the active
|
|
105
|
+
* promise's `finally`, after the tail has been cleared, so it cannot merely
|
|
106
|
+
* join the attempt that requested it.
|
|
107
|
+
*/
|
|
108
|
+
export class RuntimeActivationTail {
|
|
109
|
+
private readonly isCurrent: (epoch: number) => boolean;
|
|
110
|
+
private readonly retryDelayMs: number;
|
|
111
|
+
private readonly maxRetries: number;
|
|
112
|
+
private readonly schedule: RetrySchedule;
|
|
113
|
+
private readonly onError: (error: unknown) => void;
|
|
114
|
+
private readonly onExhausted: (epoch: number, attempts: number) => void;
|
|
115
|
+
private inFlight: ActivationInFlight | null = null;
|
|
116
|
+
private retryEpoch: number | null = null;
|
|
117
|
+
private retryAttempts = 0;
|
|
118
|
+
private cancelRetry: (() => void) | null = null;
|
|
119
|
+
private exhaustionReported = false;
|
|
120
|
+
private stopped = false;
|
|
121
|
+
|
|
122
|
+
constructor(options: RuntimeActivationTailOptions) {
|
|
123
|
+
this.isCurrent = options.isCurrent;
|
|
124
|
+
this.retryDelayMs = options.retryDelayMs ?? 20_000;
|
|
125
|
+
this.maxRetries = Math.max(0, options.maxRetries ?? 3);
|
|
126
|
+
this.schedule = options.schedule ?? defaultSchedule;
|
|
127
|
+
this.onError = options.onError ?? (() => {});
|
|
128
|
+
this.onExhausted = options.onExhausted ?? (() => {});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
request(epoch: number, attempt: ActivationAttempt): Promise<void> {
|
|
132
|
+
if (this.stopped || !this.isCurrent(epoch)) return Promise.resolve();
|
|
133
|
+
if (this.inFlight?.epoch === epoch) return this.inFlight.promise;
|
|
134
|
+
|
|
135
|
+
this.selectEpoch(epoch);
|
|
136
|
+
// An explicit activation supersedes a delayed retry for the same epoch.
|
|
137
|
+
// Keep the consumed retry count so repeated external nudges cannot reset
|
|
138
|
+
// the bound without creating a new daemon generation.
|
|
139
|
+
this.cancelScheduledRetry();
|
|
140
|
+
|
|
141
|
+
const predecessor = this.inFlight?.promise;
|
|
142
|
+
let result: RuntimeActivationAttemptResult = "complete";
|
|
143
|
+
const run = async (): Promise<void> => {
|
|
144
|
+
if (this.stopped || !this.isCurrent(epoch)) return;
|
|
145
|
+
try {
|
|
146
|
+
result = await attempt();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
result = "retry";
|
|
149
|
+
try {
|
|
150
|
+
this.onError(error);
|
|
151
|
+
} catch {
|
|
152
|
+
// Diagnostics must not break the retry tail.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
let running!: Promise<void>;
|
|
158
|
+
running = (predecessor
|
|
159
|
+
? predecessor.catch(() => {}).then(run)
|
|
160
|
+
: run()
|
|
161
|
+
).finally(() => {
|
|
162
|
+
if (this.inFlight?.promise !== running) return;
|
|
163
|
+
this.inFlight = null;
|
|
164
|
+
if (
|
|
165
|
+
result === "retry" &&
|
|
166
|
+
!this.stopped &&
|
|
167
|
+
this.isCurrent(epoch)
|
|
168
|
+
) {
|
|
169
|
+
this.scheduleRetry(epoch, attempt);
|
|
170
|
+
} else {
|
|
171
|
+
this.resolve(epoch);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
this.inFlight = { epoch, promise: running };
|
|
175
|
+
return running;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Schedule a retry requested outside the activation tail (for example, by
|
|
179
|
+
* reconnect revalidation). An active attempt for the epoch already owns the
|
|
180
|
+
* verdict and will schedule its own retry if needed. */
|
|
181
|
+
scheduleRetry(epoch: number, attempt: ActivationAttempt): boolean {
|
|
182
|
+
if (this.stopped || !this.isCurrent(epoch)) return false;
|
|
183
|
+
if (this.inFlight?.epoch === epoch) return true;
|
|
184
|
+
this.selectEpoch(epoch);
|
|
185
|
+
if (this.cancelRetry) return true;
|
|
186
|
+
if (this.retryAttempts >= this.maxRetries) {
|
|
187
|
+
if (!this.exhaustionReported) {
|
|
188
|
+
this.exhaustionReported = true;
|
|
189
|
+
this.onExhausted(epoch, this.retryAttempts);
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.retryAttempts += 1;
|
|
195
|
+
let cancel!: () => void;
|
|
196
|
+
cancel = this.schedule(() => {
|
|
197
|
+
if (this.cancelRetry !== cancel) return;
|
|
198
|
+
this.cancelRetry = null;
|
|
199
|
+
if (this.stopped || !this.isCurrent(epoch)) return;
|
|
200
|
+
void this.request(epoch, attempt);
|
|
201
|
+
}, this.retryDelayMs);
|
|
202
|
+
this.cancelRetry = cancel;
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Clear retry bookkeeping after another path proves the epoch operational. */
|
|
207
|
+
resolve(epoch: number): void {
|
|
208
|
+
if (this.retryEpoch !== epoch) return;
|
|
209
|
+
this.cancelScheduledRetry();
|
|
210
|
+
this.retryEpoch = null;
|
|
211
|
+
this.retryAttempts = 0;
|
|
212
|
+
this.exhaustionReported = false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
stop(): void {
|
|
216
|
+
this.stopped = true;
|
|
217
|
+
this.cancelScheduledRetry();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private selectEpoch(epoch: number): void {
|
|
221
|
+
if (this.retryEpoch === epoch) return;
|
|
222
|
+
this.cancelScheduledRetry();
|
|
223
|
+
this.retryEpoch = epoch;
|
|
224
|
+
this.retryAttempts = 0;
|
|
225
|
+
this.exhaustionReported = false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private cancelScheduledRetry(): void {
|
|
229
|
+
this.cancelRetry?.();
|
|
230
|
+
this.cancelRetry = null;
|
|
231
|
+
}
|
|
232
|
+
}
|