@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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-153 §4 and §6: where a task's project env comes from, for its /env and
|
|
3
|
+
* its proxy alike, on an owned runner and on a task machine's sidecar.
|
|
4
|
+
*
|
|
5
|
+
* - The store is the runner's own records on an owned runner, and on a task
|
|
6
|
+
* machine the sidecar's replica of the cloud's.
|
|
7
|
+
* - A store never activated gives tasks the stored values, as before
|
|
8
|
+
* records existed.
|
|
9
|
+
* - Once activated, a task's env is the view of one snapshot of the store,
|
|
10
|
+
* decided with the task's own schema observations: each key's value as of
|
|
11
|
+
* its head, a placeholder the proxy swaps, or nothing. While the store's
|
|
12
|
+
* gate is closed, or a read of it fails, the task gets no project env at
|
|
13
|
+
* all, never the stored values.
|
|
14
|
+
*
|
|
15
|
+
* A task's read of the store is kept until anything it depends on changes:
|
|
16
|
+
* one of its projects' values or records, a binding or the store's gate
|
|
17
|
+
* (each bumps a process-local revision, and only this process writes the
|
|
18
|
+
* store). Its observations are stored once and never change. So a change
|
|
19
|
+
* reaches the next command and the next request, while a command that
|
|
20
|
+
* changes nothing reads nothing.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { projectEnvAuthorityRevision, projectEnvRevision } from "./host-env";
|
|
24
|
+
import { readProjectEnvAuthoritySnapshot } from "./project-env-authority";
|
|
25
|
+
import { readProjectEnvReplicaSnapshot, readProjectEnvReplicaStatus } from "./project-env-replica";
|
|
26
|
+
import { taskProjectEnvResolver, type TaskEnvSource } from "./project-env-loader";
|
|
27
|
+
import { captureTaskEnvSchemas, taskEnvSchemaObservation } from "./project-env-schema-capture";
|
|
28
|
+
import {
|
|
29
|
+
sameTaskEnvHead,
|
|
30
|
+
taskEnvView,
|
|
31
|
+
taskEnvWithheldNote,
|
|
32
|
+
type TaskEnvHead,
|
|
33
|
+
type TaskEnvSnapshot,
|
|
34
|
+
} from "./project-env-task-policy";
|
|
35
|
+
import { getHostTask, hostTaskProjectEnvSpec, type TaskProjectEnvSpec } from "./runtime-state";
|
|
36
|
+
import { sidecarMode } from "./sidecar";
|
|
37
|
+
import { taskProxiedEnv } from "./secrets-proxy/policy";
|
|
38
|
+
import { taskTrustEnv } from "./secrets-proxy/readiness";
|
|
39
|
+
import type { TaskGatewayEnvResolver } from "./task-gateway-state";
|
|
40
|
+
|
|
41
|
+
/** The task's projects, lead first, as its launch pinned them. */
|
|
42
|
+
function projectIdsOf(spec: TaskProjectEnvSpec): string[] {
|
|
43
|
+
const ordered = [...spec.projects].sort(
|
|
44
|
+
(left, right) => left.position - right.position || (left.projectId < right.projectId ? -1 : left.projectId > right.projectId ? 1 : 0),
|
|
45
|
+
);
|
|
46
|
+
return [...new Set(ordered.map((project) => project.projectId))];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One snapshot of the task's projects: `legacy` on a store that has never
|
|
51
|
+
* been activated, null while its gate is closed or it cannot be read, and
|
|
52
|
+
* otherwise the records, values and observations the task's decisions come
|
|
53
|
+
* from.
|
|
54
|
+
*/
|
|
55
|
+
export function readTaskEnvSnapshot(taskId: string, spec: TaskProjectEnvSpec): TaskEnvSnapshot | "legacy" | null {
|
|
56
|
+
const projectIds = projectIdsOf(spec);
|
|
57
|
+
const key = JSON.stringify([projectEnvAuthorityRevision(), projectIds.map((projectId) => [projectId, projectEnvRevision(projectId)])]);
|
|
58
|
+
const hit = snapshots.get(taskId);
|
|
59
|
+
if (hit?.key === key) return hit.snapshot;
|
|
60
|
+
const snapshot = freshTaskEnvSnapshot(taskId, projectIds);
|
|
61
|
+
// Kept only when nothing in it can change without a revision moving: a
|
|
62
|
+
// read that failed, or one with an observation not stored yet (task-up
|
|
63
|
+
// may still be about to record its base), is read again next time.
|
|
64
|
+
if (snapshot === "legacy" || (snapshot !== null && projectIds.every((projectId) => taskEnvSchemaObservation(taskId, projectId) !== null))) {
|
|
65
|
+
snapshots.delete(taskId);
|
|
66
|
+
if (snapshots.size >= MAX_SNAPSHOTS) snapshots.delete(snapshots.keys().next().value!);
|
|
67
|
+
snapshots.set(taskId, { key, snapshot });
|
|
68
|
+
}
|
|
69
|
+
return snapshot;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Tasks' last reads, most recent last. Values stay in memory only here and
|
|
73
|
+
* in the rendered /env documents, as before. */
|
|
74
|
+
const snapshots = new Map<string, { key: string; snapshot: TaskEnvSnapshot | "legacy" }>();
|
|
75
|
+
const MAX_SNAPSHOTS = 256;
|
|
76
|
+
|
|
77
|
+
/** Forget a task's read, when its gateway generation goes. */
|
|
78
|
+
export function forgetTaskEnvSnapshot(taskId: string): void {
|
|
79
|
+
snapshots.delete(taskId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function freshTaskEnvSnapshot(taskId: string, projectIds: readonly string[]): TaskEnvSnapshot | "legacy" | null {
|
|
83
|
+
return sidecarMode() ? replicaTaskEnvSnapshot(taskId, projectIds) : ownedTaskEnvSnapshot(taskId, projectIds);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** An owned runner is the authority for its own records. */
|
|
87
|
+
function ownedTaskEnvSnapshot(taskId: string, projectIds: readonly string[]): TaskEnvSnapshot | "legacy" | null {
|
|
88
|
+
const read = readProjectEnvAuthoritySnapshot(projectIds);
|
|
89
|
+
if (read === null) return null;
|
|
90
|
+
if (read.mode === "legacy") return "legacy";
|
|
91
|
+
return snapshotOf(taskId, projectIds, (projectId) => read.projects.find((project) => project.projectId === projectId)?.entries ?? []);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* On a task machine the cloud is the authority, and the sidecar keeps a
|
|
96
|
+
* replica of its task's records (ADR-153 §4, "A machine's replica"): once it
|
|
97
|
+
* has been activated, the task's view comes from it, and a key whose value
|
|
98
|
+
* the sidecar has not installed gets none. Before that, the values the
|
|
99
|
+
* sidecar reads from the store by name, as before.
|
|
100
|
+
*/
|
|
101
|
+
function replicaTaskEnvSnapshot(taskId: string, projectIds: readonly string[]): TaskEnvSnapshot | "legacy" | null {
|
|
102
|
+
let activated: boolean;
|
|
103
|
+
try {
|
|
104
|
+
activated = readProjectEnvReplicaStatus().activated;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
if (!activated) return "legacy";
|
|
109
|
+
const read = readProjectEnvReplicaSnapshot(projectIds);
|
|
110
|
+
if (read === null) return null;
|
|
111
|
+
return snapshotOf(taskId, projectIds, (projectId) =>
|
|
112
|
+
(read.projects.find((project) => project.projectId === projectId)?.entries ?? []).map((entry) => ({
|
|
113
|
+
key: entry.state.key,
|
|
114
|
+
state: entry.state,
|
|
115
|
+
value: entry.materialization.kind === "installed" ? entry.value : null,
|
|
116
|
+
})),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface StoreEntry {
|
|
121
|
+
key: string;
|
|
122
|
+
/** The record; null on a store that predates records. */
|
|
123
|
+
state: { head: TaskEnvHead; binding: unknown; policyEpoch: unknown } | null;
|
|
124
|
+
/** Null where the store holds no value for the record. */
|
|
125
|
+
value: string | null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The task's snapshot of one read of the store, with its observations. */
|
|
129
|
+
function snapshotOf(taskId: string, projectIds: readonly string[], entriesOf: (projectId: string) => readonly StoreEntry[]): TaskEnvSnapshot {
|
|
130
|
+
const projects = projectIds.map((projectId) => {
|
|
131
|
+
const entries = entriesOf(projectId);
|
|
132
|
+
return {
|
|
133
|
+
projectId,
|
|
134
|
+
// Captured here on the task's first read, from the base task-up
|
|
135
|
+
// recorded before the container started; with none recorded yet, a
|
|
136
|
+
// schema it could not read, so an unbound key is withheld.
|
|
137
|
+
observation: taskEnvSchemaObservation(taskId, projectId) ?? captureTaskEnvSchemas(taskId, [projectId], { final: false })[0],
|
|
138
|
+
records: entries.flatMap((entry) =>
|
|
139
|
+
entry.state === null
|
|
140
|
+
? []
|
|
141
|
+
: [{ key: entry.key, head: entry.state.head, binding: entry.state.binding, policyEpoch: entry.state.policyEpoch }],
|
|
142
|
+
),
|
|
143
|
+
entries,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
projects: projects.map(({ projectId, observation, records }) => ({ projectId, observation, records })),
|
|
148
|
+
valueAt: (projectId: string, key: string, head: TaskEnvHead): string | null => {
|
|
149
|
+
const entry = projects.find((project) => project.projectId === projectId)?.entries.find((candidate) => candidate.key === key);
|
|
150
|
+
return entry?.state != null && sameTaskEnvHead(entry.state.head, head) ? entry.value : null;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Where the task's /env takes its project values from right now. */
|
|
156
|
+
export function taskEnvSource(taskId: string, spec: TaskProjectEnvSpec): TaskEnvSource {
|
|
157
|
+
const snapshot = readTaskEnvSnapshot(taskId, spec);
|
|
158
|
+
if (snapshot === "legacy") return { kind: "stored" };
|
|
159
|
+
if (snapshot === null) return { kind: "none" };
|
|
160
|
+
return { kind: "view", view: taskEnvView(snapshot) };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The task's snapshot for its proxy, by task id alone: what its persisted
|
|
164
|
+
* launch spec names. Nothing is proxied from a store never activated. */
|
|
165
|
+
export function proxyTaskEnvSnapshot(taskId: string): TaskEnvSnapshot | null {
|
|
166
|
+
const task = getHostTask(taskId);
|
|
167
|
+
let spec: TaskProjectEnvSpec | null = null;
|
|
168
|
+
try {
|
|
169
|
+
spec = task === null ? null : hostTaskProjectEnvSpec(task);
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
if (spec === null) return null;
|
|
174
|
+
const snapshot = readTaskEnvSnapshot(taskId, spec);
|
|
175
|
+
return snapshot === "legacy" ? null : snapshot;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The task's note about the keys it does not get, from the store as it is
|
|
179
|
+
* now; null when it gets every one, or the store gives no view. */
|
|
180
|
+
export function taskEnvWithheldNoteNow(taskId: string, spec: TaskProjectEnvSpec): string | null {
|
|
181
|
+
const source = taskEnvSource(taskId, spec);
|
|
182
|
+
return source.kind === "view" ? taskEnvWithheldNote(source.view.policies) : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** The resolver a loader task's gateway generation renders /env with. */
|
|
186
|
+
export function taskEnvResolver(taskId: string, spec: TaskProjectEnvSpec): TaskGatewayEnvResolver {
|
|
187
|
+
return taskProjectEnvResolver(
|
|
188
|
+
spec,
|
|
189
|
+
() => taskTrustEnv(taskId),
|
|
190
|
+
() => taskProxiedEnv(taskId),
|
|
191
|
+
() => taskEnvSource(taskId, spec),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
@@ -27,12 +27,15 @@ import {
|
|
|
27
27
|
hostWorkspaceWrite,
|
|
28
28
|
} from "./workspace-files";
|
|
29
29
|
import { assertSafeHostTaskId } from "../task-identity";
|
|
30
|
+
import { applyProjectEnvProfile } from "../runtime-authority";
|
|
30
31
|
import {
|
|
31
32
|
capturedCliExec,
|
|
32
33
|
streamingCliExec,
|
|
33
34
|
} from "./docker";
|
|
34
35
|
import {
|
|
36
|
+
assertTaskEnvironmentExecRequest,
|
|
35
37
|
assertTaskEnvironmentProcessRequest,
|
|
38
|
+
refusePrivilegedExec,
|
|
36
39
|
assertTaskEnvironmentSessionRequest,
|
|
37
40
|
type TaskEnvironmentCopyRequest,
|
|
38
41
|
type TaskEnvironmentDescriptor,
|
|
@@ -43,6 +46,7 @@ import {
|
|
|
43
46
|
type TaskEnvironmentLocator,
|
|
44
47
|
type TaskEnvironmentPort,
|
|
45
48
|
type TaskEnvironmentProcess,
|
|
49
|
+
type ProjectEnvMode,
|
|
46
50
|
type TaskEnvironmentProvider,
|
|
47
51
|
type TaskEnvironmentRecoveryContext,
|
|
48
52
|
type TaskEnvironmentRecoveryResult,
|
|
@@ -129,7 +133,11 @@ export type AppleTaskEnvironmentRecoveryDriver = (
|
|
|
129
133
|
export interface AppleContainerProvisioningDeps<TInput, TCredentials, TResult> {
|
|
130
134
|
/** Runs task-up with the selection environment already published by the
|
|
131
135
|
* runtime pin (UAI_TASK_RUNTIME / UAI_CONTAINER_CLI). */
|
|
132
|
-
taskUp: (
|
|
136
|
+
taskUp: (
|
|
137
|
+
input: TInput,
|
|
138
|
+
credentials?: TCredentials,
|
|
139
|
+
envMode?: ProjectEnvMode,
|
|
140
|
+
) => Promise<TResult>;
|
|
133
141
|
inputTaskId: (input: TInput) => string;
|
|
134
142
|
/** The host worktree path this task will occupy, derivable before task-up
|
|
135
143
|
* runs so a crash mid-provision still leaves a recoverable locator. */
|
|
@@ -184,6 +192,7 @@ export function createAppleContainerTaskEnvironmentProvider<
|
|
|
184
192
|
const result = await provisioning.taskUp(
|
|
185
193
|
request.input,
|
|
186
194
|
request.credentials,
|
|
195
|
+
request.envMode ?? "loader",
|
|
187
196
|
);
|
|
188
197
|
const prepared = parseAppleTaskEnvironmentLocator(preparedLocator);
|
|
189
198
|
// task-up composes its worktree path in shell from the operator's
|
|
@@ -202,11 +211,12 @@ export function createAppleContainerTaskEnvironmentProvider<
|
|
|
202
211
|
handle: new AppleContainerTaskEnvironmentHandle(
|
|
203
212
|
preparedLocator,
|
|
204
213
|
resolved,
|
|
214
|
+
request.envMode ?? "loader",
|
|
205
215
|
),
|
|
206
216
|
result,
|
|
207
217
|
};
|
|
208
218
|
},
|
|
209
|
-
async reconstruct(locator) {
|
|
219
|
+
async reconstruct(locator, envMode = "baked") {
|
|
210
220
|
const persisted = parseAppleTaskEnvironmentLocator(locator);
|
|
211
221
|
const current = validateAppleMachineIdentity(resolved.machineIdentity());
|
|
212
222
|
if (
|
|
@@ -217,7 +227,7 @@ export function createAppleContainerTaskEnvironmentProvider<
|
|
|
217
227
|
"task environment belongs to a different container backend",
|
|
218
228
|
);
|
|
219
229
|
}
|
|
220
|
-
return new AppleContainerTaskEnvironmentHandle(locator, resolved);
|
|
230
|
+
return new AppleContainerTaskEnvironmentHandle(locator, resolved, envMode);
|
|
221
231
|
},
|
|
222
232
|
};
|
|
223
233
|
}
|
|
@@ -266,12 +276,14 @@ class AppleContainerTaskEnvironmentHandle
|
|
|
266
276
|
constructor(
|
|
267
277
|
locator: TaskEnvironmentLocator,
|
|
268
278
|
private readonly deps: ResolvedAppleContainerDeps,
|
|
279
|
+
envMode: ProjectEnvMode,
|
|
269
280
|
) {
|
|
270
281
|
this.#value = parseAppleTaskEnvironmentLocator(locator);
|
|
271
282
|
this.durableIdentity = this.#value.containerName;
|
|
272
283
|
this.descriptor = {
|
|
273
284
|
taskId: this.#value.taskId,
|
|
274
285
|
locator,
|
|
286
|
+
envMode,
|
|
275
287
|
workspacePath: "/workspace",
|
|
276
288
|
};
|
|
277
289
|
}
|
|
@@ -294,12 +306,19 @@ class AppleContainerTaskEnvironmentHandle
|
|
|
294
306
|
}
|
|
295
307
|
|
|
296
308
|
exec(request: TaskEnvironmentExecRequest): Promise<TaskEnvironmentExecResult> {
|
|
297
|
-
|
|
309
|
+
assertTaskEnvironmentExecRequest(request);
|
|
310
|
+
refusePrivilegedExec(request);
|
|
311
|
+
const profiled = applyProjectEnvProfile(
|
|
312
|
+
request,
|
|
313
|
+
this.descriptor.envMode ?? "baked",
|
|
314
|
+
"/usr/bin/zsh",
|
|
315
|
+
);
|
|
316
|
+
request.admit?.();
|
|
298
317
|
return capturedCliExec(
|
|
299
318
|
this.deps.spawn,
|
|
300
319
|
this.deps.containerCliPath(),
|
|
301
|
-
appleExecArgs(this.#value.containerName,
|
|
302
|
-
|
|
320
|
+
appleExecArgs(this.#value.containerName, profiled, "interactive"),
|
|
321
|
+
profiled,
|
|
303
322
|
);
|
|
304
323
|
}
|
|
305
324
|
|
|
@@ -307,11 +326,16 @@ class AppleContainerTaskEnvironmentHandle
|
|
|
307
326
|
request: TaskEnvironmentSpawnRequest,
|
|
308
327
|
): Promise<TaskEnvironmentProcess> {
|
|
309
328
|
assertTaskEnvironmentProcessRequest(request);
|
|
329
|
+
const profiled = applyProjectEnvProfile(
|
|
330
|
+
request,
|
|
331
|
+
this.descriptor.envMode ?? "baked",
|
|
332
|
+
"/usr/bin/zsh",
|
|
333
|
+
);
|
|
310
334
|
return streamingCliExec(
|
|
311
335
|
this.deps.spawn,
|
|
312
336
|
this.deps.containerCliPath(),
|
|
313
|
-
appleExecArgs(this.#value.containerName,
|
|
314
|
-
|
|
337
|
+
appleExecArgs(this.#value.containerName, profiled, "interactive"),
|
|
338
|
+
profiled,
|
|
315
339
|
);
|
|
316
340
|
}
|
|
317
341
|
|
|
@@ -319,11 +343,16 @@ class AppleContainerTaskEnvironmentHandle
|
|
|
319
343
|
request: TaskEnvironmentSessionRequest,
|
|
320
344
|
): Promise<TaskEnvironmentProcess> {
|
|
321
345
|
assertTaskEnvironmentSessionRequest(request);
|
|
346
|
+
const profiled = applyProjectEnvProfile(
|
|
347
|
+
request,
|
|
348
|
+
this.descriptor.envMode ?? "baked",
|
|
349
|
+
"/usr/bin/zsh",
|
|
350
|
+
);
|
|
322
351
|
return streamingCliExec(
|
|
323
352
|
this.deps.spawn,
|
|
324
353
|
this.deps.containerCliPath(),
|
|
325
|
-
appleExecArgs(this.#value.containerName,
|
|
326
|
-
|
|
354
|
+
appleExecArgs(this.#value.containerName, profiled, "interactive"),
|
|
355
|
+
profiled,
|
|
327
356
|
);
|
|
328
357
|
}
|
|
329
358
|
|
|
@@ -331,8 +360,13 @@ class AppleContainerTaskEnvironmentHandle
|
|
|
331
360
|
request: TaskEnvironmentDetachedSessionRequest,
|
|
332
361
|
): Promise<TaskEnvironmentExecResult> {
|
|
333
362
|
assertTaskEnvironmentSessionRequest(request);
|
|
363
|
+
const profiled = applyProjectEnvProfile(
|
|
364
|
+
request,
|
|
365
|
+
this.descriptor.envMode ?? "baked",
|
|
366
|
+
"/usr/bin/zsh",
|
|
367
|
+
);
|
|
334
368
|
const result = await this.deps.run(
|
|
335
|
-
appleExecArgs(this.#value.containerName,
|
|
369
|
+
appleExecArgs(this.#value.containerName, profiled, "detached"),
|
|
336
370
|
{
|
|
337
371
|
timeoutMs: request.launchTimeoutMs,
|
|
338
372
|
maxOutputBytes: request.maxOutputBytes,
|
|
@@ -22,7 +22,9 @@ import {
|
|
|
22
22
|
import { taskDir } from "../env";
|
|
23
23
|
import { PREVIEW_SIDECAR_TASK_LABEL } from "../preview-sidecar";
|
|
24
24
|
import { assertSafeHostTaskId } from "../task-identity";
|
|
25
|
+
import { applyProjectEnvProfile } from "../runtime-authority";
|
|
25
26
|
import {
|
|
27
|
+
assertTaskEnvironmentExecRequest,
|
|
26
28
|
assertTaskEnvironmentProcessRequest,
|
|
27
29
|
assertTaskEnvironmentSessionRequest,
|
|
28
30
|
type TaskEnvironmentCopyRequest,
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
type TaskEnvironmentPort,
|
|
36
38
|
type TaskEnvironmentProcess,
|
|
37
39
|
type TaskEnvironmentProcessExit,
|
|
40
|
+
type ProjectEnvMode,
|
|
38
41
|
type TaskEnvironmentProvider,
|
|
39
42
|
type TaskEnvironmentRecoveryContext,
|
|
40
43
|
type TaskEnvironmentRecoveryResult,
|
|
@@ -73,6 +76,7 @@ export interface DockerTaskEnvironmentDeps {
|
|
|
73
76
|
taskUp?: (
|
|
74
77
|
input: TaskLaunchInput,
|
|
75
78
|
credentials?: TaskUpCredentials,
|
|
79
|
+
envMode?: ProjectEnvMode,
|
|
76
80
|
) => Promise<TaskUpResult>;
|
|
77
81
|
taskEnvironmentDown?: (taskId: string) => Promise<TaskDownResult>;
|
|
78
82
|
taskStatus?: (taskId: string) => Promise<TaskStatusResult>;
|
|
@@ -123,7 +127,11 @@ export function createDockerTaskEnvironmentProvider(
|
|
|
123
127
|
machine,
|
|
124
128
|
);
|
|
125
129
|
await request.onPrepared?.(preparedLocator);
|
|
126
|
-
const result = await resolved.taskUp(
|
|
130
|
+
const result = await resolved.taskUp(
|
|
131
|
+
request.input,
|
|
132
|
+
request.credentials,
|
|
133
|
+
request.envMode ?? "loader",
|
|
134
|
+
);
|
|
127
135
|
const prepared = parseDockerTaskEnvironmentLocator(preparedLocator);
|
|
128
136
|
// task-up composes its worktree path in shell from the operator's
|
|
129
137
|
// UAI_WORKSPACE_ROOT verbatim, so a benign trailing slash would fail a
|
|
@@ -145,11 +153,15 @@ export function createDockerTaskEnvironmentProvider(
|
|
|
145
153
|
machine,
|
|
146
154
|
);
|
|
147
155
|
return {
|
|
148
|
-
handle: new DockerTaskEnvironmentHandle(
|
|
156
|
+
handle: new DockerTaskEnvironmentHandle(
|
|
157
|
+
locator,
|
|
158
|
+
resolved,
|
|
159
|
+
request.envMode ?? "loader",
|
|
160
|
+
),
|
|
149
161
|
result,
|
|
150
162
|
};
|
|
151
163
|
},
|
|
152
|
-
async reconstruct(locator) {
|
|
164
|
+
async reconstruct(locator, envMode = "baked") {
|
|
153
165
|
const persisted = parseDockerTaskEnvironmentLocator(locator);
|
|
154
166
|
const current = validateMachineIdentity(resolved.machineIdentity());
|
|
155
167
|
if (
|
|
@@ -161,7 +173,7 @@ export function createDockerTaskEnvironmentProvider(
|
|
|
161
173
|
"task environment belongs to a different container backend",
|
|
162
174
|
);
|
|
163
175
|
}
|
|
164
|
-
return new DockerTaskEnvironmentHandle(locator, resolved);
|
|
176
|
+
return new DockerTaskEnvironmentHandle(locator, resolved, envMode);
|
|
165
177
|
},
|
|
166
178
|
};
|
|
167
179
|
}
|
|
@@ -256,12 +268,14 @@ class DockerTaskEnvironmentHandle
|
|
|
256
268
|
DockerTaskEnvironmentDeps,
|
|
257
269
|
"recover" | "recoveryDriver" | "taskStatus"
|
|
258
270
|
>,
|
|
271
|
+
envMode: ProjectEnvMode,
|
|
259
272
|
) {
|
|
260
273
|
this.#value = parseDockerTaskEnvironmentLocator(locator);
|
|
261
274
|
this.durableIdentity = this.#value.containerName;
|
|
262
275
|
this.descriptor = {
|
|
263
276
|
taskId: this.#value.taskId,
|
|
264
277
|
locator,
|
|
278
|
+
envMode,
|
|
265
279
|
workspacePath: "/workspace",
|
|
266
280
|
...(this.#value.codeServerPort === undefined
|
|
267
281
|
? {}
|
|
@@ -293,12 +307,19 @@ class DockerTaskEnvironmentHandle
|
|
|
293
307
|
}
|
|
294
308
|
|
|
295
309
|
exec(request: TaskEnvironmentExecRequest): Promise<TaskEnvironmentExecResult> {
|
|
296
|
-
|
|
310
|
+
assertTaskEnvironmentExecRequest(request);
|
|
311
|
+
const { privileged, ...unprivileged } = request;
|
|
312
|
+
const profiled = applyProjectEnvProfile(
|
|
313
|
+
unprivileged,
|
|
314
|
+
this.descriptor.envMode ?? "baked",
|
|
315
|
+
"/usr/bin/zsh",
|
|
316
|
+
);
|
|
317
|
+
request.admit?.();
|
|
297
318
|
return capturedCliExec(
|
|
298
319
|
this.deps.spawn,
|
|
299
320
|
"docker",
|
|
300
|
-
dockerExecArgs(this.#value.containerName,
|
|
301
|
-
|
|
321
|
+
dockerExecArgs(this.#value.containerName, profiled, "interactive", privileged === true),
|
|
322
|
+
profiled,
|
|
302
323
|
);
|
|
303
324
|
}
|
|
304
325
|
|
|
@@ -306,11 +327,16 @@ class DockerTaskEnvironmentHandle
|
|
|
306
327
|
request: TaskEnvironmentSpawnRequest,
|
|
307
328
|
): Promise<TaskEnvironmentProcess> {
|
|
308
329
|
assertTaskEnvironmentProcessRequest(request);
|
|
330
|
+
const profiled = applyProjectEnvProfile(
|
|
331
|
+
request,
|
|
332
|
+
this.descriptor.envMode ?? "baked",
|
|
333
|
+
"/usr/bin/zsh",
|
|
334
|
+
);
|
|
309
335
|
return streamingCliExec(
|
|
310
336
|
this.deps.spawn,
|
|
311
337
|
"docker",
|
|
312
|
-
dockerExecArgs(this.#value.containerName,
|
|
313
|
-
|
|
338
|
+
dockerExecArgs(this.#value.containerName, profiled),
|
|
339
|
+
profiled,
|
|
314
340
|
);
|
|
315
341
|
}
|
|
316
342
|
|
|
@@ -318,11 +344,16 @@ class DockerTaskEnvironmentHandle
|
|
|
318
344
|
request: TaskEnvironmentSessionRequest,
|
|
319
345
|
): Promise<TaskEnvironmentProcess> {
|
|
320
346
|
assertTaskEnvironmentSessionRequest(request);
|
|
347
|
+
const profiled = applyProjectEnvProfile(
|
|
348
|
+
request,
|
|
349
|
+
this.descriptor.envMode ?? "baked",
|
|
350
|
+
"/usr/bin/zsh",
|
|
351
|
+
);
|
|
321
352
|
return streamingCliExec(
|
|
322
353
|
this.deps.spawn,
|
|
323
354
|
"docker",
|
|
324
|
-
dockerExecArgs(this.#value.containerName,
|
|
325
|
-
|
|
355
|
+
dockerExecArgs(this.#value.containerName, profiled),
|
|
356
|
+
profiled,
|
|
326
357
|
);
|
|
327
358
|
}
|
|
328
359
|
|
|
@@ -330,8 +361,13 @@ class DockerTaskEnvironmentHandle
|
|
|
330
361
|
request: TaskEnvironmentDetachedSessionRequest,
|
|
331
362
|
): Promise<TaskEnvironmentExecResult> {
|
|
332
363
|
assertTaskEnvironmentSessionRequest(request);
|
|
364
|
+
const profiled = applyProjectEnvProfile(
|
|
365
|
+
request,
|
|
366
|
+
this.descriptor.envMode ?? "baked",
|
|
367
|
+
"/usr/bin/zsh",
|
|
368
|
+
);
|
|
333
369
|
const result = await this.deps.docker(
|
|
334
|
-
dockerExecArgs(this.#value.containerName,
|
|
370
|
+
dockerExecArgs(this.#value.containerName, profiled, "detached"),
|
|
335
371
|
{
|
|
336
372
|
timeoutMs: request.launchTimeoutMs,
|
|
337
373
|
maxOutputBytes: request.maxOutputBytes,
|
|
@@ -606,8 +642,11 @@ function dockerExecArgs(
|
|
|
606
642
|
| TaskEnvironmentSessionRequest
|
|
607
643
|
| TaskEnvironmentDetachedSessionRequest,
|
|
608
644
|
mode: "interactive" | "detached" = "interactive",
|
|
645
|
+
/** Passed only by exec(), after assertTaskEnvironmentExecRequest. */
|
|
646
|
+
privileged = false,
|
|
609
647
|
): string[] {
|
|
610
648
|
const args = ["exec", mode === "detached" ? "-d" : "-i"];
|
|
649
|
+
if (privileged) args.push("--privileged");
|
|
611
650
|
if (request.cwd) args.push("--workdir", request.cwd);
|
|
612
651
|
if (request.user) args.push("--user", request.user);
|
|
613
652
|
for (const name of request.inheritEnv ?? []) {
|
|
@@ -33,6 +33,7 @@ import { createMachineHostTaskEnvironmentProvider } from "./machine-task-up";
|
|
|
33
33
|
import { TaskEnvironmentRegistry } from "./registry";
|
|
34
34
|
import {
|
|
35
35
|
parseTaskEnvironmentLocator,
|
|
36
|
+
type ProjectEnvMode,
|
|
36
37
|
type TaskEnvironmentHandle,
|
|
37
38
|
type TaskEnvironmentLocator,
|
|
38
39
|
type TaskEnvironmentProvisioned,
|
|
@@ -47,6 +48,7 @@ export {
|
|
|
47
48
|
import { sidecarMode } from "../sidecar";
|
|
48
49
|
import { mountSharedFilesFromLease } from "../shared-files-s3";
|
|
49
50
|
import { createSelfTaskEnvironmentProvider } from "./self";
|
|
51
|
+
import { hostTaskEnvMode } from "../runtime-state";
|
|
50
52
|
|
|
51
53
|
const registry = new TaskEnvironmentRegistry<
|
|
52
54
|
TaskLaunchInput,
|
|
@@ -215,6 +217,7 @@ export function provisionTaskEnvironment(
|
|
|
215
217
|
: dockerProvider;
|
|
216
218
|
return provider.provision({
|
|
217
219
|
taskId: input.task.id,
|
|
220
|
+
envMode: "loader",
|
|
218
221
|
input,
|
|
219
222
|
credentials,
|
|
220
223
|
onPrepared,
|
|
@@ -223,8 +226,9 @@ export function provisionTaskEnvironment(
|
|
|
223
226
|
|
|
224
227
|
export function reconstructTaskEnvironment(
|
|
225
228
|
locator: TaskEnvironmentLocator,
|
|
229
|
+
envMode: ProjectEnvMode,
|
|
226
230
|
): Promise<TaskEnvironmentHandle<TaskDownResult>> {
|
|
227
|
-
return registry.reconstruct(locator);
|
|
231
|
+
return registry.reconstruct(locator, envMode);
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
export async function reconstructPersistedTaskEnvironment(
|
|
@@ -240,7 +244,7 @@ export async function reconstructPersistedTaskEnvironment(
|
|
|
240
244
|
: requireMachineIdentity(),
|
|
241
245
|
);
|
|
242
246
|
if (locator === null) return null;
|
|
243
|
-
const handle = await registry.reconstruct(locator);
|
|
247
|
+
const handle = await registry.reconstruct(locator, hostTaskEnvMode(task));
|
|
244
248
|
if (handle.descriptor.taskId !== task.taskId) {
|
|
245
249
|
throw new Error("task environment locator belongs to a different task");
|
|
246
250
|
}
|