@runuai/host 0.9.148 → 0.9.150
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/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/meta/_journal.json +21 -0
- package/db/schema.ts +312 -2
- package/images/standard/Dockerfile +44 -6
- package/images/standard/container/uai-init +85 -2
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/lib/agent-cli.ts +18 -1
- package/lib/agent.ts +99 -34
- package/lib/agents/codex.ts +9 -1
- package/lib/agents/cursor.ts +15 -1
- package/lib/agents/grok.ts +8 -0
- package/lib/agents/kimi.ts +1 -0
- package/lib/agents/machine-durable.ts +2 -0
- package/lib/agents/opencode.ts +1 -0
- package/lib/agents/proc.ts +6 -1
- package/lib/agents/transport.ts +9 -5
- package/lib/browser-testing.ts +6 -0
- package/lib/codex-auth.ts +2 -0
- package/lib/credential-store.ts +141 -6
- package/lib/engine-accounts.ts +1 -0
- package/lib/env-file.ts +7 -0
- package/lib/env-schema.ts +841 -51
- package/lib/git-diff.ts +1 -0
- package/lib/git-identity.ts +2 -0
- package/lib/github-tokens.ts +1 -0
- package/lib/host-env.ts +98 -18
- package/lib/lease.ts +11 -3
- package/lib/machine-gateway-tunnel.ts +195 -17
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/managed-runtime.ts +270 -51
- package/lib/mcp-gateway.ts +777 -26
- package/lib/orchestrator.ts +408 -81
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-loader.ts +237 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/runtime-archive.ts +79 -16
- package/lib/runtime-authority.ts +44 -0
- package/lib/runtime-state.ts +175 -10
- package/lib/secrets-proxy/address.ts +72 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +60 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +141 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +574 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/shared-files-s3.ts +3 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +51 -3
- package/lib/standard-image.ts +50 -12
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +193 -0
- package/lib/task-environment/apple-container.ts +45 -11
- package/lib/task-environment/docker.ts +51 -12
- package/lib/task-environment/index.ts +6 -2
- package/lib/task-environment/machine-task-up.ts +258 -25
- package/lib/task-environment/machine.ts +80 -22
- package/lib/task-environment/registry.ts +3 -2
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +92 -16
- package/lib/task-environment/types.ts +63 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +44 -0
- package/lib/task-gateway-state.ts +497 -0
- package/lib/transcript.ts +1 -0
- package/package.json +13 -1
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/task-up.sh +476 -27
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/src/cli.ts +26 -3
- package/src/index.ts +273 -19
- package/src/main.ts +939 -48
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +281 -0
- package/src/project-env-wording.ts +47 -0
- package/src/protocol.ts +2219 -15
|
@@ -28,13 +28,38 @@ import type {
|
|
|
28
28
|
TaskUpResult,
|
|
29
29
|
} from "../agent";
|
|
30
30
|
import { injectCodexIntoMachine } from "../codex-auth";
|
|
31
|
+
import {
|
|
32
|
+
ensureMachineGatewayTunnelReady,
|
|
33
|
+
MACHINE_GATEWAY_TUNNEL_PORT,
|
|
34
|
+
stopMachineGatewayTunnel,
|
|
35
|
+
} from "../machine-gateway-tunnel";
|
|
36
|
+
import {
|
|
37
|
+
installMachineProjectEnvLoader,
|
|
38
|
+
installMachineTaskForwarderAsset,
|
|
39
|
+
} from "../machine-project-env-loader";
|
|
40
|
+
import {
|
|
41
|
+
MACHINE_RUNTIME_AUTHORITY_ENV,
|
|
42
|
+
PROJECT_ENV_HOOK_PATH,
|
|
43
|
+
} from "../runtime-authority";
|
|
31
44
|
import { sharedRoot } from "../shared-files";
|
|
45
|
+
import { launchTaskEnvForwarder } from "../task-env-forwarder";
|
|
46
|
+
import { MAX_ENV_SCHEMA_BYTES } from "../env-schema";
|
|
47
|
+
import {
|
|
48
|
+
captureTaskEnvSchemaFromClone,
|
|
49
|
+
captureTaskEnvSchemas,
|
|
50
|
+
taskEnvSchemaObservation,
|
|
51
|
+
type RemoteGit,
|
|
52
|
+
} from "../project-env-schema-capture";
|
|
53
|
+
import { activeSecretsProxy } from "../secrets-proxy/host";
|
|
54
|
+
import { reconcileSidecarTaskProxy } from "../secrets-proxy/task-trust";
|
|
55
|
+
import { TASK_FORWARDER_SECRET_BYTES } from "../task-gateway-protocol";
|
|
32
56
|
import {
|
|
33
57
|
MACHINE_TASK_ENVIRONMENT_PROVIDER,
|
|
34
58
|
parseMachineTaskEnvironmentLocator,
|
|
35
59
|
type MachineTaskInput,
|
|
36
60
|
} from "./machine";
|
|
37
61
|
import type {
|
|
62
|
+
ProjectEnvMode,
|
|
38
63
|
TaskEnvironmentDescriptor,
|
|
39
64
|
TaskEnvironmentHandle,
|
|
40
65
|
TaskEnvironmentProvider,
|
|
@@ -75,6 +100,51 @@ export interface MachineHostTaskUpDeps {
|
|
|
75
100
|
workspacePath: string,
|
|
76
101
|
task: { ownerOrgId?: string; ownerUserId: string; sharedFiles?: string },
|
|
77
102
|
) => Promise<string | null>;
|
|
103
|
+
/** Test seam for the host-side persistent reverse-tunnel supervisor. */
|
|
104
|
+
ensureGatewayTunnel?: typeof ensureMachineGatewayTunnelReady;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function startMachineTaskForwarder(
|
|
108
|
+
taskId: string,
|
|
109
|
+
handle: TaskEnvironmentHandle<unknown>,
|
|
110
|
+
credentials: TaskUpCredentials,
|
|
111
|
+
ensureGatewayTunnel: typeof ensureMachineGatewayTunnelReady,
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
const gatewayTunnelSshArgs = handle.gatewayTunnelSshArgs?.bind(handle);
|
|
114
|
+
if (!gatewayTunnelSshArgs) {
|
|
115
|
+
// The sidecar gateway is in this root process and assigns loopback
|
|
116
|
+
// traffic to its one fixed task. Binding another listener would collide.
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const identity = credentials.taskGateway;
|
|
120
|
+
if (
|
|
121
|
+
!identity ||
|
|
122
|
+
!/^[A-Za-z0-9_-]{16,64}$/.test(identity.generation) ||
|
|
123
|
+
identity.secret.byteLength !== TASK_FORWARDER_SECRET_BYTES
|
|
124
|
+
) {
|
|
125
|
+
throw new Error("machine task env gateway identity is unavailable");
|
|
126
|
+
}
|
|
127
|
+
await installMachineTaskForwarderAsset(handle);
|
|
128
|
+
|
|
129
|
+
await launchTaskEnvForwarder(
|
|
130
|
+
handle,
|
|
131
|
+
{ taskId, generation: identity.generation },
|
|
132
|
+
Uint8Array.from(identity.secret),
|
|
133
|
+
{
|
|
134
|
+
gatewayHost: "127.0.0.1",
|
|
135
|
+
gatewayPort: MACHINE_GATEWAY_TUNNEL_PORT,
|
|
136
|
+
nodePath: "/usr/bin/node",
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Start this during task-up rather than waiting for the first agent. The
|
|
141
|
+
// editor can open a terminal before any roster process exists, and its
|
|
142
|
+
// first command must already be able to refresh through the forwarder.
|
|
143
|
+
await ensureGatewayTunnel(
|
|
144
|
+
taskId,
|
|
145
|
+
{ gatewayTunnelSshArgs },
|
|
146
|
+
MACHINE_GATEWAY_TUNNEL_PORT,
|
|
147
|
+
);
|
|
78
148
|
}
|
|
79
149
|
|
|
80
150
|
/** Mirrors src/index.ts isGithubRepo — host-side predicate only. */
|
|
@@ -129,6 +199,7 @@ async function execWithStdin(
|
|
|
129
199
|
): Promise<StepResult> {
|
|
130
200
|
const proc = await handle.spawn({
|
|
131
201
|
argv,
|
|
202
|
+
projectEnvProfile: "maintenance",
|
|
132
203
|
inheritEnv: [],
|
|
133
204
|
env: { GIT_TERMINAL_PROMPT: "0" },
|
|
134
205
|
timeoutMs,
|
|
@@ -159,6 +230,7 @@ async function execStep(
|
|
|
159
230
|
): Promise<StepResult> {
|
|
160
231
|
const result = await handle.exec({
|
|
161
232
|
argv,
|
|
233
|
+
projectEnvProfile: "maintenance",
|
|
162
234
|
env: { GIT_TERMINAL_PROMPT: "0" },
|
|
163
235
|
timeoutMs,
|
|
164
236
|
maxOutputBytes: STEP_OUTPUT_BYTES,
|
|
@@ -175,15 +247,23 @@ async function execStep(
|
|
|
175
247
|
const CREDENTIALED_CLONE_SCRIPT =
|
|
176
248
|
'IFS= read -r UAI_GIT_TOKEN && export UAI_GIT_TOKEN GIT_ASKPASS=/usr/local/bin/uai-git-askpass GIT_TERMINAL_PROMPT=0 && exec git clone -- "$1" "$2"';
|
|
177
249
|
|
|
250
|
+
type ProjectEnvSchemaObservation = NonNullable<TaskUpResult["envSchemas"]>[number];
|
|
251
|
+
|
|
252
|
+
/** What a project's clone step did: cloned it now, found the task's own
|
|
253
|
+
* clone already there (a resume), or could not clone it; and what to say. */
|
|
254
|
+
interface CloneOutcome {
|
|
255
|
+
clone: "fresh" | "existing" | "failed";
|
|
256
|
+
warning: string | null;
|
|
257
|
+
}
|
|
258
|
+
|
|
178
259
|
async function cloneProject(
|
|
179
260
|
handle: TaskEnvironmentHandle<unknown>,
|
|
180
|
-
|
|
261
|
+
destination: string,
|
|
181
262
|
project: { slug: string; repoUrl: string },
|
|
182
263
|
taskBranch: string,
|
|
183
264
|
owner: { name?: string; email?: string },
|
|
184
265
|
token: string | null,
|
|
185
|
-
): Promise<
|
|
186
|
-
const destination = `${workspacePath}/${projectDirectoryName(project.slug)}`;
|
|
266
|
+
): Promise<CloneOutcome> {
|
|
187
267
|
const url = machineCloneUrl(project.repoUrl);
|
|
188
268
|
const useToken = token !== null && isGithubRepoUrl(project.repoUrl);
|
|
189
269
|
|
|
@@ -194,7 +274,7 @@ async function cloneProject(
|
|
|
194
274
|
["/bin/sh", "-c", 'test -e "$1/.git"', "check", destination],
|
|
195
275
|
GIT_STEP_TIMEOUT_MS,
|
|
196
276
|
);
|
|
197
|
-
if (already.exitCode === 0) return null;
|
|
277
|
+
if (already.exitCode === 0) return { clone: "existing", warning: null };
|
|
198
278
|
|
|
199
279
|
const clone = useToken
|
|
200
280
|
? await execWithStdin(
|
|
@@ -214,13 +294,15 @@ async function cloneProject(
|
|
|
214
294
|
// git's own message ("could not read Username … terminal prompts
|
|
215
295
|
// disabled") explains none of that. Say the actionable thing.
|
|
216
296
|
if (token === null && isGithubRepoUrl(project.repoUrl)) {
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
297
|
+
return {
|
|
298
|
+
clone: "failed",
|
|
299
|
+
warning:
|
|
300
|
+
`could not clone ${project.repoUrl} for ${project.slug}: GitHub is ` +
|
|
301
|
+
"not connected on this host. Connect GitHub on the host's page " +
|
|
302
|
+
"(Connections → GitHub), then Resume.",
|
|
303
|
+
};
|
|
222
304
|
}
|
|
223
|
-
return `could not clone ${project.repoUrl} for ${project.slug}: ${clone.stderr || "clone failed"}
|
|
305
|
+
return { clone: "failed", warning: `could not clone ${project.repoUrl} for ${project.slug}: ${clone.stderr || "clone failed"}` };
|
|
224
306
|
}
|
|
225
307
|
|
|
226
308
|
// Task branch off the clone's default HEAD (matches the compose path's
|
|
@@ -232,7 +314,7 @@ async function cloneProject(
|
|
|
232
314
|
GIT_STEP_TIMEOUT_MS,
|
|
233
315
|
);
|
|
234
316
|
if (branch.exitCode !== 0) {
|
|
235
|
-
return `cloned ${project.slug} but could not create branch ${taskBranch}: ${branch.stderr}
|
|
317
|
+
return { clone: "fresh", warning: `cloned ${project.slug} but could not create branch ${taskBranch}: ${branch.stderr}` };
|
|
236
318
|
}
|
|
237
319
|
|
|
238
320
|
// Repo-local commit identity so agent commits work out of the box.
|
|
@@ -250,7 +332,85 @@ async function cloneProject(
|
|
|
250
332
|
GIT_STEP_TIMEOUT_MS,
|
|
251
333
|
);
|
|
252
334
|
}
|
|
253
|
-
return null;
|
|
335
|
+
return { clone: "fresh", warning: null };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Git, read-only, in a fresh clone on the machine, as the task's user (the
|
|
339
|
+
* way the clone ran) but with nothing of that user's environment: no
|
|
340
|
+
* profile variable, PATH entry or global config can point it elsewhere. */
|
|
341
|
+
function gitInClone(handle: TaskEnvironmentHandle<unknown>, destination: string): RemoteGit {
|
|
342
|
+
return async (args) => {
|
|
343
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
344
|
+
const result = await handle.exec({
|
|
345
|
+
argv: [
|
|
346
|
+
"/usr/bin/env",
|
|
347
|
+
"-i",
|
|
348
|
+
"PATH=/usr/bin:/bin",
|
|
349
|
+
"HOME=/nonexistent",
|
|
350
|
+
"LC_ALL=C",
|
|
351
|
+
"GIT_CONFIG_NOSYSTEM=1",
|
|
352
|
+
"GIT_CONFIG_GLOBAL=/dev/null",
|
|
353
|
+
"GIT_TERMINAL_PROMPT=0",
|
|
354
|
+
"/usr/bin/git",
|
|
355
|
+
"--no-replace-objects",
|
|
356
|
+
"-C",
|
|
357
|
+
destination,
|
|
358
|
+
"-c",
|
|
359
|
+
"core.hooksPath=/dev/null",
|
|
360
|
+
"-c",
|
|
361
|
+
"core.fsmonitor=false",
|
|
362
|
+
...args,
|
|
363
|
+
],
|
|
364
|
+
projectEnvProfile: "maintenance",
|
|
365
|
+
timeoutMs: GIT_STEP_TIMEOUT_MS,
|
|
366
|
+
maxOutputBytes: MAX_ENV_SCHEMA_BYTES + 64 * 1024,
|
|
367
|
+
});
|
|
368
|
+
// A process that did not run to its end says nothing about the clone.
|
|
369
|
+
if (result.exitCode !== null) {
|
|
370
|
+
return result.exitCode === 0 && !result.stdoutTruncated ? Buffer.from(result.stdout) : null;
|
|
371
|
+
}
|
|
372
|
+
if (attempt >= 3) return null;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* ADR-153 §4, "Machine and sidecar": each project's schema observation,
|
|
379
|
+
* stored where the task cannot write before anything in it starts, and the
|
|
380
|
+
* same one reported by every task-up of the task (the cloud keeps the first).
|
|
381
|
+
* Only a clone made now, on a machine where nothing of the task has run, is
|
|
382
|
+
* read. A resumed clone is the task's live work, and a clone that failed is
|
|
383
|
+
* retried on a resume after the task's processes have run, so neither is
|
|
384
|
+
* ever read: with nothing stored, the base is missing, for the task's life.
|
|
385
|
+
* A failure here withholds that project's unbound keys, never fails the
|
|
386
|
+
* start.
|
|
387
|
+
*/
|
|
388
|
+
async function captureMachineEnvSchemas(
|
|
389
|
+
taskId: string,
|
|
390
|
+
handle: TaskEnvironmentHandle<unknown>,
|
|
391
|
+
clones: ReadonlyArray<{ projectId: string; destination: string; clone: CloneOutcome["clone"] }>,
|
|
392
|
+
): Promise<ProjectEnvSchemaObservation[]> {
|
|
393
|
+
const observations: ProjectEnvSchemaObservation[] = [];
|
|
394
|
+
for (const { projectId, destination, clone } of clones) {
|
|
395
|
+
try {
|
|
396
|
+
const stored = taskEnvSchemaObservation(taskId, projectId);
|
|
397
|
+
observations.push(
|
|
398
|
+
stored ??
|
|
399
|
+
(clone === "fresh"
|
|
400
|
+
? await captureTaskEnvSchemaFromClone(taskId, projectId, gitInClone(handle, destination))
|
|
401
|
+
: // No base is ever recorded on a machine: this stores it missing.
|
|
402
|
+
captureTaskEnvSchemas(taskId, [projectId])[0]!),
|
|
403
|
+
);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.warn(
|
|
406
|
+
`[machine] task ${taskId}: .env.schema capture failed for ${projectId}: ${
|
|
407
|
+
error instanceof Error ? error.message : String(error)
|
|
408
|
+
}`,
|
|
409
|
+
);
|
|
410
|
+
observations.push(captureTaskEnvSchemas(taskId, [projectId])[0]!);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return observations;
|
|
254
414
|
}
|
|
255
415
|
|
|
256
416
|
/** The curated code-server defaults (slim chrome, telemetry off, trust
|
|
@@ -293,6 +453,7 @@ async function seedEditorSettings(
|
|
|
293
453
|
"seed",
|
|
294
454
|
destination,
|
|
295
455
|
],
|
|
456
|
+
projectEnvProfile: "maintenance",
|
|
296
457
|
stdin: Buffer.from(editorSettingsSeed(editorTheme)),
|
|
297
458
|
timeoutMs: GIT_STEP_TIMEOUT_MS,
|
|
298
459
|
maxOutputBytes: STEP_OUTPUT_BYTES,
|
|
@@ -351,6 +512,7 @@ async function copySharedFiles(
|
|
|
351
512
|
"shared",
|
|
352
513
|
scope.destination,
|
|
353
514
|
],
|
|
515
|
+
projectEnvProfile: "maintenance",
|
|
354
516
|
stdin: tarball.stdout,
|
|
355
517
|
timeoutMs: 5 * 60_000,
|
|
356
518
|
maxOutputBytes: STEP_OUTPUT_BYTES,
|
|
@@ -392,6 +554,7 @@ export async function ensureCodeServer(
|
|
|
392
554
|
handle: TaskEnvironmentHandle<unknown>,
|
|
393
555
|
workspacePath: string,
|
|
394
556
|
editorTheme: string | undefined,
|
|
557
|
+
envMode: ProjectEnvMode = handle.descriptor.envMode ?? "baked",
|
|
395
558
|
): Promise<void> {
|
|
396
559
|
// Settings before process: code-server reads them at startup. A failed
|
|
397
560
|
// seed still launches — a themed editor is worth less than one that starts.
|
|
@@ -413,21 +576,49 @@ export async function ensureCodeServer(
|
|
|
413
576
|
"-c",
|
|
414
577
|
'pgrep -f "[c]ode-server.*--bind-addr" >/dev/null 2>&1',
|
|
415
578
|
],
|
|
579
|
+
projectEnvProfile: "maintenance",
|
|
416
580
|
timeoutMs: GIT_STEP_TIMEOUT_MS,
|
|
417
581
|
maxOutputBytes: STEP_OUTPUT_BYTES,
|
|
418
582
|
});
|
|
419
583
|
if (running.exitCode === 0) return;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
584
|
+
// Editor processes are task processes, but code-server must not inherit a
|
|
585
|
+
// real project value that a fixed-task sidecar's outer login shell loaded.
|
|
586
|
+
// Start it from an explicit empty environment and give its descendants only
|
|
587
|
+
// the host-owned runtime baseline plus the loader controls editor terminals
|
|
588
|
+
// need. The maintenance profile also prevents the provider-added outer
|
|
589
|
+
// shell from fetching project env before `env -i` gets control.
|
|
590
|
+
const editorEnvironment = {
|
|
591
|
+
...MACHINE_RUNTIME_AUTHORITY_ENV,
|
|
592
|
+
...(envMode === "loader"
|
|
593
|
+
? { BASH_ENV: PROJECT_ENV_HOOK_PATH, UAI_ENV_HOOK: "1" }
|
|
594
|
+
: {}),
|
|
595
|
+
LOGNAME: "node",
|
|
596
|
+
PLAYWRIGHT_BROWSERS_PATH: "/opt/pw-browsers",
|
|
597
|
+
SHELL: "/bin/bash",
|
|
598
|
+
USER: "node",
|
|
599
|
+
} as const;
|
|
427
600
|
const result = await handle.launchDetachedSession({
|
|
428
|
-
argv: [
|
|
601
|
+
argv: [
|
|
602
|
+
"/usr/bin/env",
|
|
603
|
+
"-i",
|
|
604
|
+
...Object.entries(editorEnvironment)
|
|
605
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
606
|
+
.map(([name, value]) => `${name}=${value}`),
|
|
607
|
+
"/home/node/.local/bin/code-server",
|
|
608
|
+
"--user-data-dir",
|
|
609
|
+
"/home/node/.local/share/code-server",
|
|
610
|
+
"--disable-workspace-trust",
|
|
611
|
+
"--auth",
|
|
612
|
+
"none",
|
|
613
|
+
"--disable-telemetry",
|
|
614
|
+
"--disable-update-check",
|
|
615
|
+
"--bind-addr",
|
|
616
|
+
`0.0.0.0:${MACHINE_CODE_SERVER_PORT}`,
|
|
617
|
+
workspacePath,
|
|
618
|
+
],
|
|
429
619
|
inheritEnv: [],
|
|
430
620
|
env: {},
|
|
621
|
+
projectEnvProfile: "maintenance",
|
|
431
622
|
launchTimeoutMs: 30_000,
|
|
432
623
|
maxOutputBytes: 64 * 1024,
|
|
433
624
|
});
|
|
@@ -502,6 +693,7 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
502
693
|
request: TaskEnvironmentProvisionRequest<TaskLaunchInput, TaskUpCredentials>,
|
|
503
694
|
): Promise<TaskEnvironmentProvisioned<TaskUpResult, TaskDownResult>> {
|
|
504
695
|
const { input } = request;
|
|
696
|
+
const envMode = request.envMode ?? "loader";
|
|
505
697
|
const provisioned = await deps.environment.provision({
|
|
506
698
|
taskId: request.taskId,
|
|
507
699
|
input: {
|
|
@@ -511,13 +703,27 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
511
703
|
...(deps.publishSsh() ? { publishSsh: true } : {}),
|
|
512
704
|
},
|
|
513
705
|
credentials: undefined,
|
|
706
|
+
envMode,
|
|
514
707
|
onPrepared: request.onPrepared,
|
|
515
708
|
});
|
|
516
709
|
const handle = provisioned.handle;
|
|
517
710
|
const workspacePath = handle.descriptor.workspacePath;
|
|
518
711
|
const warnings: string[] = [];
|
|
712
|
+
const clones: Array<{ projectId: string; destination: string; clone: CloneOutcome["clone"] }> = [];
|
|
519
713
|
|
|
520
714
|
try {
|
|
715
|
+
if (envMode === "loader") {
|
|
716
|
+
await installMachineProjectEnvLoader(handle);
|
|
717
|
+
// ADR-153, on a machine's own task (the sidecar's): the proxy's CA
|
|
718
|
+
// and pins before anything in the task loads its env. Never fails
|
|
719
|
+
// the start; its proxied keys stay withheld until they are in.
|
|
720
|
+
const proxyWarning = await reconcileSidecarTaskProxy(
|
|
721
|
+
handle,
|
|
722
|
+
activeSecretsProxy()?.runtime ?? null,
|
|
723
|
+
{ start: true },
|
|
724
|
+
);
|
|
725
|
+
if (proxyWarning !== null) console.warn(`[machine] task ${request.taskId}: ${proxyWarning}`);
|
|
726
|
+
}
|
|
521
727
|
const projects = [...input.projects].sort(
|
|
522
728
|
(a, b) => a.position - b.position,
|
|
523
729
|
);
|
|
@@ -528,15 +734,17 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
528
734
|
? await deps.githubToken(input.task.ownerUserId)
|
|
529
735
|
: null;
|
|
530
736
|
for (const project of projects) {
|
|
531
|
-
const
|
|
737
|
+
const destination = `${workspacePath}/${projectDirectoryName(project.slug)}`;
|
|
738
|
+
const outcome = await cloneProject(
|
|
532
739
|
handle,
|
|
533
|
-
|
|
740
|
+
destination,
|
|
534
741
|
project,
|
|
535
742
|
input.task.branch,
|
|
536
743
|
{ name: input.ownerName, email: input.ownerEmail },
|
|
537
744
|
token?.accessToken ?? null,
|
|
538
745
|
);
|
|
539
|
-
|
|
746
|
+
clones.push({ projectId: project.id, destination, clone: outcome.clone });
|
|
747
|
+
if (outcome.warning) warnings.push(outcome.warning);
|
|
540
748
|
}
|
|
541
749
|
} catch (error) {
|
|
542
750
|
// Provisioning-infrastructure failures (ssh died, unsafe slug) tear
|
|
@@ -545,6 +753,9 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
545
753
|
await handle.teardown().catch(() => {});
|
|
546
754
|
throw error;
|
|
547
755
|
}
|
|
756
|
+
// ADR-153 §4: right after the clones, before shared files, the
|
|
757
|
+
// forwarder, the editor or any agent.
|
|
758
|
+
const envSchemas = await captureMachineEnvSchemas(request.taskId, handle, clones);
|
|
548
759
|
|
|
549
760
|
// ADR-062 shared files: point-in-time copy (see copySharedFiles).
|
|
550
761
|
const sharedWarning = await (deps.sharedFiles ?? copySharedFiles)(handle, workspacePath, {
|
|
@@ -574,6 +785,24 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
574
785
|
});
|
|
575
786
|
}
|
|
576
787
|
|
|
788
|
+
if (envMode === "loader") {
|
|
789
|
+
try {
|
|
790
|
+
await startMachineTaskForwarder(
|
|
791
|
+
request.taskId,
|
|
792
|
+
handle,
|
|
793
|
+
request.credentials,
|
|
794
|
+
deps.ensureGatewayTunnel ?? ensureMachineGatewayTunnelReady,
|
|
795
|
+
);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
// Unlike editor/auth conveniences, the loader is the only project
|
|
798
|
+
// env delivery path. A task without it must not start and silently
|
|
799
|
+
// hand consumers an incomplete or legacy environment.
|
|
800
|
+
stopMachineGatewayTunnel(request.taskId);
|
|
801
|
+
await handle.teardown().catch(() => {});
|
|
802
|
+
throw error;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
577
806
|
// Editor pane: code-server on the machine (same flags as uai-init's
|
|
578
807
|
// container launch), idempotent across provision re-runs and resume.
|
|
579
808
|
// Best-effort — a missing binary degrades the Editor tab, never the task.
|
|
@@ -581,6 +810,7 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
581
810
|
handle,
|
|
582
811
|
workspacePath,
|
|
583
812
|
input.task.previewEnv?.UAI_EDITOR_THEME,
|
|
813
|
+
envMode,
|
|
584
814
|
).catch((error: unknown) => {
|
|
585
815
|
console.warn(
|
|
586
816
|
`[machine] task ${request.taskId}: code-server launch failed: ${
|
|
@@ -597,6 +827,7 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
597
827
|
composeProject: environmentRuntimeIdentity(handle),
|
|
598
828
|
worktreePath: deps.taskControlDir(request.taskId),
|
|
599
829
|
codeServerPort: MACHINE_CODE_SERVER_PORT,
|
|
830
|
+
...(envSchemas.length > 0 ? { envSchemas } : {}),
|
|
600
831
|
...(warnings.length > 0
|
|
601
832
|
? { initWarning: warnings.join("\n") }
|
|
602
833
|
: {}),
|
|
@@ -604,8 +835,10 @@ export function createMachineHostTaskEnvironmentProvider(
|
|
|
604
835
|
};
|
|
605
836
|
},
|
|
606
837
|
|
|
607
|
-
async reconstruct(locator) {
|
|
608
|
-
return withTaskDownResult(
|
|
838
|
+
async reconstruct(locator, envMode = "baked") {
|
|
839
|
+
return withTaskDownResult(
|
|
840
|
+
await deps.environment.reconstruct(locator, envMode),
|
|
841
|
+
);
|
|
609
842
|
},
|
|
610
843
|
};
|
|
611
844
|
}
|