@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.
- 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 +33 -2
- 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 +3060 -218
- 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 +1405 -107
- 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 +766 -42
- 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,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict host-local configuration manager for ADR-100.
|
|
3
|
+
*
|
|
4
|
+
* Only the three protocol-owned booleans are accepted. Agent CLI and
|
|
5
|
+
* telemetry switches are persisted in `.env.local`, where they take effect
|
|
6
|
+
* before the next process starts; terminal-history retention is a typed
|
|
7
|
+
* singleton row in host.sqlite. The cloud remains the only authority for the
|
|
8
|
+
* host display name, so it is deliberately absent here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { eq } from "drizzle-orm";
|
|
14
|
+
|
|
15
|
+
import { getDb, schema, type Db } from "./db";
|
|
16
|
+
import {
|
|
17
|
+
readEnvFileSnapshot,
|
|
18
|
+
restoreEnvFileSnapshotAtomic,
|
|
19
|
+
serializeEnvFileMutation,
|
|
20
|
+
writeEnvValuesAtomic,
|
|
21
|
+
} from "./env-file";
|
|
22
|
+
import { env } from "./env";
|
|
23
|
+
import { applyTerminalHistoryRetentionInTransaction } from "./runtime-state";
|
|
24
|
+
import type {
|
|
25
|
+
HostConfigErrorCode,
|
|
26
|
+
HostConfigPatch,
|
|
27
|
+
HostConfigState,
|
|
28
|
+
} from "../src/protocol";
|
|
29
|
+
|
|
30
|
+
export type {
|
|
31
|
+
HostConfigErrorCode,
|
|
32
|
+
HostConfigPatch,
|
|
33
|
+
HostConfigState,
|
|
34
|
+
} from "../src/protocol";
|
|
35
|
+
|
|
36
|
+
export type HostConfigResult =
|
|
37
|
+
| {
|
|
38
|
+
readonly ok: true;
|
|
39
|
+
readonly config: HostConfigState;
|
|
40
|
+
readonly restartRequired: boolean;
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
readonly ok: false;
|
|
44
|
+
readonly errorCode: HostConfigErrorCode;
|
|
45
|
+
readonly message: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const CONFIG_KEYS = [
|
|
49
|
+
"agentCliAutoupdate",
|
|
50
|
+
"telemetryEnabled",
|
|
51
|
+
"retainTerminalHistory",
|
|
52
|
+
] as const satisfies readonly (keyof HostConfigState)[];
|
|
53
|
+
const CONFIG_KEY_SET = new Set<string>(CONFIG_KEYS);
|
|
54
|
+
const SETTINGS_ID = "host";
|
|
55
|
+
|
|
56
|
+
export interface HostConfigSeams {
|
|
57
|
+
readonly getDb: () => Db;
|
|
58
|
+
readonly envFilePath: () => string;
|
|
59
|
+
readonly procEnv: NodeJS.ProcessEnv;
|
|
60
|
+
readonly now: () => number;
|
|
61
|
+
readonly readEnvFileSnapshot: typeof readEnvFileSnapshot;
|
|
62
|
+
readonly restoreEnvFileSnapshotAtomic: typeof restoreEnvFileSnapshotAtomic;
|
|
63
|
+
readonly writeEnvValuesAtomic: typeof writeEnvValuesAtomic;
|
|
64
|
+
readonly serializeEnvFileMutation: typeof serializeEnvFileMutation;
|
|
65
|
+
readonly applyTerminalHistoryRetentionInTransaction: typeof applyTerminalHistoryRetentionInTransaction;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function defaultSeams(): HostConfigSeams {
|
|
69
|
+
return {
|
|
70
|
+
getDb,
|
|
71
|
+
envFilePath: () => join(env.uaiHome, ".env.local"),
|
|
72
|
+
procEnv: process.env,
|
|
73
|
+
now: Date.now,
|
|
74
|
+
readEnvFileSnapshot,
|
|
75
|
+
restoreEnvFileSnapshotAtomic,
|
|
76
|
+
writeEnvValuesAtomic,
|
|
77
|
+
serializeEnvFileMutation,
|
|
78
|
+
applyTerminalHistoryRetentionInTransaction,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The class form makes durability failures testable without weakening the
|
|
84
|
+
* production singleton API below.
|
|
85
|
+
*/
|
|
86
|
+
export class HostConfigManager {
|
|
87
|
+
private readonly bootTelemetryEnabled: boolean;
|
|
88
|
+
|
|
89
|
+
constructor(private readonly seams: HostConfigSeams = defaultSeams()) {
|
|
90
|
+
// The telemetry SDK is initialized before remote config is served. Keep
|
|
91
|
+
// that boot-applied value immutable for this process so GET and repeated
|
|
92
|
+
// PATCH acknowledgements continue to report a pending restart until the
|
|
93
|
+
// service actually restarts (or the durable setting is toggled back).
|
|
94
|
+
this.bootTelemetryEnabled =
|
|
95
|
+
this.seams.procEnv.UAI_TELEMETRY_DISABLED !== "1";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getConfig(): HostConfigResult {
|
|
99
|
+
try {
|
|
100
|
+
const config = this.readEffectiveConfig();
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
config,
|
|
104
|
+
restartRequired:
|
|
105
|
+
config.telemetryEnabled !== this.bootTelemetryEnabled,
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return persistenceFailure();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
patchConfig(patch: unknown): Promise<HostConfigResult> {
|
|
113
|
+
const parsed = parseHostConfigPatch(patch);
|
|
114
|
+
if (!parsed) return Promise.resolve(invalidPatch());
|
|
115
|
+
|
|
116
|
+
const envFile = this.seams.envFilePath();
|
|
117
|
+
return this.seams
|
|
118
|
+
.serializeEnvFileMutation(envFile, () => this.applyPatch(envFile, parsed))
|
|
119
|
+
.catch(() => persistenceFailure());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private readEffectiveConfig(db: Db = this.seams.getDb()): HostConfigState {
|
|
123
|
+
const settings = db
|
|
124
|
+
.select({
|
|
125
|
+
retainTerminalHistory: schema.hostSettings.retainTerminalHistory,
|
|
126
|
+
})
|
|
127
|
+
.from(schema.hostSettings)
|
|
128
|
+
.where(eq(schema.hostSettings.id, SETTINGS_ID))
|
|
129
|
+
.get();
|
|
130
|
+
return {
|
|
131
|
+
// Keep these predicates identical to their current runtime consumers.
|
|
132
|
+
agentCliAutoupdate:
|
|
133
|
+
this.seams.procEnv.UAI_AGENT_CLI_AUTOUPDATE !== "0",
|
|
134
|
+
telemetryEnabled: this.seams.procEnv.UAI_TELEMETRY_DISABLED !== "1",
|
|
135
|
+
retainTerminalHistory: settings?.retainTerminalHistory ?? true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private applyPatch(
|
|
140
|
+
envFile: string,
|
|
141
|
+
patch: HostConfigPatch,
|
|
142
|
+
): HostConfigResult {
|
|
143
|
+
try {
|
|
144
|
+
const db = this.seams.getDb();
|
|
145
|
+
const current = this.readEffectiveConfig(db);
|
|
146
|
+
const desired: HostConfigState = { ...current, ...patch };
|
|
147
|
+
const envUpdates: Record<string, string> = {};
|
|
148
|
+
if (patch.agentCliAutoupdate !== undefined) {
|
|
149
|
+
envUpdates.UAI_AGENT_CLI_AUTOUPDATE = desired.agentCliAutoupdate
|
|
150
|
+
? "1"
|
|
151
|
+
: "0";
|
|
152
|
+
}
|
|
153
|
+
if (patch.telemetryEnabled !== undefined) {
|
|
154
|
+
// The durable kill switch is inverse by design.
|
|
155
|
+
envUpdates.UAI_TELEMETRY_DISABLED = desired.telemetryEnabled ? "0" : "1";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const snapshot =
|
|
159
|
+
Object.keys(envUpdates).length > 0
|
|
160
|
+
? this.seams.readEnvFileSnapshot(envFile)
|
|
161
|
+
: null;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
db.transaction((tx) => {
|
|
165
|
+
if (patch.retainTerminalHistory !== undefined) {
|
|
166
|
+
const now = this.seams.now();
|
|
167
|
+
tx.insert(schema.hostSettings)
|
|
168
|
+
.values({
|
|
169
|
+
id: SETTINGS_ID,
|
|
170
|
+
retainTerminalHistory: desired.retainTerminalHistory,
|
|
171
|
+
updatedAt: now,
|
|
172
|
+
})
|
|
173
|
+
.onConflictDoUpdate({
|
|
174
|
+
target: schema.hostSettings.id,
|
|
175
|
+
set: {
|
|
176
|
+
retainTerminalHistory: desired.retainTerminalHistory,
|
|
177
|
+
updatedAt: now,
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
.run();
|
|
181
|
+
}
|
|
182
|
+
if (patch.retainTerminalHistory === false) {
|
|
183
|
+
// The setting row and every owner-facing terminal-history scrub
|
|
184
|
+
// share one SQLite commit. A crash can leave neither a durable
|
|
185
|
+
// opt-out with stale history nor a scrub with the old setting.
|
|
186
|
+
this.seams.applyTerminalHistoryRetentionInTransaction(tx);
|
|
187
|
+
}
|
|
188
|
+
// better-sqlite3 transactions are synchronous. A failed durable env
|
|
189
|
+
// replacement throws through this callback and rolls the row back.
|
|
190
|
+
this.seams.writeEnvValuesAtomic(envFile, envUpdates);
|
|
191
|
+
});
|
|
192
|
+
} catch {
|
|
193
|
+
// The writer can fail after rename (for example directory fsync). The
|
|
194
|
+
// SQLite transaction has rolled back; restore the exact prior env
|
|
195
|
+
// bytes before returning a negative acknowledgement.
|
|
196
|
+
if (snapshot) {
|
|
197
|
+
try {
|
|
198
|
+
this.seams.restoreEnvFileSnapshotAtomic(envFile, snapshot);
|
|
199
|
+
} catch {
|
|
200
|
+
// Still fail closed. A retry reads effective state and converges;
|
|
201
|
+
// an error response can never claim the patch was committed.
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return persistenceFailure();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Runtime consumers read process.env, so reflect only values that are
|
|
208
|
+
// durable. Telemetry still needs a restart to fully (de)initialize the
|
|
209
|
+
// already-created SDK; the acknowledgement makes that explicit.
|
|
210
|
+
if (patch.agentCliAutoupdate !== undefined) {
|
|
211
|
+
this.seams.procEnv.UAI_AGENT_CLI_AUTOUPDATE =
|
|
212
|
+
envUpdates.UAI_AGENT_CLI_AUTOUPDATE;
|
|
213
|
+
}
|
|
214
|
+
if (patch.telemetryEnabled !== undefined) {
|
|
215
|
+
this.seams.procEnv.UAI_TELEMETRY_DISABLED =
|
|
216
|
+
envUpdates.UAI_TELEMETRY_DISABLED;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const effective = this.readEffectiveConfig(db);
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
config: effective,
|
|
223
|
+
restartRequired:
|
|
224
|
+
effective.telemetryEnabled !== this.bootTelemetryEnabled,
|
|
225
|
+
};
|
|
226
|
+
} catch {
|
|
227
|
+
return persistenceFailure();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Strict runtime validation: exact keys, booleans, and a non-empty patch. */
|
|
233
|
+
export function parseHostConfigPatch(value: unknown): HostConfigPatch | null {
|
|
234
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
235
|
+
try {
|
|
236
|
+
const keys = Object.keys(value);
|
|
237
|
+
if (
|
|
238
|
+
keys.length === 0 ||
|
|
239
|
+
keys.some(
|
|
240
|
+
(key) =>
|
|
241
|
+
!CONFIG_KEY_SET.has(key) ||
|
|
242
|
+
typeof (value as Record<string, unknown>)[key] !== "boolean",
|
|
243
|
+
)
|
|
244
|
+
) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return Object.fromEntries(
|
|
248
|
+
keys.map((key) => [key, (value as Record<string, boolean>)[key]]),
|
|
249
|
+
) as HostConfigPatch;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function invalidPatch(): HostConfigResult {
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
errorCode: "invalid_patch",
|
|
259
|
+
message: "config patch must contain only supported boolean keys",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function persistenceFailure(): HostConfigResult {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
errorCode: "persistence_failed",
|
|
267
|
+
message: "host config could not be persisted",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const manager = new HostConfigManager();
|
|
272
|
+
|
|
273
|
+
export function getHostConfig(): HostConfigResult {
|
|
274
|
+
return manager.getConfig();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function patchHostConfig(patch: unknown): Promise<HostConfigResult> {
|
|
278
|
+
return manager.patchConfig(patch);
|
|
279
|
+
}
|