@prismatic-io/lux 0.0.2-preview.29 → 0.0.2-preview.30
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/lib/answerers/persona/index.d.ts +6 -0
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/answerers/persona/index.js +5 -0
- package/lib/answerers/persona/index.js.map +1 -1
- package/lib/assertions/core/command-exits-zero.d.ts +2 -0
- package/lib/assertions/core/command-exits-zero.d.ts.map +1 -1
- package/lib/assertions/core/command-exits-zero.js +46 -4
- package/lib/assertions/core/command-exits-zero.js.map +1 -1
- package/lib/assertions/rubric/index.d.ts +16 -0
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.js +34 -3
- package/lib/assertions/rubric/index.js.map +1 -1
- package/lib/assertions/rubric/internal.d.ts +14 -0
- package/lib/assertions/rubric/internal.d.ts.map +1 -1
- package/lib/assertions/rubric/internal.js +122 -12
- package/lib/assertions/rubric/internal.js.map +1 -1
- package/lib/core/agent-environment.d.ts +5 -0
- package/lib/core/agent-environment.d.ts.map +1 -0
- package/lib/core/agent-environment.js +47 -0
- package/lib/core/agent-environment.js.map +1 -0
- package/lib/core/command-verification.d.ts +10 -0
- package/lib/core/command-verification.d.ts.map +1 -0
- package/lib/core/command-verification.js +66 -0
- package/lib/core/command-verification.js.map +1 -0
- package/lib/core/driver.d.ts +7 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js +2 -0
- package/lib/core/driver.js.map +1 -1
- package/lib/core/index.d.ts +2 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +2 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/model-cli.d.ts +5 -0
- package/lib/core/model-cli.d.ts.map +1 -1
- package/lib/core/model-cli.js +8 -2
- package/lib/core/model-cli.js.map +1 -1
- package/lib/core/run.d.ts +5 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/secure-copy.d.ts.map +1 -0
- package/lib/core/secure-copy.js.map +1 -0
- package/lib/drivers/antigravity/config.d.ts +3 -0
- package/lib/drivers/antigravity/config.d.ts.map +1 -1
- package/lib/drivers/antigravity/config.js +2 -0
- package/lib/drivers/antigravity/config.js.map +1 -1
- package/lib/drivers/antigravity/index.d.ts +4 -0
- package/lib/drivers/antigravity/index.d.ts.map +1 -1
- package/lib/drivers/antigravity/runtime.d.ts.map +1 -1
- package/lib/drivers/antigravity/runtime.js +7 -2
- package/lib/drivers/antigravity/runtime.js.map +1 -1
- package/lib/drivers/claude-code/index.d.ts +18 -4
- package/lib/drivers/claude-code/index.d.ts.map +1 -1
- package/lib/drivers/claude-code/index.js +97 -200
- package/lib/drivers/claude-code/index.js.map +1 -1
- package/lib/drivers/claude-code/isolation.d.ts +8 -0
- package/lib/drivers/claude-code/isolation.d.ts.map +1 -0
- package/lib/drivers/claude-code/isolation.js +199 -0
- package/lib/drivers/claude-code/isolation.js.map +1 -0
- package/lib/drivers/codex/config.d.ts +3 -0
- package/lib/drivers/codex/config.d.ts.map +1 -1
- package/lib/drivers/codex/config.js +2 -0
- package/lib/drivers/codex/config.js.map +1 -1
- package/lib/drivers/codex/index.d.ts +4 -0
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +52 -24
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/copilot/config.d.ts +3 -0
- package/lib/drivers/copilot/config.d.ts.map +1 -1
- package/lib/drivers/copilot/config.js +2 -0
- package/lib/drivers/copilot/config.js.map +1 -1
- package/lib/drivers/copilot/index.d.ts +4 -0
- package/lib/drivers/copilot/index.d.ts.map +1 -1
- package/lib/drivers/copilot/index.js +2 -1
- package/lib/drivers/copilot/index.js.map +1 -1
- package/lib/drivers/cursor/config.d.ts +1 -0
- package/lib/drivers/cursor/config.d.ts.map +1 -1
- package/lib/drivers/cursor/config.js +2 -0
- package/lib/drivers/cursor/config.js.map +1 -1
- package/lib/drivers/cursor/index.d.ts +2 -0
- package/lib/drivers/cursor/index.d.ts.map +1 -1
- package/lib/drivers/cursor/index.js +2 -1
- package/lib/drivers/cursor/index.js.map +1 -1
- package/lib/drivers/grok-build/config.d.ts +3 -0
- package/lib/drivers/grok-build/config.d.ts.map +1 -1
- package/lib/drivers/grok-build/config.js +2 -0
- package/lib/drivers/grok-build/config.js.map +1 -1
- package/lib/drivers/grok-build/index.d.ts +4 -0
- package/lib/drivers/grok-build/index.d.ts.map +1 -1
- package/lib/drivers/grok-build/index.js +2 -1
- package/lib/drivers/grok-build/index.js.map +1 -1
- package/lib/drivers/subprocess/index.d.ts +3 -3
- package/lib/drivers/subprocess/index.d.ts.map +1 -1
- package/lib/drivers/subprocess/index.js +5 -2
- package/lib/drivers/subprocess/index.js.map +1 -1
- package/lib/orchestrator/artifact-origin.d.ts +3 -0
- package/lib/orchestrator/artifact-origin.d.ts.map +1 -0
- package/lib/orchestrator/artifact-origin.js +40 -0
- package/lib/orchestrator/artifact-origin.js.map +1 -0
- package/lib/orchestrator/config.d.ts +6 -0
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/experiment-context.d.ts +2 -0
- package/lib/orchestrator/experiment-context.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.js +2 -2
- package/lib/orchestrator/experiment-corpus.js.map +1 -1
- package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-identity.js +4 -4
- package/lib/orchestrator/experiment-identity.js.map +1 -1
- package/lib/orchestrator/fixture-identity.d.ts +1 -0
- package/lib/orchestrator/fixture-identity.d.ts.map +1 -1
- package/lib/orchestrator/fixture-identity.js +6 -1
- package/lib/orchestrator/fixture-identity.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +2 -2
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +14 -3
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/answerers/persona/index.ts +5 -0
- package/src/assertions/core/command-exits-zero.ts +57 -4
- package/src/assertions/rubric/index.ts +38 -3
- package/src/assertions/rubric/internal.ts +172 -11
- package/src/core/agent-environment.ts +64 -0
- package/src/core/command-verification.ts +74 -0
- package/src/core/driver.ts +4 -0
- package/src/core/index.ts +2 -0
- package/src/core/model-cli.ts +14 -2
- package/src/drivers/antigravity/config.ts +2 -0
- package/src/drivers/antigravity/runtime.ts +8 -2
- package/src/drivers/claude-code/index.ts +112 -221
- package/src/drivers/claude-code/isolation.ts +228 -0
- package/src/drivers/codex/config.ts +2 -0
- package/src/drivers/codex/index.ts +71 -31
- package/src/drivers/copilot/config.ts +2 -0
- package/src/drivers/copilot/index.ts +2 -1
- package/src/drivers/cursor/config.ts +2 -0
- package/src/drivers/cursor/index.ts +2 -1
- package/src/drivers/grok-build/config.ts +2 -0
- package/src/drivers/grok-build/index.ts +2 -1
- package/src/drivers/subprocess/index.ts +5 -2
- package/src/orchestrator/artifact-origin.ts +50 -0
- package/src/orchestrator/experiment-corpus.ts +10 -7
- package/src/orchestrator/experiment-identity.ts +16 -13
- package/src/orchestrator/fixture-identity.ts +9 -1
- package/src/orchestrator/orchestrator.ts +12 -6
- package/src/orchestrator/run-execution.ts +21 -4
- package/lib/drivers/shared/secure-copy.d.ts.map +0 -1
- package/lib/drivers/shared/secure-copy.js.map +0 -1
- /package/lib/{drivers/shared → core}/secure-copy.d.ts +0 -0
- /package/lib/{drivers/shared → core}/secure-copy.js +0 -0
- /package/src/{drivers/shared → core}/secure-copy.ts +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { mkdir, readdir, realpath, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve, sep } from "node:path";
|
|
4
|
+
import type { StartContext } from "../../core/index.js";
|
|
5
|
+
import type { ClaudeCodeDriverConfig } from "./index.js";
|
|
6
|
+
|
|
7
|
+
const isWithin = (parent: string, child: string): boolean =>
|
|
8
|
+
child === parent || child.startsWith(`${parent}${sep}`);
|
|
9
|
+
|
|
10
|
+
const permissionPath = (tool: "Edit" | "Read", path: string, directory: boolean): string => {
|
|
11
|
+
const absolute = resolve(path).replaceAll("\\", "/");
|
|
12
|
+
return `${tool}(//${absolute.replace(/^\/+/, "")}${directory ? "/**" : ""})`;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Permission rules have no "allow inside deny" override, so isolate a root by
|
|
17
|
+
* denying every sibling around explicitly allowed descendants.
|
|
18
|
+
*/
|
|
19
|
+
const carvedPermissionDenials = async (
|
|
20
|
+
tool: "Edit" | "Read",
|
|
21
|
+
root: string,
|
|
22
|
+
allowed: readonly string[],
|
|
23
|
+
): Promise<string[]> => {
|
|
24
|
+
const normalizedRoot = resolve(root);
|
|
25
|
+
const inside = allowed
|
|
26
|
+
.map((path) => resolve(path))
|
|
27
|
+
.filter((path) => isWithin(normalizedRoot, path));
|
|
28
|
+
if (inside.some((path) => path === normalizedRoot)) return [];
|
|
29
|
+
if (inside.length === 0) {
|
|
30
|
+
const metadata = await stat(normalizedRoot);
|
|
31
|
+
return [permissionPath(tool, normalizedRoot, metadata.isDirectory())];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const out: string[] = [];
|
|
35
|
+
const walk = async (dir: string): Promise<void> => {
|
|
36
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
37
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const path = join(dir, entry.name);
|
|
40
|
+
if (inside.some((allowedPath) => allowedPath === path)) continue;
|
|
41
|
+
if (entry.isDirectory() && inside.some((allowedPath) => isWithin(path, allowedPath))) {
|
|
42
|
+
await walk(path);
|
|
43
|
+
} else {
|
|
44
|
+
out.push(permissionPath(tool, path, entry.isDirectory()));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
await walk(normalizedRoot);
|
|
49
|
+
return out;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const carvedReadDenials = (root: string, allowed: readonly string[]): Promise<string[]> =>
|
|
53
|
+
carvedPermissionDenials("Read", root, allowed);
|
|
54
|
+
|
|
55
|
+
const existingRealpaths = async (paths: readonly string[]): Promise<string[]> => {
|
|
56
|
+
const found = await Promise.all(
|
|
57
|
+
paths.map(async (path) => {
|
|
58
|
+
try {
|
|
59
|
+
return await realpath(resolve(path));
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
return [...new Set(found.filter((path): path is string => path !== null))];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const compactRoots = (paths: readonly string[]): string[] =>
|
|
69
|
+
[...new Set(paths)]
|
|
70
|
+
.sort((a, b) => a.length - b.length || a.localeCompare(b))
|
|
71
|
+
.filter((path, index, roots) => !roots.slice(0, index).some((root) => isWithin(root, path)));
|
|
72
|
+
|
|
73
|
+
export const isolatedLaunch = async (
|
|
74
|
+
config: ClaudeCodeDriverConfig,
|
|
75
|
+
ctx: StartContext,
|
|
76
|
+
workspace: string,
|
|
77
|
+
controlDir: string,
|
|
78
|
+
): Promise<{ env: Record<string, string>; settings: object }> => {
|
|
79
|
+
const homeRoots = await existingRealpaths([
|
|
80
|
+
homedir(),
|
|
81
|
+
...(config.env?.HOME ? [config.env.HOME] : []),
|
|
82
|
+
]);
|
|
83
|
+
const sensitiveHomePaths = homeRoots.flatMap((home) => [
|
|
84
|
+
join(home, ".aws"),
|
|
85
|
+
join(home, ".claude.json"),
|
|
86
|
+
join(home, ".claude", ".credentials.json"),
|
|
87
|
+
join(home, ".config", "gcloud"),
|
|
88
|
+
join(home, ".docker", "config.json"),
|
|
89
|
+
join(home, ".git-credentials"),
|
|
90
|
+
join(home, ".gnupg"),
|
|
91
|
+
join(home, ".kube"),
|
|
92
|
+
join(home, ".netrc"),
|
|
93
|
+
join(home, ".npmrc"),
|
|
94
|
+
join(home, ".ssh"),
|
|
95
|
+
...[
|
|
96
|
+
".bash_profile",
|
|
97
|
+
".bash_login",
|
|
98
|
+
".bashrc",
|
|
99
|
+
".bash_aliases",
|
|
100
|
+
".profile",
|
|
101
|
+
".zshenv",
|
|
102
|
+
".zprofile",
|
|
103
|
+
".zshrc",
|
|
104
|
+
".zlogin",
|
|
105
|
+
].map((name) => join(home, name)),
|
|
106
|
+
]);
|
|
107
|
+
const stateDir = join(workspace, ".lux-claude");
|
|
108
|
+
const configDir = join(controlDir, "config");
|
|
109
|
+
await mkdir(configDir, { recursive: true });
|
|
110
|
+
// An empty override preserves the CLI's default, unhashed Keychain service.
|
|
111
|
+
const authConfigOverride =
|
|
112
|
+
config.env?.CLAUDE_SECURESTORAGE_CONFIG_DIR ??
|
|
113
|
+
process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR ??
|
|
114
|
+
config.env?.CLAUDE_CONFIG_DIR ??
|
|
115
|
+
process.env.CLAUDE_CONFIG_DIR ??
|
|
116
|
+
"";
|
|
117
|
+
const authConfigDir = authConfigOverride || join(config.env?.HOME ?? homedir(), ".claude");
|
|
118
|
+
const cleanShell = process.platform === "win32" ? undefined : join(controlDir, "clean-bash");
|
|
119
|
+
if (cleanShell)
|
|
120
|
+
await writeFile(
|
|
121
|
+
cleanShell,
|
|
122
|
+
'#!/bin/sh\nunset BASH_ENV ENV\nexec /bin/bash --noprofile --norc "$@"\n',
|
|
123
|
+
{ mode: 0o700 },
|
|
124
|
+
);
|
|
125
|
+
const protectedRoots = await existingRealpaths([
|
|
126
|
+
...(config.interactionMode === "defer-resume" ? [configDir, authConfigDir] : []),
|
|
127
|
+
ctx.runDir,
|
|
128
|
+
...(ctx.runsRoot ? [ctx.runsRoot] : []),
|
|
129
|
+
...(process.env.CLAUDE_CONFIG_DIR ? [process.env.CLAUDE_CONFIG_DIR] : []),
|
|
130
|
+
...(config.env?.CLAUDE_CONFIG_DIR ? [config.env.CLAUDE_CONFIG_DIR] : []),
|
|
131
|
+
...(process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR
|
|
132
|
+
? [process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR]
|
|
133
|
+
: []),
|
|
134
|
+
...(config.env?.CLAUDE_SECURESTORAGE_CONFIG_DIR
|
|
135
|
+
? [config.env.CLAUDE_SECURESTORAGE_CONFIG_DIR]
|
|
136
|
+
: []),
|
|
137
|
+
...sensitiveHomePaths,
|
|
138
|
+
...(config.isolation?.denyRead ?? []),
|
|
139
|
+
...homeRoots.flatMap((home) => [
|
|
140
|
+
join(home, ".claude", "projects"),
|
|
141
|
+
join(home, ".claude", "memory"),
|
|
142
|
+
]),
|
|
143
|
+
]);
|
|
144
|
+
const denyRoots = compactRoots(protectedRoots);
|
|
145
|
+
const allowed = compactRoots(
|
|
146
|
+
await existingRealpaths([
|
|
147
|
+
...(cleanShell ? [cleanShell] : []),
|
|
148
|
+
...(config.pluginDirs ?? []),
|
|
149
|
+
...(config.addDirs ?? []),
|
|
150
|
+
...(config.isolation?.allowRead ?? []),
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
const overlapping = allowed.find(
|
|
154
|
+
(path) =>
|
|
155
|
+
(path !== workspace && isWithin(path, workspace)) ||
|
|
156
|
+
denyRoots.some((root) => isWithin(path, root)) ||
|
|
157
|
+
protectedRoots.some((root) => isWithin(root, path)),
|
|
158
|
+
);
|
|
159
|
+
if (overlapping) {
|
|
160
|
+
throw new Error(`isolation read carve-out overlaps a protected boundary: ${overlapping}`);
|
|
161
|
+
}
|
|
162
|
+
const readOnlyRoots = allowed.filter((path) => !isWithin(workspace, path));
|
|
163
|
+
const editDenyRoots = compactRoots([...denyRoots, ...readOnlyRoots, controlDir]);
|
|
164
|
+
const [readDenials, writeDenials] = await Promise.all([
|
|
165
|
+
Promise.all(denyRoots.map((root) => carvedPermissionDenials("Read", root, allowed))),
|
|
166
|
+
Promise.all(editDenyRoots.map((root) => carvedPermissionDenials("Edit", root, []))),
|
|
167
|
+
]);
|
|
168
|
+
const denyRules = [...readDenials.flat(), ...writeDenials.flat()];
|
|
169
|
+
const sandboxSettings = {
|
|
170
|
+
disableAllHooks: true,
|
|
171
|
+
permissions: { deny: denyRules },
|
|
172
|
+
sandbox: {
|
|
173
|
+
enabled: true,
|
|
174
|
+
failIfUnavailable: true,
|
|
175
|
+
allowUnsandboxedCommands: false,
|
|
176
|
+
filesystem: {
|
|
177
|
+
denyRead: denyRoots,
|
|
178
|
+
denyWrite: [...denyRoots, controlDir],
|
|
179
|
+
allowRead: allowed,
|
|
180
|
+
},
|
|
181
|
+
// Filesystem isolation does not disable the live docs and package
|
|
182
|
+
// registry access that cases are intended to exercise.
|
|
183
|
+
network: { allowedDomains: ["*"] },
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
const tmpDir = join(stateDir, "tmp");
|
|
187
|
+
const cacheDir = join(stateDir, "cache");
|
|
188
|
+
const pluginCacheDir = join(stateDir, "plugins");
|
|
189
|
+
await Promise.all([
|
|
190
|
+
mkdir(tmpDir, { recursive: true }),
|
|
191
|
+
mkdir(cacheDir, { recursive: true }),
|
|
192
|
+
mkdir(pluginCacheDir, { recursive: true }),
|
|
193
|
+
]);
|
|
194
|
+
return {
|
|
195
|
+
settings: sandboxSettings,
|
|
196
|
+
env: {
|
|
197
|
+
...(config.env ?? {}),
|
|
198
|
+
...(config.interactionMode === "defer-resume"
|
|
199
|
+
? {
|
|
200
|
+
CLAUDE_CONFIG_DIR: configDir,
|
|
201
|
+
CLAUDE_SECURESTORAGE_CONFIG_DIR: authConfigOverride,
|
|
202
|
+
}
|
|
203
|
+
: {}),
|
|
204
|
+
...(cleanShell ? { CLAUDE_CODE_SHELL: cleanShell } : {}),
|
|
205
|
+
// Hook shells launch outside the subject sandbox; clear their startup inputs too.
|
|
206
|
+
BASH_ENV: "",
|
|
207
|
+
ENV: "",
|
|
208
|
+
ZDOTDIR: controlDir,
|
|
209
|
+
CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
|
|
210
|
+
CLAUDE_CODE_DISABLE_CLAUDE_MDS: "1",
|
|
211
|
+
// The CLI checks presence, not boolean parsing: "0" still disables resume.
|
|
212
|
+
CLAUDE_CODE_SKIP_PROMPT_HISTORY: config.interactionMode === "defer-resume" ? "" : "1",
|
|
213
|
+
...(config.interactionMode === "defer-resume"
|
|
214
|
+
? { CLAUDE_CODE_FORCE_SESSION_PERSISTENCE: "1" }
|
|
215
|
+
: {}),
|
|
216
|
+
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
|
|
217
|
+
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: "1",
|
|
218
|
+
CLAUDE_CODE_AUTO_CONNECT_IDE: "false",
|
|
219
|
+
CLAUDE_CODE_PLUGIN_CACHE_DIR: pluginCacheDir,
|
|
220
|
+
CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL: "1",
|
|
221
|
+
DISABLE_AUTOUPDATER: "1",
|
|
222
|
+
TMPDIR: tmpDir,
|
|
223
|
+
XDG_CACHE_HOME: cacheDir,
|
|
224
|
+
npm_config_cache: join(cacheDir, "npm"),
|
|
225
|
+
BUN_INSTALL_CACHE_DIR: join(cacheDir, "bun"),
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { InheritEnvironmentSchema } from "../../core/agent-environment.js";
|
|
2
3
|
import { DurationSchema } from "../../core/duration.js";
|
|
3
4
|
import { applyCatalogEffort, codexCatalog, DEFAULT_CODEX_MODEL } from "../../core/index.js";
|
|
4
5
|
|
|
@@ -36,6 +37,7 @@ export const CodexDriverConfigSchema = z
|
|
|
36
37
|
cwdMode: z.enum(["artifacts", "runDir"]).default("artifacts"),
|
|
37
38
|
cwd: z.string().optional(),
|
|
38
39
|
env: z.record(z.string(), z.string()).optional(),
|
|
40
|
+
inheritEnv: InheritEnvironmentSchema.optional(),
|
|
39
41
|
addDirs: z.array(z.string()).optional(),
|
|
40
42
|
profile: z.string().optional(),
|
|
41
43
|
config: z.record(z.string(), CodexConfigValueSchema).optional(),
|
|
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { temporaryDirectory } from "tempy";
|
|
6
|
+
import { agentEnvironment } from "../../core/agent-environment.js";
|
|
6
7
|
import { durationMs } from "../../core/duration.js";
|
|
7
8
|
import {
|
|
8
9
|
type AgentDriver,
|
|
@@ -18,10 +19,11 @@ import {
|
|
|
18
19
|
type TokenUsage,
|
|
19
20
|
TypedEmitter,
|
|
20
21
|
} from "../../core/index.js";
|
|
22
|
+
import { resolveExecutable } from "../../core/platform-process.js";
|
|
23
|
+
import { copyRegularFileNoFollow } from "../../core/secure-copy.js";
|
|
21
24
|
import { walkArtifacts } from "../shared/artifacts.js";
|
|
22
25
|
import { configuredClaudeSourceHashes } from "../shared/experiment-behavior.js";
|
|
23
26
|
import { prepareCodexExperimentConfig } from "../shared/experiment-policy.js";
|
|
24
|
-
import { copyRegularFileNoFollow } from "../shared/secure-copy.js";
|
|
25
27
|
import { subprocessDriver } from "../subprocess/index.js";
|
|
26
28
|
import {
|
|
27
29
|
type AppTurnFacets,
|
|
@@ -59,6 +61,7 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
59
61
|
private runDir: string | null = null;
|
|
60
62
|
private isolatedWorkspace: string | null = null;
|
|
61
63
|
private isolatedStateDir: string | null = null;
|
|
64
|
+
private isolationConfig: Record<string, CodexConfigValue> = {};
|
|
62
65
|
private threadId: string | null = null;
|
|
63
66
|
private cliVersion: string | undefined;
|
|
64
67
|
private nextRequestId = 1;
|
|
@@ -96,6 +99,7 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
96
99
|
let cwd =
|
|
97
100
|
this.config.cwd ?? (this.config.cwdMode === "artifacts" ? ctx.artifactsDir : ctx.runDir);
|
|
98
101
|
let env = this.config.env;
|
|
102
|
+
let launchCommand = this.config.command;
|
|
99
103
|
if (this.config.isolation) {
|
|
100
104
|
const workspace = await realpath(temporaryDirectory({ prefix: "lux-codex-workspace-" }));
|
|
101
105
|
await cp(ctx.artifactsDir, workspace, { recursive: true });
|
|
@@ -123,6 +127,45 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
123
127
|
CODEX_CONFIG_HOME: stateDir,
|
|
124
128
|
};
|
|
125
129
|
}
|
|
130
|
+
if (this.config.isolation) {
|
|
131
|
+
const reads = await Promise.all(
|
|
132
|
+
this.config.isolation.allowRead.map((path) => realpath(path)),
|
|
133
|
+
);
|
|
134
|
+
const executable = resolveExecutable(
|
|
135
|
+
this.config.command,
|
|
136
|
+
{ ...agentEnvironment(process.env, this.config.inheritEnv), ...env },
|
|
137
|
+
undefined,
|
|
138
|
+
cwd,
|
|
139
|
+
);
|
|
140
|
+
if (executable) {
|
|
141
|
+
launchCommand = await realpath(executable);
|
|
142
|
+
reads.push(executable, launchCommand);
|
|
143
|
+
}
|
|
144
|
+
// Native filesystem tools relaunch the CLI inside the subject sandbox.
|
|
145
|
+
// Workspace requirements reload process configuration during a turn.
|
|
146
|
+
// A thread-only profile is accepted at startup but fails that reload.
|
|
147
|
+
this.isolationConfig = {
|
|
148
|
+
allow_login_shell: false,
|
|
149
|
+
project_doc_max_bytes: 0,
|
|
150
|
+
projects: { [cwd]: { trust_level: "untrusted" } },
|
|
151
|
+
default_permissions: "lux-isolated",
|
|
152
|
+
permissions: {
|
|
153
|
+
"lux-isolated": {
|
|
154
|
+
filesystem: {
|
|
155
|
+
":minimal": "read",
|
|
156
|
+
[cwd]: "write",
|
|
157
|
+
...Object.fromEntries(reads.map((path) => [path, "read"])),
|
|
158
|
+
},
|
|
159
|
+
network: { enabled: true },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
features: {
|
|
163
|
+
...((this.config.config?.features as Record<string, CodexConfigValue>) ?? {}),
|
|
164
|
+
shell_snapshot: false,
|
|
165
|
+
shell_snapshot_v2: false,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
126
169
|
this.collectDir = cwd;
|
|
127
170
|
this.artifactsDir = ctx.artifactsDir;
|
|
128
171
|
this.runDir = ctx.runDir;
|
|
@@ -133,16 +176,23 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
133
176
|
commandName(this.config.command) === "codex"
|
|
134
177
|
? await discoverCliVersion(this.config.command)
|
|
135
178
|
: undefined;
|
|
136
|
-
const managed = spawnManagedProcess(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
...
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
179
|
+
const managed = spawnManagedProcess(
|
|
180
|
+
launchCommand,
|
|
181
|
+
buildAppServerArgs({
|
|
182
|
+
...this.config,
|
|
183
|
+
config: { ...this.config.config, ...this.isolationConfig },
|
|
184
|
+
}),
|
|
185
|
+
{
|
|
186
|
+
cwd,
|
|
187
|
+
env: {
|
|
188
|
+
...agentEnvironment(process.env, this.config.inheritEnv),
|
|
189
|
+
LUX_RUN_DIR: ctx.runDir,
|
|
190
|
+
LUX_ARTIFACTS_DIR: ctx.artifactsDir,
|
|
191
|
+
...env,
|
|
192
|
+
},
|
|
193
|
+
signal: ctx.abortSignal,
|
|
143
194
|
},
|
|
144
|
-
|
|
145
|
-
});
|
|
195
|
+
);
|
|
146
196
|
this.process = managed;
|
|
147
197
|
managed.child.stderr.setEncoding("utf8");
|
|
148
198
|
let stderrTail = "";
|
|
@@ -187,35 +237,19 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
187
237
|
capabilities: { experimentalApi: true, requestAttestation: false },
|
|
188
238
|
});
|
|
189
239
|
this.notify("initialized");
|
|
190
|
-
const isolationReads = this.config.isolation
|
|
191
|
-
? await Promise.all(this.config.isolation.allowRead.map((path) => realpath(path)))
|
|
192
|
-
: [];
|
|
193
240
|
const threadConfig: Record<string, CodexConfigValue> = {
|
|
194
241
|
...codexConfigOverrides(this.config),
|
|
195
242
|
...(this.config.reasoningEffort
|
|
196
243
|
? { model_reasoning_effort: this.config.reasoningEffort }
|
|
197
244
|
: {}),
|
|
198
|
-
...
|
|
199
|
-
? {
|
|
200
|
-
project_doc_max_bytes: 0,
|
|
201
|
-
projects: { [cwd]: { trust_level: "untrusted" } },
|
|
202
|
-
permissions: {
|
|
203
|
-
"lux-isolated": {
|
|
204
|
-
filesystem: {
|
|
205
|
-
":minimal": "read",
|
|
206
|
-
":workspace_roots": { ".": "write" },
|
|
207
|
-
...Object.fromEntries(isolationReads.map((path) => [path, "read"])),
|
|
208
|
-
},
|
|
209
|
-
network: { enabled: true },
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
}
|
|
213
|
-
: {}),
|
|
245
|
+
...this.isolationConfig,
|
|
214
246
|
};
|
|
215
247
|
const started = await this.request("thread/start", {
|
|
216
248
|
model: this.config.model,
|
|
217
249
|
cwd,
|
|
218
|
-
runtimeWorkspaceRoots: this.config.
|
|
250
|
+
runtimeWorkspaceRoots: this.config.isolation
|
|
251
|
+
? [cwd, ...(this.config.addDirs ?? [])]
|
|
252
|
+
: (this.config.addDirs ?? []),
|
|
219
253
|
approvalPolicy: this.config.approvalPolicy,
|
|
220
254
|
approvalsReviewer: this.config.approvalsReviewer,
|
|
221
255
|
...(this.config.isolation
|
|
@@ -348,6 +382,11 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
348
382
|
: withRoot;
|
|
349
383
|
}
|
|
350
384
|
|
|
385
|
+
async verificationWorkspace(): Promise<string | undefined> {
|
|
386
|
+
await this.quiesce();
|
|
387
|
+
return this.isolatedWorkspace ?? undefined;
|
|
388
|
+
}
|
|
389
|
+
|
|
351
390
|
async quiesce(): Promise<void> {
|
|
352
391
|
this.clearIdleTimer();
|
|
353
392
|
await this.process?.terminate();
|
|
@@ -714,7 +753,8 @@ class CodexExecDriverImpl implements AgentDriver {
|
|
|
714
753
|
command: this.config.command,
|
|
715
754
|
args: buildArgs(this.config),
|
|
716
755
|
cwd,
|
|
717
|
-
env: this.config.env,
|
|
756
|
+
env: agentEnvironment(process.env, this.config.inheritEnv, this.config.env),
|
|
757
|
+
inheritEnv: false,
|
|
718
758
|
protocol: "custom",
|
|
719
759
|
parseEvent: makeParseEvent(this.config, cliVersion),
|
|
720
760
|
formatPrompt: (prompt) => prompt,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { InheritEnvironmentSchema } from "../../core/agent-environment.js";
|
|
2
3
|
import { DurationSchema } from "../../core/duration.js";
|
|
3
4
|
import { applyCatalogEffort, copilotCatalog } from "../../core/index.js";
|
|
4
5
|
|
|
@@ -24,6 +25,7 @@ export const CopilotDriverConfigSchema = z
|
|
|
24
25
|
extraArgs: z.array(z.string()).optional(),
|
|
25
26
|
cwd: z.string().optional(),
|
|
26
27
|
env: z.record(z.string(), z.string()).optional(),
|
|
28
|
+
inheritEnv: InheritEnvironmentSchema.optional(),
|
|
27
29
|
startupTimeout: DurationSchema.default("30 seconds"),
|
|
28
30
|
idleTimeout: DurationSchema.default("10 minutes"),
|
|
29
31
|
maxInterrupts: z.number().int().positive().default(20),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { agentEnvironment } from "../../core/agent-environment.js";
|
|
2
3
|
import { durationMs } from "../../core/duration.js";
|
|
3
4
|
import {
|
|
4
5
|
type AgentDriver,
|
|
@@ -70,7 +71,7 @@ class CopilotDriver implements AgentDriver {
|
|
|
70
71
|
command: this.config.command,
|
|
71
72
|
args: buildCopilotArgs(this.config),
|
|
72
73
|
cwd: this.cwd,
|
|
73
|
-
env:
|
|
74
|
+
env: agentEnvironment(process.env, this.config.inheritEnv, this.config.env),
|
|
74
75
|
signal: ctx.abortSignal,
|
|
75
76
|
maxLineBytes: this.config.maxLineBytes,
|
|
76
77
|
maxQueuedBytes: this.config.maxQueuedBytes,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { InheritEnvironmentSchema } from "../../core/agent-environment.js";
|
|
2
3
|
import { DurationSchema } from "../../core/duration.js";
|
|
3
4
|
import { cursorCatalog } from "../../core/index.js";
|
|
4
5
|
|
|
@@ -10,6 +11,7 @@ export const CursorDriverConfigSchema = z
|
|
|
10
11
|
mode: z.enum(["agent", "plan", "ask"]).default("agent"),
|
|
11
12
|
cwd: z.string().optional(),
|
|
12
13
|
env: z.record(z.string(), z.string()).optional(),
|
|
14
|
+
inheritEnv: InheritEnvironmentSchema.optional(),
|
|
13
15
|
startupTimeout: DurationSchema.default("30 seconds"),
|
|
14
16
|
idleTimeout: DurationSchema.default("10 minutes"),
|
|
15
17
|
maxInterrupts: z.number().int().positive().default(20),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { agentEnvironment } from "../../core/agent-environment.js";
|
|
2
3
|
import { durationMs } from "../../core/duration.js";
|
|
3
4
|
import {
|
|
4
5
|
type AgentDriver,
|
|
@@ -65,7 +66,7 @@ class CursorDriver implements AgentDriver {
|
|
|
65
66
|
command: this.config.command,
|
|
66
67
|
args: buildCursorArgs(this.config),
|
|
67
68
|
cwd: this.cwd,
|
|
68
|
-
env:
|
|
69
|
+
env: agentEnvironment(process.env, this.config.inheritEnv, this.config.env),
|
|
69
70
|
signal: ctx.abortSignal,
|
|
70
71
|
maxLineBytes: this.config.maxLineBytes,
|
|
71
72
|
maxQueuedBytes: this.config.maxQueuedBytes,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { InheritEnvironmentSchema } from "../../core/agent-environment.js";
|
|
2
3
|
import { DurationSchema } from "../../core/duration.js";
|
|
3
4
|
import { applyCatalogEffort, grokBuildCatalog } from "../../core/index.js";
|
|
4
5
|
|
|
@@ -12,6 +13,7 @@ export const GrokBuildDriverConfigSchema = z
|
|
|
12
13
|
sandbox: z.enum(["workspace", "read-only", "strict"]).default("workspace"),
|
|
13
14
|
cwd: z.string().optional(),
|
|
14
15
|
env: z.record(z.string(), z.string()).optional(),
|
|
16
|
+
inheritEnv: InheritEnvironmentSchema.optional(),
|
|
15
17
|
startupTimeout: DurationSchema.default("30 seconds"),
|
|
16
18
|
idleTimeout: DurationSchema.default("10 minutes"),
|
|
17
19
|
permissionTimeout: DurationSchema.default("150 seconds"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { agentEnvironment } from "../../core/agent-environment.js";
|
|
2
3
|
import { durationMs } from "../../core/duration.js";
|
|
3
4
|
import {
|
|
4
5
|
type AgentDriver,
|
|
@@ -70,7 +71,7 @@ class GrokBuildDriver implements AgentDriver {
|
|
|
70
71
|
command: this.config.command,
|
|
71
72
|
args: buildGrokBuildArgs(this.config),
|
|
72
73
|
cwd: this.cwd,
|
|
73
|
-
env:
|
|
74
|
+
env: agentEnvironment(process.env, this.config.inheritEnv, this.config.env),
|
|
74
75
|
signal: ctx.abortSignal,
|
|
75
76
|
maxLineBytes: this.config.maxLineBytes,
|
|
76
77
|
maxQueuedBytes: this.config.maxQueuedBytes,
|
|
@@ -2,6 +2,7 @@ import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { agentEnvironment, InheritEnvironmentSchema } from "../../core/agent-environment.js";
|
|
5
6
|
import { DurationSchema, durationMs } from "../../core/duration.js";
|
|
6
7
|
import {
|
|
7
8
|
type AgentDriver,
|
|
@@ -35,7 +36,7 @@ export const SubprocessDriverConfigSchema = z
|
|
|
35
36
|
cwd: z.string().optional(),
|
|
36
37
|
env: z.record(z.string(), z.string()).optional(),
|
|
37
38
|
/** Inherit the host environment. Experiment mode always forces this off. */
|
|
38
|
-
inheritEnv: z.
|
|
39
|
+
inheritEnv: z.union([InheritEnvironmentSchema, z.literal(false)]).optional(),
|
|
39
40
|
protocol: z.enum(["lux-jsonl", "custom"]).default("lux-jsonl"),
|
|
40
41
|
idleTimeout: DurationSchema.default("2 minutes"),
|
|
41
42
|
maxInterrupts: z.number().positive().default(20),
|
|
@@ -151,7 +152,9 @@ class SubprocessDriverImpl implements AgentDriver {
|
|
|
151
152
|
const managed = spawnManagedProcess(this.config.command, this.config.args ?? [], {
|
|
152
153
|
cwd: this.config.cwd ?? ctx.artifactsDir,
|
|
153
154
|
env: {
|
|
154
|
-
...(this.config.inheritEnv === false
|
|
155
|
+
...(this.config.inheritEnv === false
|
|
156
|
+
? {}
|
|
157
|
+
: agentEnvironment(process.env, this.config.inheritEnv)),
|
|
155
158
|
...this.config.env,
|
|
156
159
|
...(ctx.caseId ? { LUX_CASE_ID: ctx.caseId } : {}),
|
|
157
160
|
LUX_RUN_DIR: ctx.runDir,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
type Artifact,
|
|
4
|
+
contentHash,
|
|
5
|
+
inspectRunArtifact,
|
|
6
|
+
readRunArtifactBytes,
|
|
7
|
+
} from "../core/index.js";
|
|
8
|
+
|
|
9
|
+
const MAX_ORIGIN_BYTES = 10 * 1024 * 1024;
|
|
10
|
+
|
|
11
|
+
export const identifyArtifactOrigins = async (
|
|
12
|
+
runDir: string,
|
|
13
|
+
artifacts: readonly Artifact[],
|
|
14
|
+
fixtureHashes: ReadonlyMap<string, string>,
|
|
15
|
+
): Promise<Artifact[]> => {
|
|
16
|
+
const run = { runDir, artifacts };
|
|
17
|
+
const result: Artifact[] = [];
|
|
18
|
+
for (const artifact of artifacts) {
|
|
19
|
+
const { origin: _claimedOrigin, ...captured } = artifact;
|
|
20
|
+
const inspected = await inspectRunArtifact(run, artifact.path);
|
|
21
|
+
if (!inspected.stats?.isFile()) {
|
|
22
|
+
result.push(captured);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const path = relative(resolve(runDir, "artifacts"), inspected.target);
|
|
26
|
+
const expected =
|
|
27
|
+
!isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`)
|
|
28
|
+
? fixtureHashes.get(path)
|
|
29
|
+
: undefined;
|
|
30
|
+
if (expected === undefined) {
|
|
31
|
+
result.push({ ...captured, origin: "generated" });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (inspected.stats.size > MAX_ORIGIN_BYTES) {
|
|
35
|
+
result.push(captured);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const bytes = await readRunArtifactBytes(run, artifact.path, { maxBytes: MAX_ORIGIN_BYTES });
|
|
40
|
+
result.push({
|
|
41
|
+
...captured,
|
|
42
|
+
origin: contentHash(bytes) === expected ? "fixture" : "modified",
|
|
43
|
+
});
|
|
44
|
+
} catch {
|
|
45
|
+
// Advisory provenance must not discard the rest of a safely archived index.
|
|
46
|
+
result.push(captured);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
@@ -130,14 +130,17 @@ export const agentCorpusHash = async (
|
|
|
130
130
|
execution.loaded.campaign.profiles.map(async (profileId) => {
|
|
131
131
|
const profile = profileOrThrow(execution.config, profileId);
|
|
132
132
|
const harness = profile.harness ?? execution.config.harness;
|
|
133
|
-
const driver =
|
|
134
|
-
bound,
|
|
135
|
-
|
|
133
|
+
const driver = bindSubjectRoot(
|
|
134
|
+
resolveDriverReference(bound, profile.driver ?? execution.config.defaultDriver),
|
|
135
|
+
execution.loaded.subjectRoot,
|
|
136
136
|
);
|
|
137
|
-
const answerer =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const answerer = bindSubjectRoot(
|
|
138
|
+
resolveAnswererReference(
|
|
139
|
+
bound,
|
|
140
|
+
profile.answerer ?? execution.config.defaultAnswerer,
|
|
141
|
+
harness,
|
|
142
|
+
),
|
|
143
|
+
execution.loaded.subjectRoot,
|
|
141
144
|
);
|
|
142
145
|
const answererCli = referencedAnswererCli(answerer);
|
|
143
146
|
return {
|
|
@@ -131,9 +131,9 @@ const trustedExecutableEntrypoints = async (
|
|
|
131
131
|
);
|
|
132
132
|
for (const profileId of execution.loaded.campaign.profiles) {
|
|
133
133
|
const profile = profileOrThrow(execution.config, profileId);
|
|
134
|
-
const driver =
|
|
135
|
-
authored,
|
|
136
|
-
|
|
134
|
+
const driver = bindSubjectRoot(
|
|
135
|
+
resolveDriverReference(authored, profile.driver ?? execution.config.defaultDriver),
|
|
136
|
+
execution.loaded.subjectRoot,
|
|
137
137
|
);
|
|
138
138
|
for (const command of referencedDriverRuntime(driver, execution.registry).commands)
|
|
139
139
|
add(command);
|
|
@@ -197,14 +197,17 @@ export const runtimeCliReferences = async (
|
|
|
197
197
|
for (const profileId of execution.loaded.campaign.profiles) {
|
|
198
198
|
const profile = profileOrThrow(execution.config, profileId);
|
|
199
199
|
const harness = profile.harness ?? execution.config.harness;
|
|
200
|
-
const driver =
|
|
201
|
-
authored,
|
|
202
|
-
|
|
200
|
+
const driver = bindSubjectRoot(
|
|
201
|
+
resolveDriverReference(authored, profile.driver ?? execution.config.defaultDriver),
|
|
202
|
+
execution.loaded.subjectRoot,
|
|
203
203
|
);
|
|
204
|
-
const answerer =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
const answerer = bindSubjectRoot(
|
|
205
|
+
resolveAnswererReference(
|
|
206
|
+
authored,
|
|
207
|
+
profile.answerer ?? execution.config.defaultAnswerer,
|
|
208
|
+
harness,
|
|
209
|
+
),
|
|
210
|
+
execution.loaded.subjectRoot,
|
|
208
211
|
);
|
|
209
212
|
for (const command of referencedDriverRuntime(driver, execution.registry).commands)
|
|
210
213
|
add(command, execution.config.rootDir);
|
|
@@ -271,9 +274,9 @@ export const validateAgentCaseProfileDrivers = async (
|
|
|
271
274
|
);
|
|
272
275
|
for (const profileId of execution.loaded.campaign.profiles) {
|
|
273
276
|
const profile = profileOrThrow(execution.config, profileId);
|
|
274
|
-
const reference =
|
|
275
|
-
authored,
|
|
276
|
-
|
|
277
|
+
const reference = bindSubjectRoot(
|
|
278
|
+
resolveDriverReference(authored, profile.driver ?? execution.config.defaultDriver),
|
|
279
|
+
execution.loaded.subjectRoot,
|
|
277
280
|
);
|
|
278
281
|
const name = typeof reference === "string" ? reference : reference.name;
|
|
279
282
|
const rawConfig = typeof reference === "object" ? reference.config : undefined;
|