@runuai/host 0.9.14 → 0.9.43

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 +3051 -200
  53. package/lib/preview-sidecar.ts +68 -14
  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 +956 -0
  70. package/lib/task-environment/index.ts +364 -0
  71. package/lib/task-environment/legacy-adoption.ts +459 -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,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
+ }