@prismatic-io/lux 0.0.2-preview.27 → 0.0.2-preview.29

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 (125) hide show
  1. package/lib/authoring.d.ts +1 -0
  2. package/lib/authoring.d.ts.map +1 -1
  3. package/lib/authoring.js +1 -0
  4. package/lib/authoring.js.map +1 -1
  5. package/lib/container/driver.d.ts +27 -0
  6. package/lib/container/driver.d.ts.map +1 -0
  7. package/lib/container/driver.js +197 -0
  8. package/lib/container/driver.js.map +1 -0
  9. package/lib/container/protocol.d.ts +12 -0
  10. package/lib/container/protocol.d.ts.map +1 -0
  11. package/lib/container/protocol.js +38 -0
  12. package/lib/container/protocol.js.map +1 -0
  13. package/lib/container/resources.d.ts +12 -0
  14. package/lib/container/resources.d.ts.map +1 -0
  15. package/lib/container/resources.js +133 -0
  16. package/lib/container/resources.js.map +1 -0
  17. package/lib/container/worker.d.ts +2 -0
  18. package/lib/container/worker.d.ts.map +1 -0
  19. package/lib/container/worker.js +85 -0
  20. package/lib/container/worker.js.map +1 -0
  21. package/lib/core/case.d.ts +80 -0
  22. package/lib/core/case.d.ts.map +1 -1
  23. package/lib/core/case.js +8 -1
  24. package/lib/core/case.js.map +1 -1
  25. package/lib/core/driver-execution.d.ts +83 -0
  26. package/lib/core/driver-execution.d.ts.map +1 -0
  27. package/lib/core/driver-execution.js +72 -0
  28. package/lib/core/driver-execution.js.map +1 -0
  29. package/lib/core/driver.d.ts +5 -1
  30. package/lib/core/driver.d.ts.map +1 -1
  31. package/lib/core/driver.js +5 -1
  32. package/lib/core/driver.js.map +1 -1
  33. package/lib/core/harness-catalogs/cursor.js +1 -1
  34. package/lib/core/harness-catalogs/cursor.js.map +1 -1
  35. package/lib/core/harness-matrix.d.ts +84 -0
  36. package/lib/core/harness-matrix.d.ts.map +1 -1
  37. package/lib/core/harness-matrix.js +11 -1
  38. package/lib/core/harness-matrix.js.map +1 -1
  39. package/lib/core/run.d.ts +40 -0
  40. package/lib/core/run.d.ts.map +1 -1
  41. package/lib/drivers/antigravity/artifacts.d.ts +2 -0
  42. package/lib/drivers/antigravity/artifacts.d.ts.map +1 -0
  43. package/lib/drivers/antigravity/artifacts.js +33 -0
  44. package/lib/drivers/antigravity/artifacts.js.map +1 -0
  45. package/lib/drivers/antigravity/config.d.ts +27 -0
  46. package/lib/drivers/antigravity/config.d.ts.map +1 -1
  47. package/lib/drivers/antigravity/config.js +35 -0
  48. package/lib/drivers/antigravity/config.js.map +1 -1
  49. package/lib/drivers/antigravity/index.d.ts +35 -0
  50. package/lib/drivers/antigravity/index.d.ts.map +1 -1
  51. package/lib/drivers/antigravity/index.js +42 -20
  52. package/lib/drivers/antigravity/index.js.map +1 -1
  53. package/lib/drivers/antigravity/runtime.d.ts +7 -0
  54. package/lib/drivers/antigravity/runtime.d.ts.map +1 -0
  55. package/lib/drivers/antigravity/runtime.js +78 -0
  56. package/lib/drivers/antigravity/runtime.js.map +1 -0
  57. package/lib/drivers/copilot/config.d.ts +3 -3
  58. package/lib/drivers/copilot/config.js +5 -5
  59. package/lib/drivers/copilot/config.js.map +1 -1
  60. package/lib/drivers/copilot/index.d.ts +4 -4
  61. package/lib/drivers/copilot/index.d.ts.map +1 -1
  62. package/lib/drivers/copilot/index.js +13 -3
  63. package/lib/drivers/copilot/index.js.map +1 -1
  64. package/lib/drivers/cursor/config.js +1 -1
  65. package/lib/drivers/cursor/config.js.map +1 -1
  66. package/lib/drivers/cursor/index.d.ts.map +1 -1
  67. package/lib/drivers/cursor/index.js +8 -3
  68. package/lib/drivers/cursor/index.js.map +1 -1
  69. package/lib/drivers/shared/acp-session.d.ts +4 -0
  70. package/lib/drivers/shared/acp-session.d.ts.map +1 -0
  71. package/lib/drivers/shared/acp-session.js +23 -0
  72. package/lib/drivers/shared/acp-session.js.map +1 -0
  73. package/lib/index.d.ts +1 -0
  74. package/lib/index.d.ts.map +1 -1
  75. package/lib/index.js +1 -0
  76. package/lib/index.js.map +1 -1
  77. package/lib/orchestrator/config.d.ts +122 -1
  78. package/lib/orchestrator/config.d.ts.map +1 -1
  79. package/lib/orchestrator/config.js +2 -1
  80. package/lib/orchestrator/config.js.map +1 -1
  81. package/lib/orchestrator/doctor.d.ts.map +1 -1
  82. package/lib/orchestrator/doctor.js +3 -1
  83. package/lib/orchestrator/doctor.js.map +1 -1
  84. package/lib/orchestrator/driver-capabilities.d.ts +2 -1
  85. package/lib/orchestrator/driver-capabilities.d.ts.map +1 -1
  86. package/lib/orchestrator/driver-capabilities.js +5 -2
  87. package/lib/orchestrator/driver-capabilities.js.map +1 -1
  88. package/lib/orchestrator/driver-identity.d.ts.map +1 -1
  89. package/lib/orchestrator/driver-identity.js +11 -1
  90. package/lib/orchestrator/driver-identity.js.map +1 -1
  91. package/lib/orchestrator/experiment-context.d.ts +34 -0
  92. package/lib/orchestrator/experiment-context.d.ts.map +1 -1
  93. package/lib/orchestrator/orchestrator.d.ts.map +1 -1
  94. package/lib/orchestrator/orchestrator.js +16 -3
  95. package/lib/orchestrator/orchestrator.js.map +1 -1
  96. package/package.json +1 -1
  97. package/skills/lux-answerer/SKILL.md +1 -1
  98. package/src/authoring.ts +6 -0
  99. package/src/container/driver.ts +211 -0
  100. package/src/container/protocol.ts +48 -0
  101. package/src/container/resources.ts +153 -0
  102. package/src/container/worker.ts +107 -0
  103. package/src/core/case.ts +8 -1
  104. package/src/core/driver-execution.ts +84 -0
  105. package/src/core/driver.ts +9 -1
  106. package/src/core/harness-catalogs/cursor.ts +1 -1
  107. package/src/core/harness-matrix.ts +19 -1
  108. package/src/drivers/antigravity/README.md +108 -10
  109. package/src/drivers/antigravity/artifacts.ts +37 -0
  110. package/src/drivers/antigravity/config.ts +35 -0
  111. package/src/drivers/antigravity/index.ts +41 -22
  112. package/src/drivers/antigravity/runtime.ts +88 -0
  113. package/src/drivers/copilot/README.md +19 -11
  114. package/src/drivers/copilot/config.ts +5 -5
  115. package/src/drivers/copilot/index.ts +20 -4
  116. package/src/drivers/cursor/README.md +14 -8
  117. package/src/drivers/cursor/config.ts +1 -1
  118. package/src/drivers/cursor/index.ts +11 -7
  119. package/src/drivers/shared/acp-session.ts +40 -0
  120. package/src/index.ts +6 -0
  121. package/src/orchestrator/config.ts +4 -1
  122. package/src/orchestrator/doctor.ts +3 -1
  123. package/src/orchestrator/driver-capabilities.ts +6 -1
  124. package/src/orchestrator/driver-identity.ts +11 -1
  125. package/src/orchestrator/orchestrator.ts +19 -3
@@ -1,14 +1,15 @@
1
1
  # Cursor driver
2
2
 
3
3
  The `cursor` driver uses Cursor CLI's ACP transport. Install Cursor CLI and
4
- authenticate it before running Lux. The executable is normally named `agent`;
5
- set `command` to its absolute path if another application owns that name.
4
+ authenticate it before running Lux. Lux defaults to the unambiguous
5
+ `cursor-agent` executable because other harnesses also install `agent`.
6
+ Set `command` to an absolute path or to `agent` if that is your Cursor installation.
6
7
 
7
8
  ```ts
8
9
  {
9
10
  name: "cursor",
10
11
  config: {
11
- command: "/absolute/path/to/cursor/agent",
12
+ command: "/absolute/path/to/cursor-agent",
12
13
  model: "YOUR_CURSOR_MODEL_ID",
13
14
  // Optional: one of low, medium, high, xhigh; applied only when ACP advertises it.
14
15
  // reasoningEffort: "low",
@@ -16,11 +17,13 @@ set `command` to its absolute path if another application owns that name.
16
17
  }
17
18
  ```
18
19
 
19
- `model` is required. Cursor's model list is account-scoped (`agent
20
+ `model` is required. Cursor's model list is account-scoped (`cursor-agent
20
21
  --list-models` after login), so the harness catalog keeps it open: any ID is
21
22
  accepted in configuration and in a `matrix`, and some IDs encode reasoning
22
- effort. Lux passes that ID through `--model` and rejects a different model when
23
- the session reports one. If `reasoningEffort` is supplied (`low`, `medium`,
23
+ effort. Use the exact ACP model ID, including bracketed settings such as
24
+ `composer-2.5[fast=true]`. Lux passes the ID through `--model`, selects it with
25
+ ACP when the session advertises a model selector, and rejects a different
26
+ reported model. If `reasoningEffort` is supplied (`low`, `medium`,
24
27
  `high`, or `xhigh`), Lux requires a matching advertised ACP thought-level
25
28
  selector and checks the response confirms the requested value before sending
26
29
  the prompt. No default effort is applied.
@@ -49,8 +52,11 @@ managed process tree before collecting artifacts. Aborting a run also terminates
49
52
  the managed tree. Unsupported client filesystem/terminal operations are not
50
53
  advertised and unsupported requests receive a JSON-RPC error.
51
54
 
52
- The adapter is covered by synthetic protocol and lifecycle tests. Its initial
53
- implementation has not been validated against an authenticated Cursor session.
55
+ The adapter has synthetic protocol and lifecycle tests plus an opt-in live
56
+ smoke task: `mise run smoke:harnesses -- cursor` from the repository root.
57
+ The live task checks tool evidence, exact file output, and normal completion;
58
+ it uses your installed CLI, existing login, and model quota. Cursor CLI
59
+ `2026.09.23-86fc751` passed the basic authenticated file-copy smoke on 2026-09-30.
54
60
 
55
61
  Protocol references:
56
62
 
@@ -4,7 +4,7 @@ import { cursorCatalog } from "../../core/index.js";
4
4
 
5
5
  export const CursorDriverConfigSchema = z
6
6
  .object({
7
- command: z.string().min(1).default("agent"),
7
+ command: z.string().min(1).default("cursor-agent"),
8
8
  model: z.string().min(1),
9
9
  reasoningEffort: z.enum(cursorCatalog.efforts).optional(),
10
10
  mode: z.enum(["agent", "plan", "ask"]).default("agent"),
@@ -14,6 +14,7 @@ import {
14
14
  } from "../../core/index.js";
15
15
  import { AcpEvents } from "../shared/acp-events.js";
16
16
  import { permissionRequestText, records } from "../shared/acp-interaction.js";
17
+ import { acpModel, selectAcpModel } from "../shared/acp-session.js";
17
18
  import { AcpTransport, asRecord, type RpcId, type RpcRecord } from "../shared/acp-transport.js";
18
19
  import { walkArtifacts } from "../shared/artifacts.js";
19
20
  import { buildCursorArgs, type CursorDriverConfig, CursorDriverConfigSchema } from "./config.js";
@@ -91,6 +92,9 @@ class CursorDriver implements AgentDriver {
91
92
  let session = await this.request("session/new", { cwd: this.cwd, mcpServers: [] });
92
93
  if (typeof session.sessionId !== "string") throw new Error("Cursor ACP omitted sessionId");
93
94
  this.sessionId = session.sessionId;
95
+ session = await selectAcpModel("Cursor", session, this.config.model, (method, params) =>
96
+ this.request(method, params),
97
+ );
94
98
  if (this.config.reasoningEffort) {
95
99
  const selection = effortSelector(session, this.config.reasoningEffort);
96
100
  const updated = await this.request("session/set_config_option", {
@@ -104,10 +108,7 @@ class CursorDriver implements AgentDriver {
104
108
  throw new Error("Cursor ACP did not apply the requested reasoning effort");
105
109
  session = { ...session, ...updated };
106
110
  }
107
- const model =
108
- records(session.configOptions).find(
109
- (option) => option.category === "model" || option.id === "model",
110
- )?.currentValue ?? asRecord(session.models)?.currentModelId;
111
+ const model = acpModel(session);
111
112
  if (typeof model === "string" && model !== this.config.model)
112
113
  throw new Error(
113
114
  `Cursor ACP selected '${model}' instead of requested '${this.config.model}'`,
@@ -182,8 +183,11 @@ class CursorDriver implements AgentDriver {
182
183
  });
183
184
  return;
184
185
  }
185
- if (++this.interruptCount > this.config.maxInterrupts)
186
- throw new Error("Cursor exceeded maxInterrupts");
186
+ if (++this.interruptCount > this.config.maxInterrupts) {
187
+ this.transport?.write({ id, result: { outcome: { outcome: "cancelled" } } });
188
+ this.fail(new Error("Cursor exceeded maxInterrupts"), "max-interactions");
189
+ return;
190
+ }
187
191
  const interruptId = `cursor-${id}`;
188
192
  if (this.interrupts.has(interruptId)) throw new Error("Cursor reused a pending request ID");
189
193
  this.interrupts.set(interruptId, { id, method, params });
@@ -245,7 +249,7 @@ class CursorDriver implements AgentDriver {
245
249
  );
246
250
  }
247
251
 
248
- private fail(error: unknown, exitReason: "error" | "idle-timeout" = "error"): void {
252
+ private fail(error: unknown, exitReason: DriverEventMap["error"]["exitReason"] = "error"): void {
249
253
  if (this.terminal) return;
250
254
  this.terminal = true;
251
255
  clearTimeout(this.idleTimer);
@@ -0,0 +1,40 @@
1
+ import { records } from "./acp-interaction.js";
2
+ import { asRecord, type RpcRecord } from "./acp-transport.js";
3
+
4
+ export const acpModel = (session: RpcRecord): unknown =>
5
+ records(session.configOptions).find(
6
+ (option) => option.category === "model" || option.id === "model",
7
+ )?.currentValue ?? asRecord(session.models)?.currentModelId;
8
+
9
+ const hasOption = (options: unknown, value: string): boolean =>
10
+ records(options).some((option) => option.value === value || hasOption(option.options, value));
11
+
12
+ export const selectAcpModel = async (
13
+ label: string,
14
+ session: RpcRecord,
15
+ model: string,
16
+ request: (method: string, params: RpcRecord) => Promise<RpcRecord>,
17
+ ): Promise<RpcRecord> => {
18
+ let selected = session;
19
+ const selector = records(session.configOptions).find(
20
+ (option) => option.category === "model" || option.id === "model",
21
+ );
22
+ if (
23
+ acpModel(session) !== model &&
24
+ typeof selector?.id === "string" &&
25
+ hasOption(selector.options, model)
26
+ ) {
27
+ const updated = await request("session/set_config_option", {
28
+ sessionId: session.sessionId,
29
+ configId: selector.id,
30
+ value: model,
31
+ });
32
+ selected = { ...session, ...updated };
33
+ }
34
+ const reported = acpModel(selected);
35
+ if (typeof reported === "string" && reported !== model)
36
+ throw new Error(
37
+ `${label} ACP selected '${reported}' instead of requested '${model}'; use an exact model ID advertised by the ACP session`,
38
+ );
39
+ return selected;
40
+ };
package/src/index.ts CHANGED
@@ -265,6 +265,12 @@ export {
265
265
  type StartContext,
266
266
  selectModelAndReasoningEffort,
267
267
  } from "./core/driver.js";
268
+ export {
269
+ type ContainerExecution,
270
+ ContainerExecutionSchema,
271
+ type DriverExecution,
272
+ DriverExecutionSchema,
273
+ } from "./core/driver-execution.js";
268
274
  export {
269
275
  type DriverMetric,
270
276
  type DriverMetricDescriptor,
@@ -74,7 +74,7 @@ const LifecycleHooksPluginSchema = z.custom<LifecycleHooksPlugin>().superRefine(
74
74
  if (issue) ctx.addIssue({ code: "custom", message: issue });
75
75
  });
76
76
 
77
- const LuxConfigSchema = z
77
+ export const LuxConfigSchema = z
78
78
  .object({
79
79
  environment: EnvironmentDeclarationSchema.optional(),
80
80
  casesRoot: z.string().optional(),
@@ -194,6 +194,9 @@ const builtinDrivers: DriverPlugin<never>[] = [
194
194
  subprocessDriver,
195
195
  mcpProbeDriver,
196
196
  ];
197
+ export const isBuiltinDriver = (plugin: DriverPlugin): boolean =>
198
+ builtinDrivers.some((builtin) => builtin === plugin);
199
+
197
200
  const builtinAnswerers: AnswererPlugin<never>[] = [
198
201
  personaAnswerer,
199
202
  claudeCodeAnswerer,
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
4
4
  import { glob } from "tinyglobby";
5
5
  import { z } from "zod";
6
6
  import { RubricConfigSchema } from "../assertions/rubric/index.js";
7
+ import { driverExecutionOf } from "../core/driver-execution.js";
7
8
  import {
8
9
  casePermutations,
9
10
  type EvalCase,
@@ -143,6 +144,7 @@ const effectiveDriverReference = (local: unknown, fallback: unknown): unknown =>
143
144
  return {
144
145
  name: refName(local),
145
146
  config: mergeRecords(rawRefConfig(fallback), rawRefConfig(local)),
147
+ execution: driverExecutionOf(local) ?? driverExecutionOf(fallback),
146
148
  };
147
149
  };
148
150
 
@@ -511,7 +513,7 @@ const caseDriverFailure = (
511
513
  rawRefConfig(reference),
512
514
  `case '${caseId}' driver '${name}'`,
513
515
  );
514
- validateDriverCapabilities(plugin, config, required);
516
+ validateDriverCapabilities(plugin, config, required, driverExecutionOf(reference));
515
517
  return null;
516
518
  } catch (error) {
517
519
  return firstLine(error);
@@ -1,12 +1,17 @@
1
1
  import type { DriverCapability, DriverPlugin } from "../core/driver.js";
2
+ import type { DriverExecution } from "../core/driver-execution.js";
2
3
 
3
4
  export const validateDriverCapabilities = (
4
5
  plugin: DriverPlugin,
5
6
  config: unknown,
6
7
  required: readonly DriverCapability[] = [],
8
+ execution?: DriverExecution,
7
9
  ): void => {
8
10
  if (required.length === 0) return;
9
- const capabilities = plugin.runtime?.(config).capabilities;
11
+ const capabilities = {
12
+ ...plugin.runtime?.(config).capabilities,
13
+ ...(execution?.kind === "container" ? { isolation: true } : {}),
14
+ };
10
15
  const missing = required.filter((capability) => capabilities?.[capability] !== true);
11
16
  if (missing.length > 0) {
12
17
  throw new Error(
@@ -1,4 +1,5 @@
1
1
  import type { DriverPlugin, DriverRuntime } from "../core/driver.js";
2
+ import { driverExecutionOf } from "../core/driver-execution.js";
2
3
  import type { Registry } from "../core/registry.js";
3
4
  import { cliIdentity } from "./experiment-runtime-identity.js";
4
5
  import { validatePluginConfig } from "./validate-plugin-config.js";
@@ -21,7 +22,14 @@ const resolvedDriver = (
21
22
 
22
23
  export const referencedDriverRuntime = (reference: unknown, registry: Registry): DriverRuntime => {
23
24
  const resolved = resolvedDriver(reference, registry);
24
- return resolved?.plugin.runtime?.(resolved.config) ?? { commands: [], capabilities: {} };
25
+ const runtime = resolved?.plugin.runtime?.(resolved.config) ?? { commands: [], capabilities: {} };
26
+ const execution = driverExecutionOf(reference);
27
+ return execution?.kind === "container"
28
+ ? {
29
+ commands: [execution.command ?? "docker"],
30
+ capabilities: { ...runtime.capabilities, isolation: true },
31
+ }
32
+ : runtime;
25
33
  };
26
34
 
27
35
  export const experimentDriverIdentity = async (
@@ -29,6 +37,8 @@ export const experimentDriverIdentity = async (
29
37
  registry: Registry,
30
38
  rootDir: string,
31
39
  ): Promise<unknown> => {
40
+ if (driverExecutionOf(reference)?.kind === "container")
41
+ throw new Error("container execution does not yet support controlled experiments");
32
42
  const resolved = resolvedDriver(reference, registry);
33
43
  if (!resolved) return null;
34
44
  const { plugin, config } = resolved;
@@ -1,4 +1,6 @@
1
1
  import { basename, extname, join, resolve } from "node:path";
2
+ import { ContainerDriver } from "../container/driver.js";
3
+ import { driverExecutionOf } from "../core/driver-execution.js";
2
4
  import { type EnvironmentDeclaration, EnvironmentDeclarationSchema } from "../core/environment.js";
3
5
  import {
4
6
  type Answerer,
@@ -28,6 +30,7 @@ import {
28
30
  registryForEvalCase,
29
31
  valuesForFixtures,
30
32
  } from "./case-extensions.js";
33
+ import { isBuiltinDriver } from "./config.js";
31
34
  import { validateDriverCapabilities } from "./driver-capabilities.js";
32
35
  import { evaluatorImplementationHash } from "./evaluator-identity.js";
33
36
  import {
@@ -249,6 +252,7 @@ export const resolveDriverReference = (
249
252
  return {
250
253
  name: driverNameOf(local),
251
254
  config: mergePluginConfig(driverConfigOf(base), driverConfigOf(local)),
255
+ execution: driverExecutionOf(local) ?? driverExecutionOf(base),
252
256
  };
253
257
  };
254
258
 
@@ -390,8 +394,20 @@ export class Orchestrator {
390
394
  `answerer '${answererName}'`,
391
395
  );
392
396
 
393
- validateDriverCapabilities(driverPlugin, driverCfg, evalCase.requires);
394
- const driver = await driverPlugin.create(driverCfg);
397
+ const execution = driverExecutionOf(driverRef);
398
+ if (execution?.kind === "container" && request.experimentId)
399
+ throw new Error("container execution does not yet support controlled experiments");
400
+ if (
401
+ execution?.kind === "container" &&
402
+ !execution.driverModule &&
403
+ !isBuiltinDriver(driverPlugin)
404
+ )
405
+ throw new Error("custom container drivers require execution.driverModule in the image");
406
+ validateDriverCapabilities(driverPlugin, driverCfg, evalCase.requires, execution);
407
+ const driver =
408
+ execution?.kind === "container"
409
+ ? new ContainerDriver(driverName, driverCfg, execution)
410
+ : await driverPlugin.create(driverCfg);
395
411
  let answerer: Answerer;
396
412
  try {
397
413
  answerer = await answererPlugin.create(answererCfg);
@@ -427,7 +443,7 @@ export class Orchestrator {
427
443
  permutationId: request.permutationId,
428
444
  answererName,
429
445
  answererConfig: answererCfg,
430
- driverConfigHash: valueHash(driverCfg),
446
+ driverConfigHash: valueHash(execution ? { config: driverCfg, execution } : driverCfg),
431
447
  answererConfigHash: valueHash(answererCfg),
432
448
  validatedAssertionsHash,
433
449
  graderConfigHash: graderHash,