@bitkyc08/opencodex 2.12.0 → 2.13.0

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 (173) hide show
  1. package/README.md +1 -1
  2. package/gui/dist/assets/index-BHldBl6_.js +76 -0
  3. package/gui/dist/index.html +1 -1
  4. package/package.json +1 -1
  5. package/src/adapters/anthropic-output-schema.ts +137 -0
  6. package/src/adapters/anthropic.ts +15 -0
  7. package/src/adapters/google-antigravity-replay.ts +269 -5
  8. package/src/adapters/openai-chat.ts +25 -18
  9. package/src/adapters/openai-responses.ts +63 -17
  10. package/src/bridge.ts +11 -5
  11. package/src/claude/inbound.ts +24 -3
  12. package/src/cli/account-api.ts +2 -0
  13. package/src/cli/account-extended.ts +262 -1
  14. package/src/cli/account.ts +3 -1
  15. package/src/cli/dispatch.ts +532 -0
  16. package/src/cli/doctor.ts +26 -21
  17. package/src/cli/help.ts +9 -278
  18. package/src/cli/index.ts +45 -506
  19. package/src/cli/lab.ts +171 -65
  20. package/src/cli/registry.ts +384 -0
  21. package/src/cli/root.ts +86 -0
  22. package/src/cli/status.ts +9 -2
  23. package/src/cli/v2.ts +37 -2
  24. package/src/codex/account-label.ts +14 -1
  25. package/src/codex/account-store.ts +12 -1
  26. package/src/codex/admission.ts +2 -1
  27. package/src/codex/auth-api.ts +3 -2
  28. package/src/codex/catalog/metadata.ts +6 -4
  29. package/src/codex/catalog/parsing.ts +31 -7
  30. package/src/codex/catalog/provider-fetch.ts +2 -1
  31. package/src/codex/catalog/sync.ts +65 -11
  32. package/src/codex/features.ts +437 -31
  33. package/src/codex/history-job.ts +6 -11
  34. package/src/codex/history-provider.ts +10 -9
  35. package/src/codex/inject.ts +14 -1
  36. package/src/codex/native-profile-startup.ts +49 -3
  37. package/src/codex/native-residue.ts +14 -2
  38. package/src/codex/paths.ts +75 -1
  39. package/src/codex/reset-credit-recovery.ts +1044 -0
  40. package/src/codex/shim.ts +876 -16
  41. package/src/codex/sync.ts +27 -2
  42. package/src/config.ts +148 -10
  43. package/src/generated/compatibility-version.json +2572 -0
  44. package/src/images/loop.ts +1 -1
  45. package/src/lab/artifacts/sanitize.ts +3 -3
  46. package/src/lab/automation/budgets.ts +78 -0
  47. package/src/lab/automation/config-persistence.ts +256 -0
  48. package/src/lab/automation/constants.ts +39 -0
  49. package/src/lab/automation/cooldown.ts +103 -0
  50. package/src/lab/automation/dispatch.ts +211 -0
  51. package/src/lab/automation/index.ts +13 -0
  52. package/src/lab/automation/orchestrator.ts +480 -0
  53. package/src/lab/automation/persistence.ts +512 -0
  54. package/src/lab/automation/planner.ts +371 -0
  55. package/src/lab/automation/policy.ts +136 -0
  56. package/src/lab/automation/queue.ts +191 -0
  57. package/src/lab/automation/recovery.ts +24 -0
  58. package/src/lab/automation/route-context.ts +21 -0
  59. package/src/lab/automation/run-key.ts +44 -0
  60. package/src/lab/automation/runs-query.ts +34 -0
  61. package/src/lab/automation/types.ts +160 -0
  62. package/src/lab/conformance/types.ts +23 -0
  63. package/src/lab/events/types.ts +2 -1
  64. package/src/lab/events/validate.ts +2 -1
  65. package/src/lab/fabric/constants.ts +40 -0
  66. package/src/lab/fabric/executor.ts +492 -0
  67. package/src/lab/fabric/index.ts +80 -0
  68. package/src/lab/fabric/manifest.ts +222 -0
  69. package/src/lab/fabric/observe.ts +454 -0
  70. package/src/lab/fabric/patch.ts +79 -0
  71. package/src/lab/fabric/producer-child.ts +139 -0
  72. package/src/lab/fabric/producer-isolate.ts +276 -0
  73. package/src/lab/fabric/producer-protocol.ts +61 -0
  74. package/src/lab/fabric/scratch.ts +439 -0
  75. package/src/lab/fabric/subject.ts +106 -0
  76. package/src/lab/fabric/types.ts +134 -0
  77. package/src/lab/fabric/verifier.ts +98 -0
  78. package/src/lab/index.ts +2 -0
  79. package/src/lab/ledger/store.ts +173 -0
  80. package/src/lab/live/executor.ts +29 -4
  81. package/src/lab/live/transport.ts +3 -1
  82. package/src/lab/observe/from-conformance.ts +12 -57
  83. package/src/lab/observe/from-live.ts +9 -1
  84. package/src/lab/paths.ts +74 -9
  85. package/src/lab/projection/schema.ts +14 -1
  86. package/src/lab/projection/verdicts.ts +89 -53
  87. package/src/lab/projection/verification.ts +83 -3
  88. package/src/lab/query/catalog.ts +34 -5
  89. package/src/lab/query/freshness.ts +53 -0
  90. package/src/lab/query/index.ts +11 -0
  91. package/src/lab/query/latest-observation.ts +59 -0
  92. package/src/lab/query/passive-production.ts +159 -0
  93. package/src/lab/subject/installation-salt.ts +24 -4
  94. package/src/lab/subject/protocol-subject.ts +80 -0
  95. package/src/lab/subject/route-subject.ts +2 -1
  96. package/src/lib/fabric-task-execution-authority.ts +7 -0
  97. package/src/lib/fabric-task-host.ts +29 -0
  98. package/src/lib/lab-live-route-production.ts +130 -0
  99. package/src/lib/local-management-capability.ts +100 -0
  100. package/src/lib/server-resource-ownership.ts +71 -0
  101. package/src/lib/windows-elevation.ts +8 -1
  102. package/src/lib/windows-user-principal.ts +62 -2
  103. package/src/lib/winsw.ts +8 -2
  104. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  105. package/src/oauth/account-import/index.ts +15 -0
  106. package/src/oauth/account-import/parser.ts +83 -0
  107. package/src/oauth/account-import/registry.ts +18 -0
  108. package/src/oauth/account-import/service.ts +75 -0
  109. package/src/oauth/account-import/types.ts +91 -0
  110. package/src/oauth/anthropic.ts +12 -1
  111. package/src/oauth/callback-server.ts +8 -2
  112. package/src/oauth/chatgpt.ts +12 -1
  113. package/src/oauth/google-antigravity.ts +27 -0
  114. package/src/oauth/health.ts +12 -48
  115. package/src/oauth/index.ts +122 -0
  116. package/src/oauth/kimi.ts +16 -2
  117. package/src/oauth/nous.ts +749 -0
  118. package/src/oauth/store.ts +56 -3
  119. package/src/providers/codex-capacity.ts +1 -0
  120. package/src/providers/registry.ts +161 -16
  121. package/src/responses/custom-tool-compat.ts +182 -0
  122. package/src/responses/parser.ts +22 -0
  123. package/src/responses/reasoning-replay-cache.ts +168 -11
  124. package/src/responses/schema.ts +1 -1
  125. package/src/router.ts +5 -20
  126. package/src/routing/capability.ts +9 -3
  127. package/src/routing/compatibility/assemble.ts +159 -0
  128. package/src/routing/compatibility/behavior.ts +224 -0
  129. package/src/routing/compatibility/catalog.ts +99 -0
  130. package/src/routing/compatibility/endpoint.ts +52 -0
  131. package/src/routing/compatibility/policy.ts +181 -0
  132. package/src/routing/compatibility/reader.ts +110 -0
  133. package/src/routing/compatibility/subject.ts +191 -0
  134. package/src/routing/compatibility/types.ts +64 -0
  135. package/src/routing/compatibility/version.ts +104 -0
  136. package/src/routing/evaluator.ts +27 -32
  137. package/src/routing/profile.ts +118 -4
  138. package/src/routing/trace.ts +82 -28
  139. package/src/server/auth-cors.ts +7 -0
  140. package/src/server/background-lifecycle.ts +20 -7
  141. package/src/server/chat-completions.ts +2 -2
  142. package/src/server/claude-messages.ts +6 -3
  143. package/src/server/direct-local-http.ts +343 -0
  144. package/src/server/index.ts +80 -18
  145. package/src/server/lifecycle.ts +12 -4
  146. package/src/server/local-management-read-client.ts +90 -0
  147. package/src/server/management/agent-settings-routes.ts +27 -3
  148. package/src/server/management/lab-automation-routes.ts +206 -0
  149. package/src/server/management/lab-routes.ts +13 -0
  150. package/src/server/management/logs-usage-routes.ts +1 -0
  151. package/src/server/management/oauth-account-routes.ts +57 -1
  152. package/src/server/management/provider-routes.ts +16 -0
  153. package/src/server/management/routing-profile-routes.ts +12 -24
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/management-auth.ts +68 -3
  156. package/src/server/proxy-liveness.ts +3 -2
  157. package/src/server/request-decompress.ts +9 -3
  158. package/src/server/request-log.ts +53 -4
  159. package/src/server/responses/collaboration.ts +93 -17
  160. package/src/server/responses/compact.ts +3 -0
  161. package/src/server/responses/core.ts +202 -15
  162. package/src/server/responses/policy-fallback.ts +1 -0
  163. package/src/server/responses-custom-tool-repair.ts +280 -0
  164. package/src/server/responses-terminal-repair.ts +342 -0
  165. package/src/server/startup-action-control.ts +8 -1
  166. package/src/service.ts +278 -12
  167. package/src/types.ts +49 -3
  168. package/src/usage/expected-prices.ts +30 -0
  169. package/src/usage/log.ts +30 -4
  170. package/src/usage/summary.ts +153 -2
  171. package/src/web-search/loop.ts +1 -1
  172. package/gui/dist/assets/index-Cw1_mxO8.js +0 -70
  173. package/src/cli/internal-dispatch.ts +0 -20
package/src/cli/lab.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
- * `ocx lab` — read-only Compatibility Lab inspection (CL-04).
2
+ * `ocx lab` — Compatibility Lab inspection and explicit CL-08 automation controls.
3
3
  *
4
- * Local SQLite projection reads; no daemon, network, probes, or rebuilds.
4
+ * Read commands use the local SQLite projection. Automation mutations and manual runs are
5
+ * explicit operator actions; read commands never start probes or scheduler ticks.
5
6
  */
6
- import { getConfigDir } from "../config";
7
+ import { getConfigDir, readConfigDiagnostics } from "../config";
7
8
  import {
8
9
  ARTIFACT_CLASSES,
9
10
  EVIDENCE_LAYERS,
@@ -32,6 +33,8 @@ import {
32
33
  queryLabSubjectById,
33
34
  queryLabSubjects,
34
35
  queryLabVerdicts,
36
+ queryPassiveProductionSignals,
37
+ type PassiveProductionQueryResultV1,
35
38
  } from "../lab/query";
36
39
  import {
37
40
  CliUsageError,
@@ -43,9 +46,27 @@ import {
43
46
  takeIntegerOption,
44
47
  takeOption,
45
48
  } from "./runtime-api";
49
+ import {
50
+ buildLabAutomationStatus,
51
+ enqueueManualLabRun,
52
+ reconcileLabAutomationQueue,
53
+ setLabAutomationDispatchDeps,
54
+ startLabAutomationScheduler,
55
+ stopLabAutomationScheduler,
56
+ } from "../lab/automation/orchestrator";
57
+ import { loadLabAutomationState } from "../lab/automation/persistence";
58
+ import {
59
+ loadLabAutomationConfig,
60
+ saveLabAutomationPolicyConfig,
61
+ } from "../lab/automation/config-persistence";
62
+ import { planManualLabRun } from "../lab/automation/planner";
63
+ import { listLabAutomationRuns } from "../lab/automation/runs-query";
64
+ import { LabAutomationError, type LabAutomationLayer } from "../lab/automation/types";
65
+ import { createProductionLabRouteExecutor } from "../lib/lab-live-route-production";
46
66
 
47
67
  const USAGE = `Usage:
48
68
  ocx lab status [--json]
69
+ ocx lab production-signals --subject <id> [--limit <n>] [--json]
49
70
  ocx lab verdicts [--subject <id>] [--layer <layer>] [--suite <id>] [--verdict <v>] [--from <ms>] [--to <ms>] [--limit <n>] [--cursor <c>] [--json]
50
71
  ocx lab subjects [--kind <kind>] [--limit <n>] [--cursor <c>] [--json]
51
72
  ocx lab subject <subjectId> [--json]
@@ -54,7 +75,12 @@ const USAGE = `Usage:
54
75
  ocx lab event <eventId> [--json]
55
76
  ocx lab artifacts [--status <s>] [--artifact-class <c>] [--limit <n>] [--cursor <c>] [--json]
56
77
  ocx lab artifact <digest> [--json]
57
- ocx lab catalog [--layer <layer>] [--suite <id>] [--json]`;
78
+ ocx lab catalog [--layer <layer>] [--suite <id>] [--json]
79
+ ocx lab automation status [--json]
80
+ ocx lab automation enable [--protocol] [--live] [--json]
81
+ ocx lab automation disable [--json]
82
+ ocx lab automation runs [--limit <n>] [--cursor <c>] [--json]
83
+ ocx lab run --layer <layer> --scenario <id> [--provider <name>] [--model <id>] [--json]`;
58
84
 
59
85
  const ARTIFACT_STATUSES = ["present", "corrupt", "purged_unavailable"] as const;
60
86
  type ArtifactStatus = (typeof ARTIFACT_STATUSES)[number];
@@ -74,6 +100,7 @@ function labErrorMessage(err: unknown): string {
74
100
  if (err instanceof LabProjectionUnavailableError) return "lab projection is not available";
75
101
  if (err instanceof LabProjectionIncompatibleError) return "lab projection schema or spec version is incompatible";
76
102
  if (err instanceof InvalidCursorError) return "invalid cursor";
103
+ if (err instanceof LabAutomationError && err.code === "invalid_cursor") return "invalid cursor";
77
104
  return "lab read failed";
78
105
  }
79
106
 
@@ -163,6 +190,39 @@ function catalogLines(scenarios: ReturnType<typeof queryLabCatalogEntries>): str
163
190
  return lines.length > 0 ? lines : ["No catalog scenarios"];
164
191
  }
165
192
 
193
+ function passiveProductionLines(result: PassiveProductionQueryResultV1): string[] {
194
+ const summary = result.summary;
195
+ return [
196
+ "Observed production traffic (not Lab verification)",
197
+ `Attempts: ${summary.recentProductionAttempts} | Successes: ${summary.recentSuccessfulAttempts} | Route errors: ${summary.recentRouteErrorSignals}`,
198
+ ...(summary.lastObservedProductionAttempt !== undefined
199
+ ? [`Last observed: ${summary.lastObservedProductionAttempt}`]
200
+ : []),
201
+ ];
202
+ }
203
+
204
+ function automationStatusLines(status: ReturnType<typeof buildLabAutomationStatus>): string[] {
205
+ return [
206
+ `Automation enabled: ${status.policy.enabled}`,
207
+ `Scheduler intent: ${status.policy.enabled ? "enabled" : "disabled"}`,
208
+ `Layers: protocol=${status.policy.layers.protocolConformance} live=${status.policy.layers.liveRouteCompatibility} task=${status.policy.layers.taskEffectiveness}`,
209
+ `Queued: ${status.counters.queued} | Running: ${status.counters.running}`,
210
+ `Run budget remaining: ${status.counters.remainingRunBudget} | Live request budget: ${status.counters.remainingLiveRequestBudget}`,
211
+ ];
212
+ }
213
+
214
+ function automationCliStatus(status: ReturnType<typeof buildLabAutomationStatus>) {
215
+ const { schedulerRunning: _processLocalSchedulerState, ...stable } = status;
216
+ return { ...stable, schedulerIntentEnabled: status.policy.enabled };
217
+ }
218
+
219
+ function runListLines(page: ReturnType<typeof listLabAutomationRuns>): string[] {
220
+ const lines = page.items.map((row) =>
221
+ `${row.runId} ${row.state} ${row.evidenceLayer} ${row.scenarioId} trigger=${row.trigger}`,
222
+ );
223
+ return lines.length > 0 ? lines : ["No automation runs"];
224
+ }
225
+
166
226
  export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): Promise<number> {
167
227
  return runCliAction(async () => {
168
228
  const configDir = deps.configDir ?? getConfigDir();
@@ -178,6 +238,15 @@ export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): P
178
238
  printData(status, wantsJson, statusSummary(status));
179
239
  return;
180
240
  }
241
+ case "production-signals": {
242
+ const subjectId = takeOption(rest, "--subject");
243
+ const limit = takeIntegerOption(rest, "--limit", { min: 1 });
244
+ rejectArgs(rest, USAGE);
245
+ if (!subjectId) throw new CliUsageError("--subject is required", USAGE);
246
+ const result = queryPassiveProductionSignals(subjectId, limit, configDir);
247
+ printData(result, wantsJson, passiveProductionLines(result));
248
+ return;
249
+ }
181
250
  case "verdicts": {
182
251
  const subjectId = takeOption(rest, "--subject");
183
252
  const layer = takeEnumOption<EvidenceLayer>(
@@ -199,14 +268,7 @@ export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): P
199
268
  const limit = takeIntegerOption(rest, "--limit", { min: 1 });
200
269
  const cursor = takeOption(rest, "--cursor");
201
270
  rejectArgs(rest, USAGE);
202
- const page = queryLabVerdicts({
203
- subjectId,
204
- layer,
205
- suiteId,
206
- verdict,
207
- from,
208
- to,
209
- }, cursor, limit, configDir);
271
+ const page = queryLabVerdicts({ subjectId, layer, suiteId, verdict, from, to }, cursor, limit, configDir);
210
272
  printData(page, wantsJson, verdictLines(page));
211
273
  return;
212
274
  }
@@ -231,52 +293,23 @@ export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): P
231
293
  }
232
294
  case "observations": {
233
295
  const subjectId = takeOption(rest, "--subject");
234
- const layer = takeEnumOption<EvidenceLayer>(
235
- rest,
236
- "--layer",
237
- EVIDENCE_LAYERS,
238
- "--layer must be a supported evidence layer",
239
- );
296
+ const layer = takeEnumOption<EvidenceLayer>(rest, "--layer", EVIDENCE_LAYERS, "--layer must be a supported evidence layer");
240
297
  const suiteId = takeOption(rest, "--suite");
241
298
  const scenarioId = takeOption(rest, "--scenario");
242
- const outcome = takeEnumOption<ObservationOutcome>(
243
- rest,
244
- "--outcome",
245
- OUTCOMES,
246
- "--outcome must be a supported observation outcome",
247
- );
248
- const executionMode = takeEnumOption<ExecutionMode>(
249
- rest,
250
- "--execution-mode",
251
- EXECUTION_MODES,
252
- "--execution-mode must be a supported execution mode",
253
- );
299
+ const outcome = takeEnumOption<ObservationOutcome>(rest, "--outcome", OUTCOMES, "--outcome must be a supported observation outcome");
300
+ const executionMode = takeEnumOption<ExecutionMode>(rest, "--execution-mode", EXECUTION_MODES, "--execution-mode must be a supported execution mode");
254
301
  const from = takeIntegerOption(rest, "--from", { min: 0 });
255
302
  const to = takeIntegerOption(rest, "--to", { min: 0 });
256
303
  assertRange(from, to);
257
304
  const limit = takeIntegerOption(rest, "--limit", { min: 1 });
258
305
  const cursor = takeOption(rest, "--cursor");
259
306
  rejectArgs(rest, USAGE);
260
- const page = queryLabObservations({
261
- subjectId,
262
- layer,
263
- suiteId,
264
- scenarioId,
265
- outcome,
266
- executionMode,
267
- from,
268
- to,
269
- }, cursor, limit, configDir);
307
+ const page = queryLabObservations({ subjectId, layer, suiteId, scenarioId, outcome, executionMode, from, to }, cursor, limit, configDir);
270
308
  printData(page, wantsJson, observationLines(page));
271
309
  return;
272
310
  }
273
311
  case "events": {
274
- const eventKind = takeEnumOption<LabEventKind>(
275
- rest,
276
- "--event-kind",
277
- EVENT_KINDS,
278
- "--event-kind must be a supported lab event kind",
279
- );
312
+ const eventKind = takeEnumOption<LabEventKind>(rest, "--event-kind", EVENT_KINDS, "--event-kind must be a supported lab event kind");
280
313
  const subjectId = takeOption(rest, "--subject");
281
314
  const from = takeIntegerOption(rest, "--from", { min: 0 });
282
315
  const to = takeIntegerOption(rest, "--to", { min: 0 });
@@ -309,18 +342,8 @@ export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): P
309
342
  return;
310
343
  }
311
344
  case "artifacts": {
312
- const status = takeEnumOption<ArtifactStatus>(
313
- rest,
314
- "--status",
315
- ARTIFACT_STATUSES,
316
- "--status must be present, corrupt, or purged_unavailable",
317
- );
318
- const artifactClass = takeEnumOption<ArtifactClass>(
319
- rest,
320
- "--artifact-class",
321
- ARTIFACT_CLASSES,
322
- "--artifact-class must be a supported artifact class",
323
- );
345
+ const status = takeEnumOption<ArtifactStatus>(rest, "--status", ARTIFACT_STATUSES, "--status must be present, corrupt, or purged_unavailable");
346
+ const artifactClass = takeEnumOption<ArtifactClass>(rest, "--artifact-class", ARTIFACT_CLASSES, "--artifact-class must be a supported artifact class");
324
347
  const limit = takeIntegerOption(rest, "--limit", { min: 1 });
325
348
  const cursor = takeOption(rest, "--cursor");
326
349
  rejectArgs(rest, USAGE);
@@ -339,18 +362,101 @@ export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): P
339
362
  return;
340
363
  }
341
364
  case "catalog": {
342
- const layer = takeEnumOption<EvidenceLayer>(
343
- rest,
344
- "--layer",
345
- EVIDENCE_LAYERS,
346
- "--layer must be a supported evidence layer",
347
- );
365
+ const layer = takeEnumOption<EvidenceLayer>(rest, "--layer", EVIDENCE_LAYERS, "--layer must be a supported evidence layer");
348
366
  const suiteId = takeOption(rest, "--suite");
349
367
  rejectArgs(rest, USAGE);
350
368
  const scenarios = queryLabCatalogEntries({ layer, suiteId });
351
369
  printData({ scenarios }, wantsJson, catalogLines(scenarios));
352
370
  return;
353
371
  }
372
+ case "automation": {
373
+ const [automationSub = "status", ...automationRest] = rest;
374
+ switch (automationSub) {
375
+ case "status": {
376
+ rejectArgs(automationRest, USAGE);
377
+ const status = buildLabAutomationStatus(configDir);
378
+ printData(automationCliStatus(status), wantsJson, automationStatusLines(status));
379
+ return;
380
+ }
381
+ case "enable": {
382
+ const enableProtocol = takeFlag(automationRest, "--protocol");
383
+ const enableLive = takeFlag(automationRest, "--live");
384
+ rejectArgs(automationRest, USAGE);
385
+ const selectionExplicit = enableProtocol || enableLive;
386
+ const current = loadLabAutomationConfig(configDir).policy;
387
+ const policy = {
388
+ ...current,
389
+ enabled: true,
390
+ layers: {
391
+ protocolConformance: selectionExplicit ? enableProtocol : current.layers.protocolConformance,
392
+ liveRouteCompatibility: selectionExplicit ? enableLive : current.layers.liveRouteCompatibility,
393
+ taskEffectiveness: false,
394
+ },
395
+ taskEffectivenessBackgroundEnabled: false,
396
+ };
397
+ saveLabAutomationPolicyConfig(policy, configDir);
398
+ reconcileLabAutomationQueue(configDir);
399
+ startLabAutomationScheduler(configDir);
400
+ const status = buildLabAutomationStatus(configDir);
401
+ printData(automationCliStatus(status), wantsJson, automationStatusLines(status));
402
+ return;
403
+ }
404
+ case "disable": {
405
+ rejectArgs(automationRest, USAGE);
406
+ const policy = { ...loadLabAutomationConfig(configDir).policy, enabled: false };
407
+ saveLabAutomationPolicyConfig(policy, configDir);
408
+ reconcileLabAutomationQueue(configDir);
409
+ stopLabAutomationScheduler(configDir);
410
+ const status = buildLabAutomationStatus(configDir);
411
+ printData(automationCliStatus(status), wantsJson, automationStatusLines(status));
412
+ return;
413
+ }
414
+ case "runs": {
415
+ const limit = takeIntegerOption(automationRest, "--limit", { min: 1 });
416
+ const cursor = takeOption(automationRest, "--cursor");
417
+ rejectArgs(automationRest, USAGE);
418
+ const page = listLabAutomationRuns(loadLabAutomationState(configDir), limit ?? 50, cursor);
419
+ printData(page, wantsJson, runListLines(page));
420
+ return;
421
+ }
422
+ default:
423
+ throw new CliUsageError(`unknown automation subcommand: ${automationSub}`, USAGE);
424
+ }
425
+ }
426
+ case "run": {
427
+ const layer = takeEnumOption<LabAutomationLayer>(
428
+ rest,
429
+ "--layer",
430
+ ["protocol_conformance", "live_route_compatibility", "task_effectiveness"] as const,
431
+ "--layer must be protocol_conformance, live_route_compatibility, or task_effectiveness",
432
+ );
433
+ const scenarioId = takeOption(rest, "--scenario");
434
+ const providerName = takeOption(rest, "--provider");
435
+ const modelId = takeOption(rest, "--model");
436
+ rejectArgs(rest, USAGE);
437
+ if (!layer || !scenarioId) throw new CliUsageError("--layer and --scenario are required", USAGE);
438
+ const configSnapshot = readConfigDiagnostics().config;
439
+ if (layer === "live_route_compatibility") {
440
+ const loadConfig = () => configSnapshot;
441
+ setLabAutomationDispatchDeps({
442
+ configDir,
443
+ loadConfig,
444
+ routeExecutor: createProductionLabRouteExecutor({ configDir, loadConfig }),
445
+ });
446
+ }
447
+ const planned = planManualLabRun({
448
+ evidenceLayer: layer,
449
+ scenarioId,
450
+ providerName,
451
+ modelId,
452
+ config: configSnapshot,
453
+ configDir,
454
+ });
455
+ const record = await enqueueManualLabRun(planned, configDir);
456
+ if (!record) throw new CliUsageError("manual run enqueue failed", USAGE);
457
+ printData({ run: record }, wantsJson, [`Manual run ${record.runId} -> ${record.state}`]);
458
+ return;
459
+ }
354
460
  default:
355
461
  throw new CliUsageError(`unknown lab subcommand: ${sub}`, USAGE);
356
462
  }
@@ -364,4 +470,4 @@ export async function handleLabCommand(argv: string[], deps: LabCliDeps = {}): P
364
470
  });
365
471
  }
366
472
 
367
- export const LAB_USAGE = USAGE;
473
+ export const LAB_USAGE = USAGE;