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

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 (162) hide show
  1. package/lib/answerers/claude-code/index.d.ts +3 -3
  2. package/lib/answerers/claude-code/index.d.ts.map +1 -1
  3. package/lib/answerers/claude-code/index.js +3 -2
  4. package/lib/answerers/claude-code/index.js.map +1 -1
  5. package/lib/answerers/persona/index.d.ts +3 -3
  6. package/lib/answerers/persona/index.d.ts.map +1 -1
  7. package/lib/answerers/persona/index.js +3 -2
  8. package/lib/answerers/persona/index.js.map +1 -1
  9. package/lib/answerers/terminal/index.d.ts +3 -3
  10. package/lib/answerers/terminal/index.d.ts.map +1 -1
  11. package/lib/answerers/terminal/index.js +3 -2
  12. package/lib/answerers/terminal/index.js.map +1 -1
  13. package/lib/assertions/core/command-exits-zero.d.ts +1 -1
  14. package/lib/assertions/core/command-exits-zero.d.ts.map +1 -1
  15. package/lib/assertions/core/command-exits-zero.js +4 -3
  16. package/lib/assertions/core/command-exits-zero.js.map +1 -1
  17. package/lib/assertions/rubric/index.d.ts +7 -2
  18. package/lib/assertions/rubric/index.d.ts.map +1 -1
  19. package/lib/assertions/rubric/index.js +18 -10
  20. package/lib/assertions/rubric/index.js.map +1 -1
  21. package/lib/assertions/rubric/internal.d.ts +1 -1
  22. package/lib/assertions/rubric/internal.d.ts.map +1 -1
  23. package/lib/assertions/rubric/internal.js +7 -2
  24. package/lib/assertions/rubric/internal.js.map +1 -1
  25. package/lib/authoring.d.ts +1 -0
  26. package/lib/authoring.d.ts.map +1 -1
  27. package/lib/authoring.js +1 -0
  28. package/lib/authoring.js.map +1 -1
  29. package/lib/cli/output-schemas.d.ts +2 -2
  30. package/lib/cli/program.js +2 -2
  31. package/lib/cli/program.js.map +1 -1
  32. package/lib/core/duration.d.ts +5 -0
  33. package/lib/core/duration.d.ts.map +1 -0
  34. package/lib/core/duration.js +49 -0
  35. package/lib/core/duration.js.map +1 -0
  36. package/lib/core/experiment.d.ts +2 -2
  37. package/lib/core/experiment.d.ts.map +1 -1
  38. package/lib/core/experiment.js +2 -1
  39. package/lib/core/experiment.js.map +1 -1
  40. package/lib/core/index.d.ts +1 -0
  41. package/lib/core/index.d.ts.map +1 -1
  42. package/lib/core/index.js +1 -0
  43. package/lib/core/index.js.map +1 -1
  44. package/lib/drivers/antigravity/config.d.ts +3 -3
  45. package/lib/drivers/antigravity/config.d.ts.map +1 -1
  46. package/lib/drivers/antigravity/config.js +2 -1
  47. package/lib/drivers/antigravity/config.js.map +1 -1
  48. package/lib/drivers/antigravity/index.d.ts +4 -4
  49. package/lib/drivers/antigravity/index.js +1 -1
  50. package/lib/drivers/antigravity/index.js.map +1 -1
  51. package/lib/drivers/claude-code/index.d.ts +14 -14
  52. package/lib/drivers/claude-code/index.d.ts.map +1 -1
  53. package/lib/drivers/claude-code/index.js +10 -9
  54. package/lib/drivers/claude-code/index.js.map +1 -1
  55. package/lib/drivers/codex/config.d.ts +3 -3
  56. package/lib/drivers/codex/config.d.ts.map +1 -1
  57. package/lib/drivers/codex/config.js +2 -1
  58. package/lib/drivers/codex/config.js.map +1 -1
  59. package/lib/drivers/codex/index.d.ts +4 -4
  60. package/lib/drivers/codex/index.d.ts.map +1 -1
  61. package/lib/drivers/codex/index.js +4 -3
  62. package/lib/drivers/codex/index.js.map +1 -1
  63. package/lib/drivers/copilot/config.d.ts +6 -6
  64. package/lib/drivers/copilot/config.d.ts.map +1 -1
  65. package/lib/drivers/copilot/config.js +3 -2
  66. package/lib/drivers/copilot/config.js.map +1 -1
  67. package/lib/drivers/copilot/index.d.ts +8 -8
  68. package/lib/drivers/copilot/index.d.ts.map +1 -1
  69. package/lib/drivers/copilot/index.js +3 -2
  70. package/lib/drivers/copilot/index.js.map +1 -1
  71. package/lib/drivers/cursor/config.d.ts +2 -2
  72. package/lib/drivers/cursor/config.d.ts.map +1 -1
  73. package/lib/drivers/cursor/config.js +3 -2
  74. package/lib/drivers/cursor/config.js.map +1 -1
  75. package/lib/drivers/cursor/index.d.ts +4 -4
  76. package/lib/drivers/cursor/index.d.ts.map +1 -1
  77. package/lib/drivers/cursor/index.js +3 -2
  78. package/lib/drivers/cursor/index.js.map +1 -1
  79. package/lib/drivers/grok-build/config.d.ts +9 -9
  80. package/lib/drivers/grok-build/config.d.ts.map +1 -1
  81. package/lib/drivers/grok-build/config.js +4 -3
  82. package/lib/drivers/grok-build/config.js.map +1 -1
  83. package/lib/drivers/grok-build/index.d.ts +12 -12
  84. package/lib/drivers/grok-build/index.d.ts.map +1 -1
  85. package/lib/drivers/grok-build/index.js +5 -4
  86. package/lib/drivers/grok-build/index.js.map +1 -1
  87. package/lib/drivers/mcp/index.d.ts +6 -6
  88. package/lib/drivers/mcp/index.d.ts.map +1 -1
  89. package/lib/drivers/mcp/index.js +9 -8
  90. package/lib/drivers/mcp/index.js.map +1 -1
  91. package/lib/drivers/shared/artifacts.d.ts.map +1 -1
  92. package/lib/drivers/shared/artifacts.js +3 -2
  93. package/lib/drivers/shared/artifacts.js.map +1 -1
  94. package/lib/drivers/subprocess/index.d.ts +3 -3
  95. package/lib/drivers/subprocess/index.d.ts.map +1 -1
  96. package/lib/drivers/subprocess/index.js +4 -3
  97. package/lib/drivers/subprocess/index.js.map +1 -1
  98. package/lib/index.d.ts +1 -0
  99. package/lib/index.d.ts.map +1 -1
  100. package/lib/index.js +1 -0
  101. package/lib/index.js.map +1 -1
  102. package/lib/optimization/reflective.d.ts +1 -1
  103. package/lib/optimization/reflective.d.ts.map +1 -1
  104. package/lib/optimization/reflective.js +3 -2
  105. package/lib/optimization/reflective.js.map +1 -1
  106. package/lib/orchestrator/candidates.d.ts.map +1 -1
  107. package/lib/orchestrator/candidates.js +3 -2
  108. package/lib/orchestrator/candidates.js.map +1 -1
  109. package/lib/orchestrator/experiment-contracts.d.ts +1 -1
  110. package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
  111. package/lib/orchestrator/experiment-evaluation.js +3 -2
  112. package/lib/orchestrator/experiment-evaluation.js.map +1 -1
  113. package/lib/orchestrator/experiment-loader.d.ts +1 -1
  114. package/lib/orchestrator/experiment-report.d.ts +1 -1
  115. package/lib/orchestrator/experiment-runs.d.ts +1 -1
  116. package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
  117. package/lib/orchestrator/grader-experiment.js +2 -2
  118. package/lib/orchestrator/grader-experiment.js.map +1 -1
  119. package/lib/orchestrator/grading.js +1 -1
  120. package/lib/orchestrator/grading.js.map +1 -1
  121. package/lib/orchestrator/harness-registry.d.ts +4 -0
  122. package/lib/orchestrator/harness-registry.d.ts.map +1 -0
  123. package/lib/orchestrator/harness-registry.js +5 -0
  124. package/lib/orchestrator/harness-registry.js.map +1 -0
  125. package/lib/orchestrator/rubric-loader.js +1 -1
  126. package/lib/orchestrator/rubric-loader.js.map +1 -1
  127. package/package.json +1 -1
  128. package/skills/lux-answerer/SKILL.md +2 -2
  129. package/src/answerers/claude-code/index.ts +3 -2
  130. package/src/answerers/persona/index.ts +3 -2
  131. package/src/answerers/terminal/index.ts +3 -2
  132. package/src/assertions/core/command-exits-zero.ts +4 -3
  133. package/src/assertions/rubric/index.ts +23 -10
  134. package/src/assertions/rubric/internal.ts +8 -2
  135. package/src/authoring.ts +1 -0
  136. package/src/cli/program.ts +2 -2
  137. package/src/core/duration.ts +52 -0
  138. package/src/core/experiment.ts +2 -1
  139. package/src/core/index.ts +1 -0
  140. package/src/drivers/antigravity/config.ts +2 -1
  141. package/src/drivers/antigravity/index.ts +1 -1
  142. package/src/drivers/claude-code/index.ts +10 -9
  143. package/src/drivers/codex/config.ts +2 -1
  144. package/src/drivers/codex/index.ts +4 -3
  145. package/src/drivers/copilot/config.ts +3 -2
  146. package/src/drivers/copilot/index.ts +4 -3
  147. package/src/drivers/cursor/config.ts +3 -2
  148. package/src/drivers/cursor/index.ts +4 -3
  149. package/src/drivers/grok-build/README.md +2 -2
  150. package/src/drivers/grok-build/config.ts +4 -3
  151. package/src/drivers/grok-build/index.ts +6 -5
  152. package/src/drivers/mcp/index.ts +9 -8
  153. package/src/drivers/shared/artifacts.ts +6 -2
  154. package/src/drivers/subprocess/index.ts +4 -3
  155. package/src/index.ts +1 -0
  156. package/src/optimization/reflective.ts +3 -2
  157. package/src/orchestrator/candidates.ts +3 -2
  158. package/src/orchestrator/experiment-evaluation.ts +3 -5
  159. package/src/orchestrator/grader-experiment.ts +2 -5
  160. package/src/orchestrator/grading.ts +1 -1
  161. package/src/orchestrator/harness-registry.ts +12 -0
  162. package/src/orchestrator/rubric-loader.ts +1 -1
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { DurationSchema } from "../../core/duration.js";
2
3
  import { antigravityCatalog, applyCatalogEffort } from "../../core/index.js";
3
4
 
4
5
  export const AntigravityDriverConfigSchema = z
@@ -23,7 +24,7 @@ export const AntigravityDriverConfigSchema = z
23
24
  cwd: z.string().optional(),
24
25
  env: z.record(z.string(), z.string()).optional(),
25
26
  extraArgs: z.array(z.string()).optional(),
26
- idleTimeoutMs: z.number().positive().default(600_000),
27
+ idleTimeout: DurationSchema.default("10 minutes"),
27
28
  maxLineBytes: z.number().positive().default(10_000_000),
28
29
  })
29
30
  .strict()
@@ -58,7 +58,7 @@ class AntigravityDriver implements AgentDriver {
58
58
  `The task workspace is ${JSON.stringify(this.cwd)}. Treat that directory as the current directory for this task and create task artifacts there.\n\n${prompt}`,
59
59
  ),
60
60
  closeStdinAfterPrompt: true,
61
- idleTimeoutMs: this.config.idleTimeoutMs,
61
+ idleTimeout: this.config.idleTimeout,
62
62
  maxLineBytes: this.config.maxLineBytes,
63
63
  maxInterrupts: 1,
64
64
  });
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
3
3
  import { dirname, join, resolve, sep } from "node:path";
4
4
  import { temporaryDirectory } from "tempy";
5
5
  import { z } from "zod";
6
+ import { DurationSchema, durationMs } from "../../core/duration.js";
6
7
  import {
7
8
  type AgentDriver,
8
9
  type Answer,
@@ -108,19 +109,19 @@ export const ClaudeCodeDriverConfigSchema = z
108
109
  */
109
110
  textQuestionFallback: z.boolean().default(false),
110
111
  /** Maximum silence between recognized protocol events; excludes answerer wait time. */
111
- idleTimeoutMs: z.number().positive().default(600_000),
112
+ idleTimeout: DurationSchema.default("10 minutes"),
112
113
  /** Optional elapsed-time cap per defer-resume CLI invocation, regardless of activity. */
113
- turnTimeoutMs: z.number().positive().optional(),
114
+ turnTimeout: DurationSchema.optional(),
114
115
  /** Cap on outstanding interrupts before the driver aborts. */
115
116
  maxInterrupts: z.number().positive().default(20),
116
117
  })
117
118
  .strict()
118
119
  .superRefine((config, ctx) => {
119
- if (config.turnTimeoutMs !== undefined && config.interactionMode !== "defer-resume") {
120
+ if (config.turnTimeout !== undefined && config.interactionMode !== "defer-resume") {
120
121
  ctx.addIssue({
121
122
  code: "custom",
122
- path: ["turnTimeoutMs"],
123
- message: "turnTimeoutMs requires defer-resume mode",
123
+ path: ["turnTimeout"],
124
+ message: "turnTimeout requires defer-resume mode",
124
125
  });
125
126
  }
126
127
  if (config.interactionMode === "defer-resume") {
@@ -596,7 +597,7 @@ class ClaudeCodeDriverImpl implements AgentDriver {
596
597
  parseEvent: makeParseEvent(this.config, cliVersion),
597
598
  formatAnswer,
598
599
  formatPrompt,
599
- idleTimeoutMs: this.config.idleTimeoutMs,
600
+ idleTimeout: this.config.idleTimeout,
600
601
  maxInterrupts: this.config.maxInterrupts,
601
602
  });
602
603
  for (const queued of this.pendingAttach) queued.attach(inner);
@@ -752,10 +753,10 @@ class ClaudeCodeDriverImpl implements AgentDriver {
752
753
  ...(answer ? { LUX_CLAUDE_DEFERRED_ANSWER: JSON.stringify(answer) } : {}),
753
754
  },
754
755
  ...input,
755
- idleTimeoutMs: this.config.idleTimeoutMs,
756
- ...(this.config.turnTimeoutMs === undefined
756
+ idleTimeoutMs: durationMs(this.config.idleTimeout),
757
+ ...(this.config.turnTimeout === undefined
757
758
  ? {}
758
- : { turnTimeoutMs: this.config.turnTimeoutMs }),
759
+ : { turnTimeoutMs: durationMs(this.config.turnTimeout) }),
759
760
  signal: AbortSignal.any([ctx.abortSignal, this.deferredAbort.signal]),
760
761
  parse: (line) => {
761
762
  try {
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { DurationSchema } from "../../core/duration.js";
2
3
  import { applyCatalogEffort, codexCatalog, DEFAULT_CODEX_MODEL } from "../../core/index.js";
3
4
 
4
5
  export type CodexConfigValue =
@@ -51,7 +52,7 @@ export const CodexDriverConfigSchema = z
51
52
  isolation: IsolationSchema.optional(),
52
53
  maxInterrupts: z.number().positive().default(20),
53
54
  maxLineBytes: z.number().positive().default(10_000_000),
54
- idleTimeoutMs: z.number().positive().default(600_000),
55
+ idleTimeout: DurationSchema.default("10 minutes"),
55
56
  })
56
57
  .strict()
57
58
  .superRefine((config, ctx) => {
@@ -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 { durationMs } from "../../core/duration.js";
6
7
  import {
7
8
  type AgentDriver,
8
9
  type Answer,
@@ -654,8 +655,8 @@ class CodexAppServerDriverImpl implements AgentDriver {
654
655
  this.idleTimer = setTimeout(() => {
655
656
  this.idleTimer = null;
656
657
  if (this.interrupts.size > 0) return;
657
- this.fail(`no event for ${this.config.idleTimeoutMs}ms`, "idle-timeout");
658
- }, this.config.idleTimeoutMs);
658
+ this.fail(`no event for ${durationMs(this.config.idleTimeout)}ms`, "idle-timeout");
659
+ }, durationMs(this.config.idleTimeout));
659
660
  }
660
661
 
661
662
  private clearIdleTimer(): void {
@@ -718,7 +719,7 @@ class CodexExecDriverImpl implements AgentDriver {
718
719
  parseEvent: makeParseEvent(this.config, cliVersion),
719
720
  formatPrompt: (prompt) => prompt,
720
721
  closeStdinAfterPrompt: true,
721
- idleTimeoutMs: this.config.idleTimeoutMs,
722
+ idleTimeout: this.config.idleTimeout,
722
723
  maxLineBytes: this.config.maxLineBytes,
723
724
  maxInterrupts: 1,
724
725
  });
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { DurationSchema } from "../../core/duration.js";
2
3
  import { applyCatalogEffort, copilotCatalog } from "../../core/index.js";
3
4
 
4
5
  export const CopilotDriverConfigSchema = z
@@ -23,8 +24,8 @@ export const CopilotDriverConfigSchema = z
23
24
  extraArgs: z.array(z.string()).optional(),
24
25
  cwd: z.string().optional(),
25
26
  env: z.record(z.string(), z.string()).optional(),
26
- startupTimeoutMs: z.number().int().positive().default(30_000),
27
- idleTimeoutMs: z.number().int().positive().default(600_000),
27
+ startupTimeout: DurationSchema.default("30 seconds"),
28
+ idleTimeout: DurationSchema.default("10 minutes"),
28
29
  maxInterrupts: z.number().int().positive().default(20),
29
30
  maxLineBytes: z.number().int().positive().default(10_000_000),
30
31
  maxQueuedBytes: z.number().int().positive().default(20_000_000),
@@ -1,4 +1,5 @@
1
1
  import { realpath } from "node:fs/promises";
2
+ import { durationMs } from "../../core/duration.js";
2
3
  import {
3
4
  type AgentDriver,
4
5
  type Answer,
@@ -130,7 +131,7 @@ class CopilotDriver implements AgentDriver {
130
131
 
131
132
  private request(method: string, params: RpcRecord): Promise<RpcRecord> {
132
133
  if (!this.transport) throw new Error("Copilot ACP has not started");
133
- return this.transport.request(method, params, this.config.startupTimeoutMs);
134
+ return this.transport.request(method, params, durationMs(this.config.startupTimeout));
134
135
  }
135
136
 
136
137
  private async run(prompt: string): Promise<void> {
@@ -209,10 +210,10 @@ class CopilotDriver implements AgentDriver {
209
210
  this.idleTimer = setTimeout(
210
211
  () =>
211
212
  this.fail(
212
- new Error(`Copilot emitted no event for ${this.config.idleTimeoutMs}ms`),
213
+ new Error(`Copilot emitted no event for ${durationMs(this.config.idleTimeout)}ms`),
213
214
  "idle-timeout",
214
215
  ),
215
- this.config.idleTimeoutMs,
216
+ durationMs(this.config.idleTimeout),
216
217
  );
217
218
  }
218
219
 
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { DurationSchema } from "../../core/duration.js";
2
3
  import { cursorCatalog } from "../../core/index.js";
3
4
 
4
5
  export const CursorDriverConfigSchema = z
@@ -9,8 +10,8 @@ export const CursorDriverConfigSchema = z
9
10
  mode: z.enum(["agent", "plan", "ask"]).default("agent"),
10
11
  cwd: z.string().optional(),
11
12
  env: z.record(z.string(), z.string()).optional(),
12
- startupTimeoutMs: z.number().int().positive().default(30_000),
13
- idleTimeoutMs: z.number().int().positive().default(600_000),
13
+ startupTimeout: DurationSchema.default("30 seconds"),
14
+ idleTimeout: DurationSchema.default("10 minutes"),
14
15
  maxInterrupts: z.number().int().positive().default(20),
15
16
  maxLineBytes: z.number().int().positive().default(10_000_000),
16
17
  maxQueuedBytes: z.number().int().positive().default(20_000_000),
@@ -1,4 +1,5 @@
1
1
  import { realpath } from "node:fs/promises";
2
+ import { durationMs } from "../../core/duration.js";
2
3
  import {
3
4
  type AgentDriver,
4
5
  type Answer,
@@ -136,7 +137,7 @@ class CursorDriver implements AgentDriver {
136
137
 
137
138
  private request(method: string, params: RpcRecord): Promise<RpcRecord> {
138
139
  if (!this.transport) throw new Error("Cursor ACP has not started");
139
- return this.transport.request(method, params, this.config.startupTimeoutMs);
140
+ return this.transport.request(method, params, durationMs(this.config.startupTimeout));
140
141
  }
141
142
 
142
143
  private async run(prompt: string): Promise<void> {
@@ -237,10 +238,10 @@ class CursorDriver implements AgentDriver {
237
238
  this.idleTimer = setTimeout(
238
239
  () =>
239
240
  this.fail(
240
- new Error(`Cursor emitted no event for ${this.config.idleTimeoutMs}ms`),
241
+ new Error(`Cursor emitted no event for ${durationMs(this.config.idleTimeout)}ms`),
241
242
  "idle-timeout",
242
243
  ),
243
- this.config.idleTimeoutMs,
244
+ durationMs(this.config.idleTimeout),
244
245
  );
245
246
  }
246
247
 
@@ -64,8 +64,8 @@ text and invalid fields still deny the operation. If the agent offers no matchin
64
64
  one-operation option, Lux cancels and reports an error rather than widening the
65
65
  grant. Native auto decisions are handled inside Grok and are not Lux interrupts.
66
66
 
67
- Permission reviews have a separate `permissionTimeoutMs` (150 seconds by
68
- default), while ordinary inactivity uses `idleTimeoutMs` (10 minutes). Startup
67
+ Permission reviews have a separate `permissionTimeout` (150 seconds by
68
+ default), while ordinary inactivity uses `idleTimeout` (10 minutes). Startup
69
69
  RPCs time out after 30 seconds. Cancelled turns, process failures, unanswered
70
70
  reviews, and turns ending with pending approvals cannot count as successful runs.
71
71
  A completed turn can still contain denied tool calls: assert the expected files
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { DurationSchema } from "../../core/duration.js";
2
3
  import { applyCatalogEffort, grokBuildCatalog } from "../../core/index.js";
3
4
 
4
5
  export const GrokBuildDriverConfigSchema = z
@@ -11,9 +12,9 @@ export const GrokBuildDriverConfigSchema = z
11
12
  sandbox: z.enum(["workspace", "read-only", "strict"]).default("workspace"),
12
13
  cwd: z.string().optional(),
13
14
  env: z.record(z.string(), z.string()).optional(),
14
- startupTimeoutMs: z.number().int().positive().default(30_000),
15
- idleTimeoutMs: z.number().int().positive().default(600_000),
16
- permissionTimeoutMs: z.number().int().positive().default(150_000),
15
+ startupTimeout: DurationSchema.default("30 seconds"),
16
+ idleTimeout: DurationSchema.default("10 minutes"),
17
+ permissionTimeout: DurationSchema.default("150 seconds"),
17
18
  maxInterrupts: z.number().int().positive().default(20),
18
19
  maxLineBytes: z.number().int().positive().default(10_000_000),
19
20
  maxQueuedBytes: z.number().int().positive().default(20_000_000),
@@ -1,4 +1,5 @@
1
1
  import { realpath } from "node:fs/promises";
2
+ import { durationMs } from "../../core/duration.js";
2
3
  import {
3
4
  type AgentDriver,
4
5
  type Answer,
@@ -143,7 +144,7 @@ class GrokBuildDriver implements AgentDriver {
143
144
 
144
145
  private request(method: string, params: RpcRecord): Promise<RpcRecord> {
145
146
  if (!this.transport) throw new Error("Grok.build ACP has not started");
146
- return this.transport.request(method, params, this.config.startupTimeoutMs);
147
+ return this.transport.request(method, params, durationMs(this.config.startupTimeout));
147
148
  }
148
149
 
149
150
  private async run(prompt: string): Promise<void> {
@@ -198,10 +199,10 @@ class GrokBuildDriver implements AgentDriver {
198
199
  this.interrupts.delete(interruptId);
199
200
  this.fail(
200
201
  new Error(
201
- `Grok.build permission review timed out after ${this.config.permissionTimeoutMs}ms`,
202
+ `Grok.build permission review timed out after ${durationMs(this.config.permissionTimeout)}ms`,
202
203
  ),
203
204
  );
204
- }, this.config.permissionTimeoutMs);
205
+ }, durationMs(this.config.permissionTimeout));
205
206
  this.interrupts.set(interruptId, { id, params, timer });
206
207
  clearTimeout(this.idleTimer);
207
208
  void this.emitter
@@ -243,10 +244,10 @@ class GrokBuildDriver implements AgentDriver {
243
244
  this.idleTimer = setTimeout(
244
245
  () =>
245
246
  this.fail(
246
- new Error(`Grok.build emitted no event for ${this.config.idleTimeoutMs}ms`),
247
+ new Error(`Grok.build emitted no event for ${durationMs(this.config.idleTimeout)}ms`),
247
248
  "idle-timeout",
248
249
  ),
249
- this.config.idleTimeoutMs,
250
+ durationMs(this.config.idleTimeout),
250
251
  );
251
252
  }
252
253
 
@@ -1,6 +1,7 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js";
3
3
  import { z } from "zod";
4
+ import { DurationSchema, durationMs as parseDuration } from "../../core/duration.js";
4
5
  import {
5
6
  type AgentDriver,
6
7
  type Answer,
@@ -72,9 +73,9 @@ export const McpProbeDriverConfigSchema = z
72
73
  /** The steps to run. Defaults to a single `tools/list` capture. */
73
74
  probe: z.array(ProbeStepSchema).default([{ op: "list-tools" }]),
74
75
  /** Timeout for the initialize handshake. */
75
- startupTimeoutMs: z.number().positive().default(30_000),
76
+ startupTimeout: DurationSchema.default("30 seconds"),
76
77
  /** Timeout for each `tools/list` / `tools/call` request. */
77
- stepTimeoutMs: z.number().positive().default(60_000),
78
+ stepTimeout: DurationSchema.default("1 minute"),
78
79
  /** Safety cap for a paginated `tools/list` operation. */
79
80
  maxListPages: z.number().int().positive().default(100),
80
81
  })
@@ -211,8 +212,8 @@ class McpProbeDriverImpl implements AgentDriver {
211
212
 
212
213
  try {
213
214
  await client.connect(transport, {
214
- timeout: this.config.startupTimeoutMs,
215
- maxTotalTimeout: this.config.startupTimeoutMs,
215
+ timeout: parseDuration(this.config.startupTimeout),
216
+ maxTotalTimeout: parseDuration(this.config.startupTimeout),
216
217
  signal: ctx.abortSignal,
217
218
  });
218
219
  } catch (err) {
@@ -261,8 +262,8 @@ class McpProbeDriverImpl implements AgentDriver {
261
262
  throw new Error(`tools/list exceeded ${this.config.maxListPages} pages`);
262
263
  }
263
264
  const result = await client.listTools(cursor ? { cursor } : undefined, {
264
- timeout: this.config.stepTimeoutMs,
265
- maxTotalTimeout: this.config.stepTimeoutMs,
265
+ timeout: parseDuration(this.config.stepTimeout),
266
+ maxTotalTimeout: parseDuration(this.config.stepTimeout),
266
267
  ...(this.abortSignal ? { signal: this.abortSignal } : {}),
267
268
  });
268
269
  tools.push(...(result.tools as ListedTool[]));
@@ -285,8 +286,8 @@ class McpProbeDriverImpl implements AgentDriver {
285
286
  { name: step.tool, arguments: step.args },
286
287
  undefined,
287
288
  {
288
- timeout: this.config.stepTimeoutMs,
289
- maxTotalTimeout: this.config.stepTimeoutMs,
289
+ timeout: parseDuration(this.config.stepTimeout),
290
+ maxTotalTimeout: parseDuration(this.config.stepTimeout),
290
291
  ...(this.abortSignal ? { signal: this.abortSignal } : {}),
291
292
  },
292
293
  );
@@ -6,7 +6,7 @@ import {
6
6
  readArtifactSnapshotManifest,
7
7
  } from "../../core/index.js";
8
8
 
9
- const IGNORED_DIRS = new Set(["node_modules", "lib", "dist"]);
9
+ const IGNORED_DIRS = new Set(["node_modules", "dist"]);
10
10
 
11
11
  /** Enumerate agent-authored files without following symlinks or build/dependency trees. */
12
12
  export const walkArtifacts = async (root: string, max = 500): Promise<Artifact[]> => {
@@ -71,7 +71,11 @@ export const walkArtifacts = async (root: string, max = 500): Promise<Artifact[]
71
71
  const full = join(dir, entry.name);
72
72
  if (entry.isDirectory()) {
73
73
  if (await collectSnapshot(full)) continue;
74
- if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
74
+ if (
75
+ (entry.name.startsWith(".") && entry.name !== ".spectral") ||
76
+ IGNORED_DIRS.has(entry.name)
77
+ )
78
+ continue;
75
79
  await walk(full);
76
80
  } else if (entry.isFile()) {
77
81
  const metadata = await lstat(full);
@@ -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 { DurationSchema, durationMs } from "../../core/duration.js";
5
6
  import {
6
7
  type AgentDriver,
7
8
  type Answer,
@@ -36,7 +37,7 @@ export const SubprocessDriverConfigSchema = z
36
37
  /** Inherit the host environment. Experiment mode always forces this off. */
37
38
  inheritEnv: z.boolean().optional(),
38
39
  protocol: z.enum(["lux-jsonl", "custom"]).default("lux-jsonl"),
39
- idleTimeoutMs: z.number().positive().default(120_000),
40
+ idleTimeout: DurationSchema.default("2 minutes"),
40
41
  maxInterrupts: z.number().positive().default(20),
41
42
  /**
42
43
  * Maximum files inventoried from LUX_ARTIFACTS_DIR. Keep the conservative
@@ -485,7 +486,7 @@ class SubprocessDriverImpl implements AgentDriver {
485
486
  private bumpIdleTimer(): void {
486
487
  this.clearIdleTimer();
487
488
  this.idleTimer = setTimeout(() => {
488
- const reason = `no event for ${this.config.idleTimeoutMs}ms`;
489
+ const reason = `no event for ${durationMs(this.config.idleTimeout)}ms`;
489
490
  // Idle before ready means start() is still pending; reject it (and kill
490
491
  // the child) or the orchestrator waits forever.
491
492
  const preReady = this.readyRejected !== null;
@@ -497,7 +498,7 @@ class SubprocessDriverImpl implements AgentDriver {
497
498
  // Kill immediately: the run is lost and the child would keep doing
498
499
  // billable work until close().
499
500
  this.terminate();
500
- }, this.config.idleTimeoutMs);
501
+ }, durationMs(this.config.idleTimeout));
501
502
  }
502
503
 
503
504
  private clearIdleTimer(): void {
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { DurationSchema, durationMs } from "./core/duration.js";
1
2
  // Public surface of @prismatic-io/lux. The package root exposes authoring
2
3
  // contracts, extension points, built-ins, and supported programmatic
3
4
  // lifecycles. Explicit exports keep implementation and test helpers private.
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { DurationSchema, durationMs } from "../core/duration.js";
2
3
  import {
3
4
  type CandidateEvaluation,
4
5
  type CandidateProposal,
@@ -16,7 +17,7 @@ export const ReflectiveOptimizerConfigSchema = z
16
17
  model: z.string().optional(),
17
18
  command: z.string().optional(),
18
19
  reasoningEffort: ReasoningEffortSchema.optional(),
19
- timeoutMs: z.number().int().positive().default(180_000),
20
+ timeout: DurationSchema.default("3 minutes"),
20
21
  maxComponentChars: z.number().int().positive().default(80_000),
21
22
  maxAlternativeChars: z.number().int().positive().default(40_000),
22
23
  maxFeedbackChars: z.number().int().positive().default(50_000),
@@ -273,7 +274,7 @@ const reflectiveOptimizer = (config: ReflectiveOptimizerConfig): Optimizer => ({
273
274
  model,
274
275
  command: config.command,
275
276
  reasoningEffort: config.reasoningEffort,
276
- timeoutMs: config.timeoutMs,
277
+ timeoutMs: durationMs(config.timeout),
277
278
  system: systemPrompt(input.campaign.evaluation.kind),
278
279
  signal: input.abortSignal,
279
280
  prompt: builtPrompt.prompt,
@@ -5,6 +5,7 @@ import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep
5
5
  import { temporaryDirectory } from "tempy";
6
6
  import { glob } from "tinyglobby";
7
7
  import { z } from "zod";
8
+ import { durationMs } from "../core/duration.js";
8
9
  import { exec } from "../core/exec.js";
9
10
  import {
10
11
  type Candidate,
@@ -226,7 +227,7 @@ export const validateCandidate = async (
226
227
  try {
227
228
  return await exec(validation.command, validation.args, {
228
229
  cwd: materializedRoot,
229
- timeoutMs: validation.timeoutMs,
230
+ timeoutMs: durationMs(validation.timeout),
230
231
  env,
231
232
  ...(signal ? { signal } : {}),
232
233
  });
@@ -236,7 +237,7 @@ export const validateCandidate = async (
236
237
  })().finally(() => rm(validationHome, { recursive: true, force: true }));
237
238
  if (result.timedOut || result.exitCode !== 0) {
238
239
  const reason = result.timedOut
239
- ? `timed out after ${validation.timeoutMs}ms`
240
+ ? `timed out after ${durationMs(validation.timeout)}ms`
240
241
  : `exited ${result.exitCode}`;
241
242
  throw new Error(
242
243
  `candidate validation '${validation.command}' ${reason}: ${result.stderr.slice(0, 500)}`,
@@ -2,10 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { z } from "zod";
5
- import {
6
- createRubricAssertion,
7
- RubricModelAccountingDetailSchema,
8
- } from "../assertions/rubric/index.js";
5
+ import { RubricModelAccountingDetailSchema } from "../assertions/rubric/index.js";
9
6
  import {
10
7
  addUsage,
11
8
  type Candidate,
@@ -26,6 +23,7 @@ import { profileOrThrow } from "./experiment-context.js";
26
23
  import type { ExperimentEvaluation, OpenedExperimentRun } from "./experiment-runs.js";
27
24
  import { average } from "./experiment-selection.js";
28
25
  import { evaluateGraderCandidate } from "./grader-experiment.js";
26
+ import { registryWithHarness } from "./harness-registry.js";
29
27
  import { loadEvalCase } from "./loader.js";
30
28
  import { Orchestrator, type SuiteCaseResult, type SuiteItem } from "./orchestrator.js";
31
29
  import { clusterCaseSamples, groupedCaseMetricSamples } from "./paired-effects.js";
@@ -496,7 +494,7 @@ export const evaluateAgentCandidate = async (options: {
496
494
  const profile = profileOrThrow(execution.config, profileId);
497
495
  const harness = profile.harness ?? execution.config.harness;
498
496
  const defaultDriver = profile.driver ?? execution.config.defaultDriver;
499
- const registry = execution.registry.withAssertion(createRubricAssertion(harness));
497
+ const registry = registryWithHarness(execution.registry, execution.config, harness);
500
498
  const orchestrator = new Orchestrator({
501
499
  environment: profile.environment ?? execution.config.environment,
502
500
  registry,
@@ -1,10 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { join, relative, resolve, sep } from "node:path";
3
3
  import { z } from "zod";
4
- import {
5
- createRubricAssertion,
6
- RubricModelAccountingDetailSchema,
7
- } from "../assertions/rubric/index.js";
4
+ import { RubricModelAccountingDetailSchema } from "../assertions/rubric/index.js";
8
5
  import {
9
6
  type Candidate,
10
7
  type CandidateEvaluation,
@@ -711,7 +708,7 @@ export const evaluateGraderCandidate = async (options: {
711
708
  const registry = buildRegistryFromConfig({
712
709
  ...execution.config,
713
710
  ...(harness ? { harness } : {}),
714
- }).withAssertion(createRubricAssertion(harness));
711
+ });
715
712
  const items: GraderEvidenceItem[] = [];
716
713
  for (const annotation of selected) {
717
714
  const rubric = await loadRubricDefinition(
@@ -213,7 +213,7 @@ export const gradeRun = async (
213
213
  if (validatedConfigs && index >= validatedConfigs.length) {
214
214
  throw new Error(`validated assertion plan is missing assertions[${index}]`);
215
215
  }
216
- // Pass the validated config so schema defaults (e.g. timeoutMs)
216
+ // Pass the validated config so schema defaults (e.g. timeout)
217
217
  // reach evaluate(); the raw assertion object has none applied.
218
218
  const result = AssertionResultSchema.parse(
219
219
  await impl.evaluate(validated as typeof a, run, {
@@ -0,0 +1,12 @@
1
+ import { createRubricAssertion } from "../assertions/rubric/index.js";
2
+ import type { HarnessModelConfig, Registry } from "../core/index.js";
3
+ import type { ResolvedLuxConfig } from "./config.js";
4
+
5
+ export const registryWithHarness = (
6
+ registry: Registry,
7
+ config: Pick<ResolvedLuxConfig, "assertions">,
8
+ harness: HarnessModelConfig | undefined,
9
+ ): Registry =>
10
+ config.assertions?.some((assertion) => assertion.type === "rubric")
11
+ ? registry
12
+ : registry.withAssertion(createRubricAssertion(harness));
@@ -9,7 +9,7 @@ const CANDIDATE_FORBIDDEN_FIELDS = [
9
9
  "provider",
10
10
  "model",
11
11
  "reasoningEffort",
12
- "timeoutMs",
12
+ "timeout",
13
13
  "promptOverride",
14
14
  ] as const;
15
15
  const RawRubricDefinitionSchema = z.looseObject({