@prismatic-io/lux 0.0.2-preview.20 → 0.0.2-preview.22
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/assertions/rubric/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.js +1 -0
- package/lib/assertions/rubric/index.js.map +1 -1
- package/lib/assertions/rubric/internal.d.ts.map +1 -1
- package/lib/assertions/rubric/internal.js +42 -8
- package/lib/assertions/rubric/internal.js.map +1 -1
- package/lib/drivers/antigravity/config.d.ts +7 -1
- package/lib/drivers/antigravity/config.d.ts.map +1 -1
- package/lib/drivers/antigravity/config.js +8 -2
- package/lib/drivers/antigravity/config.js.map +1 -1
- package/lib/drivers/antigravity/events.d.ts.map +1 -1
- package/lib/drivers/antigravity/events.js +54 -6
- package/lib/drivers/antigravity/events.js.map +1 -1
- package/lib/drivers/antigravity/index.d.ts +8 -0
- package/lib/drivers/antigravity/index.d.ts.map +1 -1
- package/lib/drivers/antigravity/index.js +4 -3
- package/lib/drivers/antigravity/index.js.map +1 -1
- package/lib/drivers/claude-code/index.d.ts.map +1 -1
- package/lib/drivers/claude-code/index.js +6 -2
- package/lib/drivers/claude-code/index.js.map +1 -1
- package/lib/drivers/codex/config.d.ts +7 -0
- package/lib/drivers/codex/config.d.ts.map +1 -1
- package/lib/drivers/codex/config.js +26 -2
- package/lib/drivers/codex/config.js.map +1 -1
- package/lib/drivers/codex/index.d.ts +7 -0
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +8 -3
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/subprocess/index.js +1 -1
- package/lib/drivers/subprocess/index.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +15 -3
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/run-workspace.d.ts +10 -0
- package/lib/orchestrator/run-workspace.d.ts.map +1 -0
- package/lib/orchestrator/run-workspace.js +58 -0
- package/lib/orchestrator/run-workspace.js.map +1 -0
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/assertions/rubric/index.ts +1 -0
- package/src/assertions/rubric/internal.ts +35 -7
- package/src/drivers/antigravity/README.md +18 -6
- package/src/drivers/antigravity/config.ts +8 -2
- package/src/drivers/antigravity/events.ts +61 -6
- package/src/drivers/antigravity/index.ts +7 -3
- package/src/drivers/claude-code/index.ts +6 -2
- package/src/drivers/codex/config.ts +35 -2
- package/src/drivers/codex/index.ts +12 -2
- package/src/drivers/subprocess/index.ts +1 -1
- package/src/orchestrator/run-execution.ts +16 -3
- package/src/orchestrator/run-workspace.ts +62 -0
|
@@ -46,6 +46,8 @@ export const CodexDriverConfigSchema = z
|
|
|
46
46
|
strictConfig: z.boolean().default(false),
|
|
47
47
|
interactionMode: z.enum(["app-server", "exec"]).default("app-server"),
|
|
48
48
|
approvalPolicy: z.enum(["untrusted", "on-request", "never"]).default("on-request"),
|
|
49
|
+
/** Native reviewer for eligible approvals; auto_review requires app-server and on-request. */
|
|
50
|
+
approvalsReviewer: z.enum(["user", "auto_review"]).default("user"),
|
|
49
51
|
isolation: IsolationSchema.optional(),
|
|
50
52
|
maxInterrupts: z.number().positive().default(20),
|
|
51
53
|
maxLineBytes: z.number().positive().default(10_000_000),
|
|
@@ -53,6 +55,16 @@ export const CodexDriverConfigSchema = z
|
|
|
53
55
|
})
|
|
54
56
|
.strict()
|
|
55
57
|
.superRefine((config, ctx) => {
|
|
58
|
+
if (
|
|
59
|
+
config.approvalsReviewer === "auto_review" &&
|
|
60
|
+
(config.interactionMode !== "app-server" || config.approvalPolicy !== "on-request")
|
|
61
|
+
) {
|
|
62
|
+
ctx.addIssue({
|
|
63
|
+
code: "custom",
|
|
64
|
+
path: ["approvalsReviewer"],
|
|
65
|
+
message: "auto_review requires app-server interaction mode and on-request approval policy",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
56
68
|
if (config.interactionMode === "app-server" && config.profile) {
|
|
57
69
|
ctx.addIssue({
|
|
58
70
|
code: "custom",
|
|
@@ -103,6 +115,27 @@ const tomlValue = (value: CodexConfigValue): string => {
|
|
|
103
115
|
.join(",")}}`;
|
|
104
116
|
};
|
|
105
117
|
|
|
118
|
+
// Codex shell snapshots can replace the launching process's PATH. Pin only
|
|
119
|
+
// executable-search variables here; never put other environment secrets in argv.
|
|
120
|
+
export const codexConfigOverrides = (
|
|
121
|
+
config: CodexDriverConfig,
|
|
122
|
+
): Record<string, CodexConfigValue> => {
|
|
123
|
+
const authored = config.config ?? {};
|
|
124
|
+
const paths = Object.fromEntries(
|
|
125
|
+
Object.entries(config.env ?? {}).filter(
|
|
126
|
+
([key]) => key.toUpperCase() === "PATH" || key.toUpperCase() === "PATHEXT",
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
if (Object.keys(paths).length === 0) return authored;
|
|
130
|
+
const object = (value: CodexConfigValue | undefined): Record<string, CodexConfigValue> =>
|
|
131
|
+
value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
132
|
+
const policy = object(authored.shell_environment_policy);
|
|
133
|
+
return {
|
|
134
|
+
...authored,
|
|
135
|
+
shell_environment_policy: { ...policy, set: { ...paths, ...object(policy.set) } },
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
106
139
|
export const buildExecArgs = (config: CodexDriverConfig): string[] => {
|
|
107
140
|
const args = ["exec", "--json", "--color", "never", "--sandbox", config.sandbox];
|
|
108
141
|
args.push("--model", config.model);
|
|
@@ -113,7 +146,7 @@ export const buildExecArgs = (config: CodexDriverConfig): string[] => {
|
|
|
113
146
|
if (config.ignoreRules) args.push("--ignore-rules");
|
|
114
147
|
if (config.strictConfig) args.push("--strict-config");
|
|
115
148
|
for (const directory of config.addDirs ?? []) args.push("--add-dir", directory);
|
|
116
|
-
for (const [key, value] of Object.entries(config
|
|
149
|
+
for (const [key, value] of Object.entries(codexConfigOverrides(config))) {
|
|
117
150
|
args.push("--config", `${key}=${tomlValue(value)}`);
|
|
118
151
|
}
|
|
119
152
|
args.push(...effortArgs(config));
|
|
@@ -125,7 +158,7 @@ export const buildExecArgs = (config: CodexDriverConfig): string[] => {
|
|
|
125
158
|
export const buildAppServerArgs = (config: CodexDriverConfig): string[] => {
|
|
126
159
|
const args = ["app-server", "--stdio"];
|
|
127
160
|
if (config.strictConfig) args.push("--strict-config");
|
|
128
|
-
for (const [key, value] of Object.entries(config
|
|
161
|
+
for (const [key, value] of Object.entries(codexConfigOverrides(config))) {
|
|
129
162
|
args.push("--config", `${key}=${tomlValue(value)}`);
|
|
130
163
|
}
|
|
131
164
|
args.push(...effortArgs(config));
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
type CodexConfigValue,
|
|
42
42
|
type CodexDriverConfig,
|
|
43
43
|
CodexDriverConfigSchema,
|
|
44
|
+
codexConfigOverrides,
|
|
44
45
|
} from "./config.js";
|
|
45
46
|
|
|
46
47
|
import { makeParseEvent } from "./exec-events.js";
|
|
@@ -189,7 +190,7 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
189
190
|
? await Promise.all(this.config.isolation.allowRead.map((path) => realpath(path)))
|
|
190
191
|
: [];
|
|
191
192
|
const threadConfig: Record<string, CodexConfigValue> = {
|
|
192
|
-
...(this.config
|
|
193
|
+
...codexConfigOverrides(this.config),
|
|
193
194
|
...(this.config.reasoningEffort
|
|
194
195
|
? { model_reasoning_effort: this.config.reasoningEffort }
|
|
195
196
|
: {}),
|
|
@@ -215,13 +216,21 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
215
216
|
cwd,
|
|
216
217
|
runtimeWorkspaceRoots: this.config.addDirs ?? [],
|
|
217
218
|
approvalPolicy: this.config.approvalPolicy,
|
|
218
|
-
approvalsReviewer:
|
|
219
|
+
approvalsReviewer: this.config.approvalsReviewer,
|
|
219
220
|
...(this.config.isolation
|
|
220
221
|
? { permissions: "lux-isolated" }
|
|
221
222
|
: { sandbox: this.config.sandbox }),
|
|
222
223
|
config: threadConfig,
|
|
223
224
|
ephemeral: this.config.ephemeral,
|
|
224
225
|
});
|
|
226
|
+
if (
|
|
227
|
+
this.config.approvalsReviewer === "auto_review" &&
|
|
228
|
+
started.approvalsReviewer !== "auto_review"
|
|
229
|
+
) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
"codex app-server did not enable the requested auto_review approvals reviewer",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
225
234
|
const thread = asRecord(started.thread);
|
|
226
235
|
if (!thread || typeof thread.id !== "string") {
|
|
227
236
|
throw new Error("codex app-server returned an invalid thread/start response");
|
|
@@ -232,6 +241,7 @@ class CodexAppServerDriverImpl implements AgentDriver {
|
|
|
232
241
|
model: typeof started.model === "string" ? started.model : this.config.model,
|
|
233
242
|
detail: {
|
|
234
243
|
threadId: thread.id,
|
|
244
|
+
approvalsReviewer: this.config.approvalsReviewer,
|
|
235
245
|
...(typeof thread.sessionId === "string" ? { sessionId: thread.sessionId } : {}),
|
|
236
246
|
...(this.cliVersion ? { cliVersion: this.cliVersion } : {}),
|
|
237
247
|
},
|
|
@@ -148,7 +148,7 @@ class SubprocessDriverImpl implements AgentDriver {
|
|
|
148
148
|
throw new Error(`subprocess fixture values exceed ${MAX_FIXTURE_VALUES_BYTES} bytes`);
|
|
149
149
|
}
|
|
150
150
|
const managed = spawnManagedProcess(this.config.command, this.config.args ?? [], {
|
|
151
|
-
cwd: this.config.cwd ?? ctx.
|
|
151
|
+
cwd: this.config.cwd ?? ctx.artifactsDir,
|
|
152
152
|
env: {
|
|
153
153
|
...(this.config.inheritEnv === false ? {} : process.env),
|
|
154
154
|
...this.config.env,
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
type RunFailurePhase,
|
|
54
54
|
type RunLifecycleEffects,
|
|
55
55
|
} from "./run-lifecycle.js";
|
|
56
|
+
import { RunWorkspace } from "./run-workspace.js";
|
|
56
57
|
import { isRunTraceLimit, type RunTraceLimitFailure } from "./trace-limits.js";
|
|
57
58
|
|
|
58
59
|
/** Sentinel `casePath` for a pre-loaded case that has no file behind it. */
|
|
@@ -313,6 +314,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
313
314
|
private metadata: RunMetadata | undefined;
|
|
314
315
|
private usage: RunUsage | undefined;
|
|
315
316
|
private run: Run | undefined;
|
|
317
|
+
private workspace: RunWorkspace | undefined;
|
|
316
318
|
|
|
317
319
|
private constructor(
|
|
318
320
|
args: RunWithPluginsArgs,
|
|
@@ -369,10 +371,11 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
369
371
|
const { request, evalCase, casePath, driver } = this.args;
|
|
370
372
|
this.lifecycle("run-loop");
|
|
371
373
|
if (request.abortSignal?.aborted) return;
|
|
374
|
+
this.workspace = await RunWorkspace.create();
|
|
372
375
|
const staged = await stageFixtures(evalCase.fixtures, {
|
|
373
376
|
...(request.fixturesRoot ? { fixturesRoot: request.fixturesRoot } : {}),
|
|
374
377
|
casePath,
|
|
375
|
-
artifactsDir: this.
|
|
378
|
+
artifactsDir: this.workspace.path,
|
|
376
379
|
});
|
|
377
380
|
this.fixturesHash = staged?.hash;
|
|
378
381
|
if (request.abortSignal?.aborted) return;
|
|
@@ -387,7 +390,7 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
387
390
|
...(this.args.fixtureValues.length > 0 ? { fixtureValues: this.args.fixtureValues } : {}),
|
|
388
391
|
runsRoot: resolve(request.runsRoot),
|
|
389
392
|
runDir: this.runDir.path,
|
|
390
|
-
artifactsDir: this.
|
|
393
|
+
artifactsDir: this.workspace.path,
|
|
391
394
|
abortSignal: request.abortSignal ?? new AbortController().signal,
|
|
392
395
|
});
|
|
393
396
|
this.append({ type: "ready", ...this.ready });
|
|
@@ -463,6 +466,12 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
463
466
|
}
|
|
464
467
|
|
|
465
468
|
async close(primaryError: unknown | null): Promise<void> {
|
|
469
|
+
if (this.workspace) {
|
|
470
|
+
await closeQuietly(async () => {
|
|
471
|
+
await this.args.driver.close();
|
|
472
|
+
await this.workspace?.archive(this.runDir.artifactsDir);
|
|
473
|
+
}, `run workspace ${this.workspace.path}`);
|
|
474
|
+
}
|
|
466
475
|
if (primaryError instanceof Error) {
|
|
467
476
|
(primaryError as Error & RunDirCarrier).luxRunDir = this.runDir.path;
|
|
468
477
|
}
|
|
@@ -567,7 +576,11 @@ class RunExecution implements RunLifecycleEffects<RunResult> {
|
|
|
567
576
|
if (!this.ready) return { artifacts: [] };
|
|
568
577
|
try {
|
|
569
578
|
await this.args.driver.quiesce?.();
|
|
570
|
-
|
|
579
|
+
const artifacts = await this.args.driver.collect();
|
|
580
|
+
return {
|
|
581
|
+
artifacts:
|
|
582
|
+
(await this.workspace?.archive(this.runDir.artifactsDir, artifacts)) ?? artifacts,
|
|
583
|
+
};
|
|
571
584
|
} catch (error) {
|
|
572
585
|
const reason = errorMessage(error);
|
|
573
586
|
this.runDir.events.append({
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { cp, lstat, mkdir, mkdtemp, realpath, rename, rm, rmdir } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import type { Artifact } from "../core/index.js";
|
|
5
|
+
|
|
6
|
+
/** Keep package-manager discovery and repository ignore rules outside subject workspaces. */
|
|
7
|
+
export class RunWorkspace {
|
|
8
|
+
private archived = false;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
|
|
11
|
+
private constructor(path: string) {
|
|
12
|
+
this.path = path;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static async create(): Promise<RunWorkspace> {
|
|
16
|
+
const root = join(tmpdir(), `lux-workspaces-${process.getuid?.() ?? "user"}`);
|
|
17
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
18
|
+
const metadata = await lstat(root);
|
|
19
|
+
if (
|
|
20
|
+
!metadata.isDirectory() ||
|
|
21
|
+
metadata.isSymbolicLink() ||
|
|
22
|
+
(process.getuid && metadata.uid !== process.getuid())
|
|
23
|
+
) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Lux workspace root must be a real directory owned by the current user: ${root}`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return new RunWorkspace(await realpath(await mkdtemp(join(root, "run-"))));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async archive(destination: string, artifacts: Artifact[] = []): Promise<Artifact[]> {
|
|
32
|
+
if (!this.archived) {
|
|
33
|
+
const metadata = await lstat(this.path);
|
|
34
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
35
|
+
throw new Error(`run workspace must remain a real directory: ${this.path}`);
|
|
36
|
+
}
|
|
37
|
+
await rmdir(destination).catch((error: NodeJS.ErrnoException) => {
|
|
38
|
+
if (error.code !== "ENOENT") throw error;
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
await rename(this.path, destination);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if ((error as NodeJS.ErrnoException).code !== "EXDEV") throw error;
|
|
44
|
+
await cp(this.path, destination, { recursive: true, verbatimSymlinks: true });
|
|
45
|
+
await rm(this.path, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
this.archived = true;
|
|
48
|
+
}
|
|
49
|
+
return artifacts.map((artifact) => {
|
|
50
|
+
if (!artifact.root) return artifact;
|
|
51
|
+
const path = relative(this.path, resolve(artifact.root));
|
|
52
|
+
if (path === "") {
|
|
53
|
+
const { root: _root, ...captured } = artifact;
|
|
54
|
+
return captured;
|
|
55
|
+
}
|
|
56
|
+
if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path)) {
|
|
57
|
+
return artifact;
|
|
58
|
+
}
|
|
59
|
+
return { ...artifact, root: join(destination, path) };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|