@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.
Files changed (51) hide show
  1. package/lib/assertions/rubric/index.d.ts.map +1 -1
  2. package/lib/assertions/rubric/index.js +1 -0
  3. package/lib/assertions/rubric/index.js.map +1 -1
  4. package/lib/assertions/rubric/internal.d.ts.map +1 -1
  5. package/lib/assertions/rubric/internal.js +42 -8
  6. package/lib/assertions/rubric/internal.js.map +1 -1
  7. package/lib/drivers/antigravity/config.d.ts +7 -1
  8. package/lib/drivers/antigravity/config.d.ts.map +1 -1
  9. package/lib/drivers/antigravity/config.js +8 -2
  10. package/lib/drivers/antigravity/config.js.map +1 -1
  11. package/lib/drivers/antigravity/events.d.ts.map +1 -1
  12. package/lib/drivers/antigravity/events.js +54 -6
  13. package/lib/drivers/antigravity/events.js.map +1 -1
  14. package/lib/drivers/antigravity/index.d.ts +8 -0
  15. package/lib/drivers/antigravity/index.d.ts.map +1 -1
  16. package/lib/drivers/antigravity/index.js +4 -3
  17. package/lib/drivers/antigravity/index.js.map +1 -1
  18. package/lib/drivers/claude-code/index.d.ts.map +1 -1
  19. package/lib/drivers/claude-code/index.js +6 -2
  20. package/lib/drivers/claude-code/index.js.map +1 -1
  21. package/lib/drivers/codex/config.d.ts +7 -0
  22. package/lib/drivers/codex/config.d.ts.map +1 -1
  23. package/lib/drivers/codex/config.js +26 -2
  24. package/lib/drivers/codex/config.js.map +1 -1
  25. package/lib/drivers/codex/index.d.ts +7 -0
  26. package/lib/drivers/codex/index.d.ts.map +1 -1
  27. package/lib/drivers/codex/index.js +8 -3
  28. package/lib/drivers/codex/index.js.map +1 -1
  29. package/lib/drivers/subprocess/index.js +1 -1
  30. package/lib/drivers/subprocess/index.js.map +1 -1
  31. package/lib/orchestrator/run-execution.d.ts.map +1 -1
  32. package/lib/orchestrator/run-execution.js +15 -3
  33. package/lib/orchestrator/run-execution.js.map +1 -1
  34. package/lib/orchestrator/run-workspace.d.ts +10 -0
  35. package/lib/orchestrator/run-workspace.d.ts.map +1 -0
  36. package/lib/orchestrator/run-workspace.js +58 -0
  37. package/lib/orchestrator/run-workspace.js.map +1 -0
  38. package/package.json +1 -1
  39. package/skills/lux-answerer/SKILL.md +1 -1
  40. package/src/assertions/rubric/index.ts +1 -0
  41. package/src/assertions/rubric/internal.ts +35 -7
  42. package/src/drivers/antigravity/README.md +18 -6
  43. package/src/drivers/antigravity/config.ts +8 -2
  44. package/src/drivers/antigravity/events.ts +61 -6
  45. package/src/drivers/antigravity/index.ts +7 -3
  46. package/src/drivers/claude-code/index.ts +6 -2
  47. package/src/drivers/codex/config.ts +35 -2
  48. package/src/drivers/codex/index.ts +12 -2
  49. package/src/drivers/subprocess/index.ts +1 -1
  50. package/src/orchestrator/run-execution.ts +16 -3
  51. 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.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.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.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: "user",
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.runDir,
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.runDir.artifactsDir,
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.runDir.artifactsDir,
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
- return { artifacts: await this.args.driver.collect() };
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
+ }