@bike4mind/cli 0.18.0 → 0.18.1

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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { $ as PermissionDeniedError, $t as extractSnippetMeta, A as FriendshipEvents, At as XAI_IMAGE_MODELS, B as InboxEvents, Bt as isZodError, C as CorruptedFileError, Ct as TransferCreditTransaction, D as FeedbackEvents, Dt as VIDEO_SIZE_CONSTRAINTS, E as FavoriteDocumentType, Et as UnprocessableEntityError, F as HTTPError, Ft as getViewById, G as LLMEvents, Gt as sanitizeTelemetryError, H as InviteEvents, Ht as obfuscateApiKey, I as HttpStatus, It as isGPTImage2Model, J as ModelBackend, Jt as substituteArguments$1, K as MiscEvents, Kt as secureParameters, L as ImageEditUsageTransaction, Lt as isGPTImageModel, M as GenerateImageToolCallSchema, Mt as dayjsConfig_default, N as GenericCreditAddTransaction, Nt as getAccessibleDataLakes, O as FileEvents, Ot as VideoGenerationUsageTransaction, P as GenericCreditDeductTransaction, Pt as getMcpProviderMetadata, Q as Permission, Qt as buildRateLimitLogEntry, R as ImageGenerationUsageTransaction, Rt as isModelAccessible, S as CompletionApiUsageTransaction, St as ToolUsageTransaction, T as ElabsEvents, Tt as UnauthorizedError, U as InviteType, Ut as parseSkillArguments, V as InternalServerError, Vt as mapMimeTypeToArtifactType, W as KnowledgeType, Wt as resolveNavigationIntents, X as OpenAIEmbeddingModel, Y as NotFoundError, Yt as toDataLakeConfig, Z as OpenAIImageGenerationInput, _ as BadRequestError, _t as SupportedFabFileMimeTypes, at as QuestMasterParamsSchema, b as ChatModels, bt as TextGenerationUsageTransaction, c as AiEvents, ct as RechartsChartTypeList, d as ApiKeyType, dt as ResearchTaskExecutionType, en as isNearLimit, et as ProfileEvents, f as AppFileEvents, ft as ResearchTaskPeriodicFrequencyType, g as BFL_SAFETY_TOLERANCE, gt as SubscriptionCreditTransaction, h as BFL_IMAGE_MODELS, ht as SpeechToTextUsageTransaction, it as PurchaseTransaction, j as GEMINI_IMAGE_MODELS, jt as b4mLLMTools, k as ForbiddenError, kt as VideoModels, l as ApiKeyEvents, lt as RegInviteEvents, m as AuthEvents, mt as SessionEvents, n as logger, nn as CollectionType, nt as PromptIntentSchema, ot as RealtimeVoiceUsageTransaction, p as ArtifactTypeSchema, pt as ResearchTaskType, q as ModalEvents, qt as settingsMap, rt as PromptMetaZodSchema, s as ALERT_THRESHOLDS, st as ReceivedCreditTransaction, t as ConfigStore, tn as parseRateLimitHeaders, tt as ProjectEvents, u as ApiKeyScope, ut as ResearchModeParamsSchema, vt as TagType, w as DashboardParamsSchema, wt as UiNavigationEvents, x as ClaudeArtifactMimeTypes, xt as TooManyRequestsError, y as ChatCompletionCreateInputSchema, yt as TaskScheduleHandler, z as ImageModels, zt as isSupportedEmbeddingModel } from "./ConfigStore-yJU1dxpP.mjs";
2
+ import { $ as PermissionDeniedError, $t as extractSnippetMeta, A as FriendshipEvents, At as XAI_IMAGE_MODELS, B as InboxEvents, Bt as isZodError, C as CorruptedFileError, Ct as TransferCreditTransaction, D as FeedbackEvents, Dt as VIDEO_SIZE_CONSTRAINTS, E as FavoriteDocumentType, Et as UnprocessableEntityError, F as HTTPError, Ft as getViewById, G as LLMEvents, Gt as sanitizeTelemetryError, H as InviteEvents, Ht as obfuscateApiKey, I as HttpStatus, It as isGPTImage2Model, J as ModelBackend, Jt as substituteArguments$1, K as MiscEvents, Kt as secureParameters, L as ImageEditUsageTransaction, Lt as isGPTImageModel, M as GenerateImageToolCallSchema, Mt as dayjsConfig_default, N as GenericCreditAddTransaction, Nt as getAccessibleDataLakes, O as FileEvents, Ot as VideoGenerationUsageTransaction, P as GenericCreditDeductTransaction, Pt as getMcpProviderMetadata, Q as Permission, Qt as buildRateLimitLogEntry, R as ImageGenerationUsageTransaction, Rt as isModelAccessible, S as CompletionApiUsageTransaction, St as ToolUsageTransaction, T as ElabsEvents, Tt as UnauthorizedError, U as InviteType, Ut as parseSkillArguments, V as InternalServerError, Vt as mapMimeTypeToArtifactType, W as KnowledgeType, Wt as resolveNavigationIntents, X as OpenAIEmbeddingModel, Y as NotFoundError, Yt as toDataLakeConfig, Z as OpenAIImageGenerationInput, _ as BadRequestError, _t as SupportedFabFileMimeTypes, at as QuestMasterParamsSchema, b as ChatModels, bt as TextGenerationUsageTransaction, c as AiEvents, ct as RechartsChartTypeList, d as ApiKeyType, dt as ResearchTaskExecutionType, en as isNearLimit, et as ProfileEvents, f as AppFileEvents, ft as ResearchTaskPeriodicFrequencyType, g as BFL_SAFETY_TOLERANCE, gt as SubscriptionCreditTransaction, h as BFL_IMAGE_MODELS, ht as SpeechToTextUsageTransaction, it as PurchaseTransaction, j as GEMINI_IMAGE_MODELS, jt as b4mLLMTools, k as ForbiddenError, kt as VideoModels, l as ApiKeyEvents, lt as RegInviteEvents, m as AuthEvents, mt as SessionEvents, n as logger, nn as CollectionType, nt as PromptIntentSchema, ot as RealtimeVoiceUsageTransaction, p as ArtifactTypeSchema, pt as ResearchTaskType, q as ModalEvents, qt as settingsMap, rt as PromptMetaZodSchema, s as ALERT_THRESHOLDS, st as ReceivedCreditTransaction, t as ConfigStore, tn as parseRateLimitHeaders, tt as ProjectEvents, u as ApiKeyScope, ut as ResearchModeParamsSchema, vt as TagType, w as DashboardParamsSchema, wt as UiNavigationEvents, x as ClaudeArtifactMimeTypes, xt as TooManyRequestsError, y as ChatCompletionCreateInputSchema, yt as TaskScheduleHandler, z as ImageModels, zt as isSupportedEmbeddingModel } from "./ConfigStore-BbTg-UaE.mjs";
3
3
  import { a as isUserLockedOut, c as userCanDisableMFA, d as userRequiresMFA, f as verifyBackupCode, i as getLockoutTimeRemaining, l as userEligibleForMFA, n as generateBackupCodes, o as recordFailedAttempt, p as verifyTOTPToken, r as generateTOTPSetup, s as shouldResetFailedAttempts, t as clearFailedAttempts, u as userHasMFAConfigured } from "./utils-fqhJmuB8.mjs";
4
4
  import { n as isPathAllowed, t as assertPathAllowed } from "./pathValidation-D8tjkQXE-1HwvsuYT.mjs";
5
- import { t as version } from "./package-B3f78bSl.mjs";
5
+ import { t as version } from "./package-CSa_ad8r.mjs";
6
6
  import { execFile, execFileSync, spawn } from "child_process";
7
7
  import crypto, { createHash, randomBytes, randomUUID } from "crypto";
8
8
  import { existsSync, promises, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -8876,22 +8876,22 @@ function makeSchedulingResult(args) {
8876
8876
  const OperationSchema = z.object({
8877
8877
  jobId: z.number(),
8878
8878
  machineId: z.number(),
8879
- duration: z.number().positive()
8879
+ duration: z.number().positive().max(1e6)
8880
8880
  });
8881
8881
  const JobSchema = z.object({
8882
8882
  id: z.number(),
8883
- name: z.string(),
8884
- operations: z.array(OperationSchema)
8883
+ name: z.string().max(200),
8884
+ operations: z.array(OperationSchema).max(1e3)
8885
8885
  });
8886
8886
  const MachineSchema = z.object({
8887
8887
  id: z.number(),
8888
- name: z.string()
8888
+ name: z.string().max(200)
8889
8889
  });
8890
8890
  const SchedulingProblemSchema = z.object({
8891
- name: z.string(),
8892
- description: z.string().optional(),
8893
- jobs: z.array(JobSchema),
8894
- machines: z.array(MachineSchema),
8891
+ name: z.string().max(200),
8892
+ description: z.string().max(2e3).optional(),
8893
+ jobs: z.array(JobSchema).max(1e3),
8894
+ machines: z.array(MachineSchema).max(1e3),
8895
8895
  solvedJobUrl: z.string().url().optional()
8896
8896
  });
8897
8897
  const SolverIdSchema = z.enum([
@@ -8955,13 +8955,18 @@ const greedySolver = {
8955
8955
  totalIterations: totalOps
8956
8956
  });
8957
8957
  }
8958
+ const makespan = Math.max(...schedule.map((op) => op.endTime));
8958
8959
  return makeSchedulingResult({
8959
8960
  solverId: "greedy",
8960
8961
  solverName: "Greedy (Earliest Start)",
8961
- makespan: Math.max(...schedule.map((op) => op.endTime)),
8962
+ makespan,
8962
8963
  schedule,
8963
8964
  elapsedMs: performance.now() - startTime,
8964
- iterations: totalOps
8965
+ iterations: totalOps,
8966
+ trace: [{
8967
+ solution: schedule,
8968
+ objective: makespan
8969
+ }]
8965
8970
  });
8966
8971
  }
8967
8972
  };
@@ -9092,6 +9097,34 @@ function validateScheduleFeasibility(schedule) {
9092
9097
  return true;
9093
9098
  }
9094
9099
  /**
9100
+ * Append a best-so-far snapshot, thinning the early history when over cap so the
9101
+ * trace stays bounded while preserving the first point and recent progress.
9102
+ * (Same thinning policy as the family engine's pushTrace.)
9103
+ */
9104
+ function pushScheduleTrace(trace, schedule, makespan) {
9105
+ trace.push({
9106
+ solution: schedule,
9107
+ objective: makespan
9108
+ });
9109
+ if (trace.length > 48) {
9110
+ const first = trace[0];
9111
+ const rest = trace.slice(1).filter((_, i) => i % 2 === 1);
9112
+ trace.length = 0;
9113
+ trace.push(first, ...rest);
9114
+ }
9115
+ }
9116
+ const nowMs = () => typeof performance !== "undefined" ? performance.now() : Date.now();
9117
+ /**
9118
+ * Combine the timeoutMs budget with cooperative cancellation into one stop check
9119
+ * that iterative solvers poll between moves — so a UI cancel or a timeout actually
9120
+ * halts a long run instead of being decorative. `defaultTimeoutMs` preserves each
9121
+ * solver's historical default when the caller omits timeoutMs.
9122
+ */
9123
+ function makeScheduleStopper(options, startTime, defaultTimeoutMs) {
9124
+ const deadline = startTime + (options?.timeoutMs ?? defaultTimeoutMs);
9125
+ return () => (options?.shouldCancel?.() ?? false) || nowMs() > deadline;
9126
+ }
9127
+ /**
9095
9128
  * Simple seeded pseudo-random number generator (mulberry32).
9096
9129
  */
9097
9130
  function createRng$1(seed) {
@@ -9127,19 +9160,25 @@ const naiveSolver = {
9127
9160
  description: "Evaluates all permutations to find the optimal schedule. Only feasible for small problems.",
9128
9161
  async solve(problem, options) {
9129
9162
  const startTime = performance.now();
9163
+ const stop = makeScheduleStopper(options, startTime, 15e3);
9130
9164
  const ops = buildOperationList(problem);
9131
9165
  const totalPerms = factorial(ops.length);
9132
9166
  const cappedTotal = Math.min(totalPerms, MAX_PERMUTATIONS);
9133
- let bestMakespan = Infinity;
9134
9167
  let bestSchedule = decodePermutation(ops, problem);
9168
+ let bestMakespan = computeMakespan(bestSchedule);
9135
9169
  let count = 0;
9170
+ const trace = [{
9171
+ solution: bestSchedule,
9172
+ objective: bestMakespan
9173
+ }];
9136
9174
  for (const perm of permutations(ops)) {
9137
- if (count >= MAX_PERMUTATIONS) break;
9175
+ if (count >= MAX_PERMUTATIONS || stop()) break;
9138
9176
  const schedule = decodePermutation(perm, problem);
9139
9177
  const makespan = computeMakespan(schedule);
9140
9178
  if (makespan < bestMakespan) {
9141
9179
  bestMakespan = makespan;
9142
9180
  bestSchedule = schedule;
9181
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9143
9182
  }
9144
9183
  count++;
9145
9184
  if (options?.onProgress && count % 100 === 0) options.onProgress({
@@ -9163,7 +9202,8 @@ const naiveSolver = {
9163
9202
  makespan: bestMakespan,
9164
9203
  schedule: bestSchedule,
9165
9204
  elapsedMs: performance.now() - startTime,
9166
- iterations: count
9205
+ iterations: count,
9206
+ trace
9167
9207
  });
9168
9208
  }
9169
9209
  };
@@ -9188,18 +9228,22 @@ const randomRestartSolver = {
9188
9228
  description: "Multiple random starts with local search improvement via operation swaps.",
9189
9229
  async solve(problem, options) {
9190
9230
  const startTime = performance.now();
9191
- const timeoutMs = options?.timeoutMs ?? 1e4;
9231
+ const stop = makeScheduleStopper(options, startTime, 1e4);
9192
9232
  const rng = createRng$1(42);
9193
- let globalBestMakespan = Infinity;
9194
9233
  let globalBestSchedule = decodePermutation(randomPermutation(problem, rng), problem);
9234
+ let globalBestMakespan = computeMakespan(globalBestSchedule);
9195
9235
  let totalIterations = 0;
9236
+ const trace = [{
9237
+ solution: globalBestSchedule,
9238
+ objective: globalBestMakespan
9239
+ }];
9196
9240
  for (let restart = 0; restart < RESTARTS; restart++) {
9197
- if (performance.now() - startTime > timeoutMs) break;
9241
+ if (stop()) break;
9198
9242
  let currentPerm = randomPermutation(problem, rng);
9199
9243
  let currentSchedule = decodePermutation(currentPerm, problem);
9200
9244
  let currentMakespan = computeMakespan(currentSchedule);
9201
9245
  for (let i = 0; i < HILL_CLIMB_ITERATIONS; i++) {
9202
- if (performance.now() - startTime > timeoutMs) break;
9246
+ if (stop()) break;
9203
9247
  const neighborPerm = swapNeighbor(currentPerm, rng);
9204
9248
  const neighborSchedule = decodePermutation(neighborPerm, problem);
9205
9249
  const neighborMakespan = computeMakespan(neighborSchedule);
@@ -9213,6 +9257,7 @@ const randomRestartSolver = {
9213
9257
  if (currentMakespan < globalBestMakespan) {
9214
9258
  globalBestMakespan = currentMakespan;
9215
9259
  globalBestSchedule = currentSchedule;
9260
+ pushScheduleTrace(trace, globalBestSchedule, globalBestMakespan);
9216
9261
  }
9217
9262
  options?.onProgress?.({
9218
9263
  solverId: "random-restart",
@@ -9228,7 +9273,8 @@ const randomRestartSolver = {
9228
9273
  makespan: globalBestMakespan,
9229
9274
  schedule: globalBestSchedule,
9230
9275
  elapsedMs: performance.now() - startTime,
9231
- iterations: totalIterations
9276
+ iterations: totalIterations,
9277
+ trace
9232
9278
  });
9233
9279
  }
9234
9280
  };
@@ -9265,17 +9311,21 @@ function createSASolver(config) {
9265
9311
  description: config.description,
9266
9312
  async solve(problem, options) {
9267
9313
  const startTime = performance.now();
9268
- const timeoutMs = options?.timeoutMs ?? 3e4;
9314
+ const stop = makeScheduleStopper(options, startTime, 3e4);
9269
9315
  const rng = createRng$1(42);
9270
9316
  let currentPerm = randomPermutation(problem, rng);
9271
9317
  let currentSchedule = decodePermutation(currentPerm, problem);
9272
9318
  let currentMakespan = computeMakespan(currentSchedule);
9273
9319
  let bestSchedule = currentSchedule;
9274
9320
  let bestMakespan = currentMakespan;
9321
+ const trace = [{
9322
+ solution: bestSchedule,
9323
+ objective: bestMakespan
9324
+ }];
9275
9325
  let temperature = config.initialTemp;
9276
9326
  const progressInterval = Math.max(1, Math.floor(config.iterations / 100));
9277
9327
  for (let i = 0; i < config.iterations; i++) {
9278
- if (performance.now() - startTime > timeoutMs) break;
9328
+ if (stop()) break;
9279
9329
  const neighborPerm = swapNeighbor(currentPerm, rng);
9280
9330
  const neighborSchedule = decodePermutation(neighborPerm, problem);
9281
9331
  const neighborMakespan = computeMakespan(neighborSchedule);
@@ -9287,6 +9337,7 @@ function createSASolver(config) {
9287
9337
  if (currentMakespan < bestMakespan) {
9288
9338
  bestSchedule = currentSchedule;
9289
9339
  bestMakespan = currentMakespan;
9340
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9290
9341
  }
9291
9342
  }
9292
9343
  temperature *= config.coolingRate;
@@ -9311,7 +9362,8 @@ function createSASolver(config) {
9311
9362
  makespan: bestMakespan,
9312
9363
  schedule: bestSchedule,
9313
9364
  elapsedMs: performance.now() - startTime,
9314
- iterations: config.iterations
9365
+ iterations: config.iterations,
9366
+ trace
9315
9367
  });
9316
9368
  }
9317
9369
  };
@@ -9326,7 +9378,7 @@ const simulatedAnnealingLargeSolver = createSASolver(SA_CONFIGS[2]);
9326
9378
  * forbidden for a period. This prevents cycling and forces exploration
9327
9379
  * of new regions of the solution space.
9328
9380
  */
9329
- const ITERATIONS$1 = 1e4;
9381
+ const ITERATIONS$2 = 1e4;
9330
9382
  const TABU_TENURE = 15;
9331
9383
  const NEIGHBORHOOD_SIZE = 20;
9332
9384
  const tabuSolver = {
@@ -9335,17 +9387,21 @@ const tabuSolver = {
9335
9387
  description: "Hill climbing with short-term memory to avoid revisiting recent solutions.",
9336
9388
  async solve(problem, options) {
9337
9389
  const startTime = performance.now();
9338
- const timeoutMs = options?.timeoutMs ?? 15e3;
9390
+ const stop = makeScheduleStopper(options, startTime, 15e3);
9339
9391
  const rng = createRng$1(42);
9340
9392
  let currentPerm = randomPermutation(problem, rng);
9341
9393
  let currentSchedule = decodePermutation(currentPerm, problem);
9342
9394
  let currentMakespan = computeMakespan(currentSchedule);
9343
9395
  let bestSchedule = currentSchedule;
9344
9396
  let bestMakespan = currentMakespan;
9397
+ const trace = [{
9398
+ solution: bestSchedule,
9399
+ objective: bestMakespan
9400
+ }];
9345
9401
  const tabuList = /* @__PURE__ */ new Map();
9346
- const progressInterval = Math.max(1, Math.floor(ITERATIONS$1 / 100));
9347
- for (let iter = 0; iter < ITERATIONS$1; iter++) {
9348
- if (performance.now() - startTime > timeoutMs) break;
9402
+ const progressInterval = Math.max(1, Math.floor(ITERATIONS$2 / 100));
9403
+ for (let iter = 0; iter < ITERATIONS$2; iter++) {
9404
+ if (stop()) break;
9349
9405
  let bestNeighborPerm = null;
9350
9406
  let bestNeighborMakespan = Infinity;
9351
9407
  let bestSwapKey = "";
@@ -9371,6 +9427,7 @@ const tabuSolver = {
9371
9427
  if (currentMakespan < bestMakespan) {
9372
9428
  bestMakespan = currentMakespan;
9373
9429
  bestSchedule = currentSchedule;
9430
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9374
9431
  }
9375
9432
  }
9376
9433
  if (iter % 100 === 0) {
@@ -9378,18 +9435,18 @@ const tabuSolver = {
9378
9435
  }
9379
9436
  if (options?.onProgress && iter % progressInterval === 0) options.onProgress({
9380
9437
  solverId: "tabu",
9381
- percentage: Math.round(iter / ITERATIONS$1 * 100),
9438
+ percentage: Math.round(iter / ITERATIONS$2 * 100),
9382
9439
  bestObjective: bestMakespan,
9383
9440
  currentIteration: iter,
9384
- totalIterations: ITERATIONS$1
9441
+ totalIterations: ITERATIONS$2
9385
9442
  });
9386
9443
  }
9387
9444
  options?.onProgress?.({
9388
9445
  solverId: "tabu",
9389
9446
  percentage: 100,
9390
9447
  bestObjective: bestMakespan,
9391
- currentIteration: ITERATIONS$1,
9392
- totalIterations: ITERATIONS$1
9448
+ currentIteration: ITERATIONS$2,
9449
+ totalIterations: ITERATIONS$2
9393
9450
  });
9394
9451
  return makeSchedulingResult({
9395
9452
  solverId: "tabu",
@@ -9397,7 +9454,8 @@ const tabuSolver = {
9397
9454
  makespan: bestMakespan,
9398
9455
  schedule: bestSchedule,
9399
9456
  elapsedMs: performance.now() - startTime,
9400
- iterations: ITERATIONS$1
9457
+ iterations: ITERATIONS$2,
9458
+ trace
9401
9459
  });
9402
9460
  }
9403
9461
  };
@@ -9417,7 +9475,7 @@ const geneticAlgorithmSolver = {
9417
9475
  description: "Population-based search with tournament selection, crossover, and mutation.",
9418
9476
  async solve(problem, options) {
9419
9477
  const startTime = performance.now();
9420
- const timeoutMs = options?.timeoutMs ?? 2e4;
9478
+ const stop = makeScheduleStopper(options, startTime, 2e4);
9421
9479
  const rng = createRng$1(42);
9422
9480
  let population = [];
9423
9481
  for (let i = 0; i < POPULATION_SIZE; i++) {
@@ -9429,8 +9487,12 @@ const geneticAlgorithmSolver = {
9429
9487
  });
9430
9488
  }
9431
9489
  let bestIndividual = population.reduce((best, ind) => ind.makespan < best.makespan ? ind : best);
9490
+ const trace = [{
9491
+ solution: decodePermutation(bestIndividual.perm, problem),
9492
+ objective: bestIndividual.makespan
9493
+ }];
9432
9494
  for (let gen = 0; gen < GENERATIONS; gen++) {
9433
- if (performance.now() - startTime > timeoutMs) break;
9495
+ if (stop()) break;
9434
9496
  const newPopulation = [];
9435
9497
  newPopulation.push({ ...bestIndividual });
9436
9498
  while (newPopulation.length < POPULATION_SIZE) {
@@ -9446,7 +9508,10 @@ const geneticAlgorithmSolver = {
9446
9508
  }
9447
9509
  population = newPopulation;
9448
9510
  const genBest = population.reduce((best, ind) => ind.makespan < best.makespan ? ind : best);
9449
- if (genBest.makespan < bestIndividual.makespan) bestIndividual = genBest;
9511
+ if (genBest.makespan < bestIndividual.makespan) {
9512
+ bestIndividual = genBest;
9513
+ pushScheduleTrace(trace, decodePermutation(bestIndividual.perm, problem), bestIndividual.makespan);
9514
+ }
9450
9515
  options?.onProgress?.({
9451
9516
  solverId: "genetic-algorithm",
9452
9517
  percentage: Math.round((gen + 1) / GENERATIONS * 100),
@@ -9462,7 +9527,8 @@ const geneticAlgorithmSolver = {
9462
9527
  makespan: bestIndividual.makespan,
9463
9528
  schedule: bestSchedule,
9464
9529
  elapsedMs: performance.now() - startTime,
9465
- iterations: GENERATIONS * POPULATION_SIZE
9530
+ iterations: GENERATIONS * POPULATION_SIZE,
9531
+ trace
9466
9532
  });
9467
9533
  }
9468
9534
  };
@@ -9507,10 +9573,10 @@ function crossover(parent1, parent2, rng) {
9507
9573
  * assignments) that appear in good solutions and evaporate over time.
9508
9574
  */
9509
9575
  const NUM_ANTS = 20;
9510
- const ITERATIONS = 100;
9576
+ const ITERATIONS$1 = 100;
9511
9577
  const ALPHA = 1;
9512
9578
  const BETA = 2;
9513
- const EVAPORATION = .1;
9579
+ const EVAPORATION$1 = .1;
9514
9580
  const Q = 100;
9515
9581
  const antColonySolver = {
9516
9582
  id: "ant-colony",
@@ -9518,7 +9584,7 @@ const antColonySolver = {
9518
9584
  description: "Swarm intelligence using pheromone trails to guide solution construction.",
9519
9585
  async solve(problem, options) {
9520
9586
  const startTime = performance.now();
9521
- const timeoutMs = options?.timeoutMs ?? 2e4;
9587
+ const stop = makeScheduleStopper(options, startTime, 2e4);
9522
9588
  const rng = createRng$1(42);
9523
9589
  const allOps = buildOperationList(problem);
9524
9590
  const numOps = allOps.length;
@@ -9526,10 +9592,14 @@ const antColonySolver = {
9526
9592
  const heuristic = allOps.map((ref) => {
9527
9593
  return 1 / problem.jobs.find((j) => j.id === ref.jobId).operations[ref.opIndex].duration;
9528
9594
  });
9529
- let bestMakespan = Infinity;
9530
9595
  let bestSchedule = decodePermutation(allOps, problem);
9531
- for (let iter = 0; iter < ITERATIONS; iter++) {
9532
- if (performance.now() - startTime > timeoutMs) break;
9596
+ let bestMakespan = computeMakespan(bestSchedule);
9597
+ const trace = [{
9598
+ solution: bestSchedule,
9599
+ objective: bestMakespan
9600
+ }];
9601
+ for (let iter = 0; iter < ITERATIONS$1; iter++) {
9602
+ if (stop()) break;
9533
9603
  const antSolutions = [];
9534
9604
  for (let ant = 0; ant < NUM_ANTS; ant++) {
9535
9605
  const perm = constructSolution(allOps, pheromone, heuristic, rng);
@@ -9542,9 +9612,10 @@ const antColonySolver = {
9542
9612
  if (makespan < bestMakespan) {
9543
9613
  bestMakespan = makespan;
9544
9614
  bestSchedule = schedule;
9615
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9545
9616
  }
9546
9617
  }
9547
- for (let i = 0; i < numOps; i++) for (let j = 0; j < numOps; j++) pheromone[i][j] *= 1 - EVAPORATION;
9618
+ for (let i = 0; i < numOps; i++) for (let j = 0; j < numOps; j++) pheromone[i][j] *= 1 - EVAPORATION$1;
9548
9619
  for (const solution of antSolutions) {
9549
9620
  const deposit = Q / solution.makespan;
9550
9621
  for (let pos = 0; pos < solution.perm.length; pos++) {
@@ -9554,10 +9625,10 @@ const antColonySolver = {
9554
9625
  }
9555
9626
  options?.onProgress?.({
9556
9627
  solverId: "ant-colony",
9557
- percentage: Math.round((iter + 1) / ITERATIONS * 100),
9628
+ percentage: Math.round((iter + 1) / ITERATIONS$1 * 100),
9558
9629
  bestObjective: bestMakespan,
9559
9630
  currentIteration: iter + 1,
9560
- totalIterations: ITERATIONS
9631
+ totalIterations: ITERATIONS$1
9561
9632
  });
9562
9633
  }
9563
9634
  return makeSchedulingResult({
@@ -9566,7 +9637,8 @@ const antColonySolver = {
9566
9637
  makespan: bestMakespan,
9567
9638
  schedule: bestSchedule,
9568
9639
  elapsedMs: performance.now() - startTime,
9569
- iterations: ITERATIONS * NUM_ANTS
9640
+ iterations: ITERATIONS$1 * NUM_ANTS,
9641
+ trace
9570
9642
  });
9571
9643
  }
9572
9644
  };
@@ -9813,23 +9885,18 @@ function validateSchedule(problem, schedule) {
9813
9885
  errors
9814
9886
  };
9815
9887
  }
9816
- const MAX_BINARY_VARS = 150;
9817
- const MAX_TOTAL_OPS = 80;
9818
- const MAX_OPS_PER_MACHINE = 8;
9819
- /**
9820
- * Load HiGHS - creates FRESH instance each time.
9821
- *
9822
- * We intentionally do NOT cache the HiGHS instance.
9823
- * Caching causes WASM corruption errors.
9824
- */
9888
+ /** Load a fresh HiGHS WASM instance (never cache it — see file header). */
9825
9889
  async function loadHiGHS() {
9826
9890
  const highsLoader = (await import("highs")).default;
9827
- return await highsLoader({ locateFile: (file) => {
9891
+ return highsLoader({ locateFile: (file) => {
9828
9892
  const globalSelf = globalThis;
9829
9893
  if (globalSelf.location?.origin) return `${globalSelf.location.origin}/${file}`;
9830
9894
  return file;
9831
9895
  } });
9832
9896
  }
9897
+ const MAX_BINARY_VARS = 150;
9898
+ const MAX_TOTAL_OPS = 80;
9899
+ const MAX_OPS_PER_MACHINE = 8;
9833
9900
  function elapsed(startTime) {
9834
9901
  return typeof performance !== "undefined" ? performance.now() - startTime : Date.now() - startTime;
9835
9902
  }
@@ -9861,6 +9928,21 @@ const highsSolver = {
9861
9928
  const lpString = generateLPFormulation(problem);
9862
9929
  if (!lpString.includes("Subject To") || !lpString.includes("Bounds")) throw new Error("Invalid LP formulation - missing required sections");
9863
9930
  reportProgress(20);
9931
+ if (options?.shouldCancel?.()) {
9932
+ const g = await greedySolver.solve(problem, {});
9933
+ return makeSchedulingResult({
9934
+ solverId: "highs",
9935
+ solverName: "HiGHS (WASM)",
9936
+ makespan: g.objective,
9937
+ schedule: g.solution,
9938
+ elapsedMs: elapsed(startTime),
9939
+ iterations: 0,
9940
+ metadata: {
9941
+ usedFallback: true,
9942
+ reason: "cancelled before HiGHS solve"
9943
+ }
9944
+ });
9945
+ }
9864
9946
  let solution;
9865
9947
  try {
9866
9948
  solution = highs.solve(lpString, {
@@ -9899,7 +9981,11 @@ const highsSolver = {
9899
9981
  makespan,
9900
9982
  schedule,
9901
9983
  elapsedMs: elapsed(startTime),
9902
- iterations: 1
9984
+ iterations: 1,
9985
+ trace: [{
9986
+ solution: schedule,
9987
+ objective: makespan
9988
+ }]
9903
9989
  });
9904
9990
  } catch (error) {
9905
9991
  reportProgress(0);
@@ -10544,31 +10630,39 @@ const allSolvers = [
10544
10630
  solverId: "qaoa-local",
10545
10631
  percentage: pct
10546
10632
  });
10633
+ const cancelled = () => options.shouldCancel?.() ?? false;
10547
10634
  progress(0);
10548
10635
  const greedyResult = await greedySolver.solve(problem, {});
10549
10636
  progress(10);
10550
10637
  const greedyMakespan = greedyResult.objective;
10551
10638
  const greedySchedule = greedyResult.solution;
10639
+ const greedyFallback = (reason) => makeSchedulingResult({
10640
+ solverId: "qaoa-local",
10641
+ solverName: "QAOA (Local)",
10642
+ makespan: greedyMakespan,
10643
+ schedule: greedySchedule,
10644
+ elapsedMs: elapsed(),
10645
+ trace: [{
10646
+ solution: greedySchedule,
10647
+ objective: greedyMakespan
10648
+ }],
10649
+ metadata: {
10650
+ layers: LAYERS$1,
10651
+ shots: 0,
10652
+ gamma: 0,
10653
+ beta: 0,
10654
+ rawMakespan: greedyMakespan,
10655
+ usedFallback: true,
10656
+ reason
10657
+ }
10658
+ });
10552
10659
  const encoded = encodeSchedulingWithMetadata(problem, greedyMakespan);
10553
10660
  const numQubits = encoded.qubo.size;
10554
10661
  if (numQubits > MAX_QUBITS || numQubits === 0) {
10555
10662
  progress(100);
10556
- return makeSchedulingResult({
10557
- solverId: "qaoa-local",
10558
- solverName: "QAOA (Local)",
10559
- makespan: greedyMakespan,
10560
- schedule: greedySchedule,
10561
- elapsedMs: elapsed(),
10562
- metadata: {
10563
- layers: LAYERS$1,
10564
- shots: 0,
10565
- gamma: 0,
10566
- beta: 0,
10567
- rawMakespan: greedyMakespan,
10568
- usedFallback: true
10569
- }
10570
- });
10663
+ return greedyFallback(numQubits === 0 ? "no qubits to encode" : `instance needs ${numQubits} qubits (cap ${MAX_QUBITS})`);
10571
10664
  }
10665
+ if (cancelled()) return greedyFallback("cancelled");
10572
10666
  const ising = quboToIsing(encoded.qubo);
10573
10667
  const backend = new LocalSimBackend();
10574
10668
  progress(20);
@@ -10578,6 +10672,7 @@ const allSolvers = [
10578
10672
  p: LAYERS$1
10579
10673
  });
10580
10674
  progress(75);
10675
+ if (cancelled()) return greedyFallback("cancelled");
10581
10676
  const gates = buildQAOACircuit(numQubits, ising, [gamma], [beta], LAYERS$1);
10582
10677
  const { probabilities } = await backend.run(numQubits, gates);
10583
10678
  const samples = sampleFromProbabilities(probabilities, FINAL_SHOTS$1);
@@ -10594,6 +10689,13 @@ const allSolvers = [
10594
10689
  makespan: winnerMakespan,
10595
10690
  schedule: winnerSchedule,
10596
10691
  elapsedMs: elapsed(),
10692
+ trace: [{
10693
+ solution: greedySchedule,
10694
+ objective: greedyMakespan
10695
+ }, {
10696
+ solution: winnerSchedule,
10697
+ objective: winnerMakespan
10698
+ }],
10597
10699
  metadata: {
10598
10700
  layers: LAYERS$1,
10599
10701
  shots: FINAL_SHOTS$1,
@@ -10901,54 +11003,1966 @@ No markdown, no explanation, no code blocks — just the raw JSON object.`;
10901
11003
  " \\/ <- fit here",
10902
11004
  "Minimize: sum(error^2)"
10903
11005
  ].join("\n");
10904
- /** Build a jittered grid mesh: cells at (col,row), edges between face-sharing neighbors. */
10905
- function gridMesh(cols, rows, cellName, weightAt, jitter) {
10906
- const nodes = [];
10907
- const padX = 100 / (cols + 1);
10908
- const padY = 100 / (rows + 1);
10909
- for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
10910
- const i = r * cols + c;
10911
- const { dx, dy } = jitter(i);
10912
- nodes.push({
10913
- id: i,
10914
- name: cellName(i),
10915
- x: Math.round(padX * (c + 1) + dx),
10916
- y: Math.round(padY * (r + 1) + dy)
10917
- });
11006
+ const routingSampleProblems = [
11007
+ {
11008
+ family: "routing",
11009
+ name: "Austin Food Truck Circuit",
11010
+ description: "A food truck owner wants the shortest nightly loop hitting ten Austin hotspots, starting and ending at the South Lamar commissary.",
11011
+ nodes: [
11012
+ {
11013
+ id: 0,
11014
+ name: "Commissary (S Lamar)",
11015
+ x: 38,
11016
+ y: 72
11017
+ },
11018
+ {
11019
+ id: 1,
11020
+ name: "Zilker Park",
11021
+ x: 30,
11022
+ y: 58
11023
+ },
11024
+ {
11025
+ id: 2,
11026
+ name: "Rainey St",
11027
+ x: 56,
11028
+ y: 60
11029
+ },
11030
+ {
11031
+ id: 3,
11032
+ name: "6th & Red River",
11033
+ x: 54,
11034
+ y: 48
11035
+ },
11036
+ {
11037
+ id: 4,
11038
+ name: "UT Campus",
11039
+ x: 52,
11040
+ y: 32
11041
+ },
11042
+ {
11043
+ id: 5,
11044
+ name: "Mueller Market",
11045
+ x: 72,
11046
+ y: 30
11047
+ },
11048
+ {
11049
+ id: 6,
11050
+ name: "East Cesar Chavez",
11051
+ x: 70,
11052
+ y: 56
11053
+ },
11054
+ {
11055
+ id: 7,
11056
+ name: "Barton Springs",
11057
+ x: 34,
11058
+ y: 64
11059
+ },
11060
+ {
11061
+ id: 8,
11062
+ name: "The Domain",
11063
+ x: 44,
11064
+ y: 8
11065
+ },
11066
+ {
11067
+ id: 9,
11068
+ name: "Oak Hill",
11069
+ x: 12,
11070
+ y: 86
11071
+ }
11072
+ ]
11073
+ },
11074
+ {
11075
+ family: "routing",
11076
+ name: "Kyiv Drone Resupply Run",
11077
+ description: "A logistics drone must visit twelve forward supply caches and return to base on one battery. Every wasted kilometer is flight time the battery does not have.",
11078
+ nodes: [
11079
+ {
11080
+ id: 0,
11081
+ name: "Base",
11082
+ x: 50,
11083
+ y: 92
11084
+ },
11085
+ {
11086
+ id: 1,
11087
+ name: "Cache Alpha",
11088
+ x: 18,
11089
+ y: 74
11090
+ },
11091
+ {
11092
+ id: 2,
11093
+ name: "Cache Bravo",
11094
+ x: 10,
11095
+ y: 48
11096
+ },
11097
+ {
11098
+ id: 3,
11099
+ name: "Cache Charlie",
11100
+ x: 24,
11101
+ y: 22
11102
+ },
11103
+ {
11104
+ id: 4,
11105
+ name: "Cache Delta",
11106
+ x: 44,
11107
+ y: 10
11108
+ },
11109
+ {
11110
+ id: 5,
11111
+ name: "Cache Echo",
11112
+ x: 66,
11113
+ y: 14
11114
+ },
11115
+ {
11116
+ id: 6,
11117
+ name: "Cache Foxtrot",
11118
+ x: 86,
11119
+ y: 26
11120
+ },
11121
+ {
11122
+ id: 7,
11123
+ name: "Cache Golf",
11124
+ x: 92,
11125
+ y: 52
11126
+ },
11127
+ {
11128
+ id: 8,
11129
+ name: "Cache Hotel",
11130
+ x: 80,
11131
+ y: 72
11132
+ },
11133
+ {
11134
+ id: 9,
11135
+ name: "Cache India",
11136
+ x: 62,
11137
+ y: 56
11138
+ },
11139
+ {
11140
+ id: 10,
11141
+ name: "Cache Juliet",
11142
+ x: 38,
11143
+ y: 44
11144
+ },
11145
+ {
11146
+ id: 11,
11147
+ name: "Cache Kilo",
11148
+ x: 30,
11149
+ y: 60
11150
+ }
11151
+ ]
11152
+ },
11153
+ {
11154
+ family: "routing",
11155
+ name: "Hill Country Brewery Tour",
11156
+ description: "Eight breweries, one designated-driver van, and a hard rule: end where you started. Minimize the drive so there is more time for the beer.",
11157
+ nodes: [
11158
+ {
11159
+ id: 0,
11160
+ name: "Depot (Dripping Springs)",
11161
+ x: 20,
11162
+ y: 60
11163
+ },
11164
+ {
11165
+ id: 1,
11166
+ name: "Jester King",
11167
+ x: 32,
11168
+ y: 48
11169
+ },
11170
+ {
11171
+ id: 2,
11172
+ name: "Vista Brewing",
11173
+ x: 16,
11174
+ y: 78
11175
+ },
11176
+ {
11177
+ id: 3,
11178
+ name: "Treaty Oak",
11179
+ x: 28,
11180
+ y: 70
11181
+ },
11182
+ {
11183
+ id: 4,
11184
+ name: "Last Stand",
11185
+ x: 46,
11186
+ y: 38
11187
+ },
11188
+ {
11189
+ id: 5,
11190
+ name: "Fitzhugh Brewing",
11191
+ x: 40,
11192
+ y: 56
11193
+ },
11194
+ {
11195
+ id: 6,
11196
+ name: "Family Business",
11197
+ x: 60,
11198
+ y: 30
11199
+ },
11200
+ {
11201
+ id: 7,
11202
+ name: "Suds Monkey",
11203
+ x: 24,
11204
+ y: 52
11205
+ }
11206
+ ]
11207
+ }
11208
+ ];
11209
+ const packingSampleProblems = [
11210
+ {
11211
+ family: "packing",
11212
+ name: "Mars Cargo Manifest",
11213
+ description: "Fourteen payload modules must ride to orbit in standardized cargo pods. Every pod you do not launch saves nine figures.",
11214
+ binCapacity: 100,
11215
+ binLabel: "pod",
11216
+ items: [
11217
+ {
11218
+ id: 0,
11219
+ name: "Habitat Frame",
11220
+ size: 62
11221
+ },
11222
+ {
11223
+ id: 1,
11224
+ name: "Water Recycler",
11225
+ size: 48
11226
+ },
11227
+ {
11228
+ id: 2,
11229
+ name: "Solar Array A",
11230
+ size: 55
11231
+ },
11232
+ {
11233
+ id: 3,
11234
+ name: "Solar Array B",
11235
+ size: 55
11236
+ },
11237
+ {
11238
+ id: 4,
11239
+ name: "Rover Chassis",
11240
+ size: 70
11241
+ },
11242
+ {
11243
+ id: 5,
11244
+ name: "Lab Module",
11245
+ size: 38
11246
+ },
11247
+ {
11248
+ id: 6,
11249
+ name: "Greenhouse Kit",
11250
+ size: 42
11251
+ },
11252
+ {
11253
+ id: 7,
11254
+ name: "Battery Bank",
11255
+ size: 30
11256
+ },
11257
+ {
11258
+ id: 8,
11259
+ name: "Antenna Mast",
11260
+ size: 22
11261
+ },
11262
+ {
11263
+ id: 9,
11264
+ name: "Med Bay Kit",
11265
+ size: 26
11266
+ },
11267
+ {
11268
+ id: 10,
11269
+ name: "Spare Parts Crate",
11270
+ size: 18
11271
+ },
11272
+ {
11273
+ id: 11,
11274
+ name: "Food Stores",
11275
+ size: 44
11276
+ },
11277
+ {
11278
+ id: 12,
11279
+ name: "Drill Assembly",
11280
+ size: 34
11281
+ },
11282
+ {
11283
+ id: 13,
11284
+ name: "3D Printer",
11285
+ size: 16
11286
+ }
11287
+ ]
11288
+ },
11289
+ {
11290
+ family: "packing",
11291
+ name: "Moving Day — Oak Hill",
11292
+ description: "Everything Erik owns has to fit into rented trucks. Each truck costs $180 a day, so the optimizer IS the budget.",
11293
+ binCapacity: 80,
11294
+ binLabel: "truck",
11295
+ items: [
11296
+ {
11297
+ id: 0,
11298
+ name: "Couch",
11299
+ size: 40
11300
+ },
11301
+ {
11302
+ id: 1,
11303
+ name: "Bed Frame",
11304
+ size: 32
11305
+ },
11306
+ {
11307
+ id: 2,
11308
+ name: "Mattress",
11309
+ size: 28
11310
+ },
11311
+ {
11312
+ id: 3,
11313
+ name: "Dining Table",
11314
+ size: 30
11315
+ },
11316
+ {
11317
+ id: 4,
11318
+ name: "Bookshelves",
11319
+ size: 24
11320
+ },
11321
+ {
11322
+ id: 5,
11323
+ name: "Desk Battlestation",
11324
+ size: 36
11325
+ },
11326
+ {
11327
+ id: 6,
11328
+ name: "Arcade Cabinet",
11329
+ size: 44
11330
+ },
11331
+ {
11332
+ id: 7,
11333
+ name: "Kitchen Boxes",
11334
+ size: 20
11335
+ },
11336
+ {
11337
+ id: 8,
11338
+ name: "Book Boxes",
11339
+ size: 26
11340
+ },
11341
+ {
11342
+ id: 9,
11343
+ name: "Garage Tools",
11344
+ size: 18
11345
+ },
11346
+ {
11347
+ id: 10,
11348
+ name: "Bikes",
11349
+ size: 16
11350
+ },
11351
+ {
11352
+ id: 11,
11353
+ name: "Server Rack",
11354
+ size: 22
11355
+ }
11356
+ ]
11357
+ },
11358
+ {
11359
+ family: "packing",
11360
+ name: "Server Rack Tetris",
11361
+ description: "Thirteen VM workloads need hosts. Each host burns power whether it is full or idle — consolidate or pay.",
11362
+ binCapacity: 64,
11363
+ binLabel: "host",
11364
+ items: [
11365
+ {
11366
+ id: 0,
11367
+ name: "Postgres Primary",
11368
+ size: 32
11369
+ },
11370
+ {
11371
+ id: 1,
11372
+ name: "Postgres Replica",
11373
+ size: 24
11374
+ },
11375
+ {
11376
+ id: 2,
11377
+ name: "Web Frontend x4",
11378
+ size: 16
11379
+ },
11380
+ {
11381
+ id: 3,
11382
+ name: "API Cluster",
11383
+ size: 28
11384
+ },
11385
+ {
11386
+ id: 4,
11387
+ name: "Redis Cache",
11388
+ size: 12
11389
+ },
11390
+ {
11391
+ id: 5,
11392
+ name: "ML Inference",
11393
+ size: 48
11394
+ },
11395
+ {
11396
+ id: 6,
11397
+ name: "CI Runners",
11398
+ size: 20
11399
+ },
11400
+ {
11401
+ id: 7,
11402
+ name: "Log Pipeline",
11403
+ size: 14
11404
+ },
11405
+ {
11406
+ id: 8,
11407
+ name: "Metrics Stack",
11408
+ size: 18
11409
+ },
11410
+ {
11411
+ id: 9,
11412
+ name: "Batch ETL",
11413
+ size: 26
11414
+ },
11415
+ {
11416
+ id: 10,
11417
+ name: "Search Index",
11418
+ size: 22
11419
+ },
11420
+ {
11421
+ id: 11,
11422
+ name: "Mail Service",
11423
+ size: 8
11424
+ },
11425
+ {
11426
+ id: 12,
11427
+ name: "Backup Agent",
11428
+ size: 10
11429
+ }
11430
+ ]
11431
+ }
11432
+ ];
11433
+ const assignmentSampleProblems = [
11434
+ {
11435
+ family: "assignment",
11436
+ name: "Raid Roster",
11437
+ description: "Eight raiders, eight roles, one progression night. The cost matrix is each player&apos;s discomfort in each role — minimize total grumbling.",
11438
+ agents: [
11439
+ {
11440
+ id: 0,
11441
+ name: "Thorgrim"
11442
+ },
11443
+ {
11444
+ id: 1,
11445
+ name: "Lyralei"
11446
+ },
11447
+ {
11448
+ id: 2,
11449
+ name: "Mogrash"
11450
+ },
11451
+ {
11452
+ id: 3,
11453
+ name: "Seraphine"
11454
+ },
11455
+ {
11456
+ id: 4,
11457
+ name: "Kettle"
11458
+ },
11459
+ {
11460
+ id: 5,
11461
+ name: "Vexx"
11462
+ },
11463
+ {
11464
+ id: 6,
11465
+ name: "Oakenheart"
11466
+ },
11467
+ {
11468
+ id: 7,
11469
+ name: "Zinnia"
11470
+ }
11471
+ ],
11472
+ tasks: [
11473
+ {
11474
+ id: 0,
11475
+ name: "Main Tank"
11476
+ },
11477
+ {
11478
+ id: 1,
11479
+ name: "Off Tank"
11480
+ },
11481
+ {
11482
+ id: 2,
11483
+ name: "Healer 1"
11484
+ },
11485
+ {
11486
+ id: 3,
11487
+ name: "Healer 2"
11488
+ },
11489
+ {
11490
+ id: 4,
11491
+ name: "Melee DPS"
11492
+ },
11493
+ {
11494
+ id: 5,
11495
+ name: "Ranged DPS"
11496
+ },
11497
+ {
11498
+ id: 6,
11499
+ name: "Raid Lead"
11500
+ },
11501
+ {
11502
+ id: 7,
11503
+ name: "Loot Council"
11504
+ }
11505
+ ],
11506
+ cost: [
11507
+ [
11508
+ 2,
11509
+ 4,
11510
+ 9,
11511
+ 9,
11512
+ 5,
11513
+ 7,
11514
+ 6,
11515
+ 8
11516
+ ],
11517
+ [
11518
+ 9,
11519
+ 8,
11520
+ 2,
11521
+ 3,
11522
+ 7,
11523
+ 5,
11524
+ 6,
11525
+ 4
11526
+ ],
11527
+ [
11528
+ 3,
11529
+ 2,
11530
+ 9,
11531
+ 8,
11532
+ 4,
11533
+ 6,
11534
+ 7,
11535
+ 9
11536
+ ],
11537
+ [
11538
+ 8,
11539
+ 9,
11540
+ 3,
11541
+ 2,
11542
+ 6,
11543
+ 4,
11544
+ 5,
11545
+ 6
11546
+ ],
11547
+ [
11548
+ 7,
11549
+ 6,
11550
+ 5,
11551
+ 5,
11552
+ 2,
11553
+ 3,
11554
+ 8,
11555
+ 7
11556
+ ],
11557
+ [
11558
+ 6,
11559
+ 7,
11560
+ 6,
11561
+ 6,
11562
+ 3,
11563
+ 2,
11564
+ 4,
11565
+ 5
11566
+ ],
11567
+ [
11568
+ 4,
11569
+ 3,
11570
+ 7,
11571
+ 7,
11572
+ 5,
11573
+ 6,
11574
+ 2,
11575
+ 3
11576
+ ],
11577
+ [
11578
+ 9,
11579
+ 8,
11580
+ 4,
11581
+ 4,
11582
+ 6,
11583
+ 5,
11584
+ 3,
11585
+ 2
11586
+ ]
11587
+ ]
11588
+ },
11589
+ {
11590
+ family: "assignment",
11591
+ name: "Surgery Suite Staffing",
11592
+ description: "Six surgeons, six operating rooms with different specialties on the board today. Cost = mismatch between surgeon expertise and case type.",
11593
+ agents: [
11594
+ {
11595
+ id: 0,
11596
+ name: "Dr. Asante"
11597
+ },
11598
+ {
11599
+ id: 1,
11600
+ name: "Dr. Brook"
11601
+ },
11602
+ {
11603
+ id: 2,
11604
+ name: "Dr. Chen"
11605
+ },
11606
+ {
11607
+ id: 3,
11608
+ name: "Dr. Duarte"
11609
+ },
11610
+ {
11611
+ id: 4,
11612
+ name: "Dr. Eze"
11613
+ },
11614
+ {
11615
+ id: 5,
11616
+ name: "Dr. Farrow"
11617
+ }
11618
+ ],
11619
+ tasks: [
11620
+ {
11621
+ id: 0,
11622
+ name: "OR-1 Cardiac"
11623
+ },
11624
+ {
11625
+ id: 1,
11626
+ name: "OR-2 Ortho"
11627
+ },
11628
+ {
11629
+ id: 2,
11630
+ name: "OR-3 Neuro"
11631
+ },
11632
+ {
11633
+ id: 3,
11634
+ name: "OR-4 General"
11635
+ },
11636
+ {
11637
+ id: 4,
11638
+ name: "OR-5 Pediatric"
11639
+ },
11640
+ {
11641
+ id: 5,
11642
+ name: "OR-6 Trauma"
11643
+ }
11644
+ ],
11645
+ cost: [
11646
+ [
11647
+ 1,
11648
+ 8,
11649
+ 9,
11650
+ 4,
11651
+ 7,
11652
+ 5
11653
+ ],
11654
+ [
11655
+ 9,
11656
+ 1,
11657
+ 8,
11658
+ 5,
11659
+ 6,
11660
+ 3
11661
+ ],
11662
+ [
11663
+ 8,
11664
+ 9,
11665
+ 1,
11666
+ 6,
11667
+ 5,
11668
+ 7
11669
+ ],
11670
+ [
11671
+ 5,
11672
+ 4,
11673
+ 7,
11674
+ 1,
11675
+ 3,
11676
+ 2
11677
+ ],
11678
+ [
11679
+ 7,
11680
+ 6,
11681
+ 5,
11682
+ 3,
11683
+ 1,
11684
+ 4
11685
+ ],
11686
+ [
11687
+ 4,
11688
+ 3,
11689
+ 6,
11690
+ 2,
11691
+ 4,
11692
+ 1
11693
+ ]
11694
+ ]
11695
+ },
11696
+ {
11697
+ family: "assignment",
11698
+ name: "Support Ticket Triage",
11699
+ description: "Seven engineers, nine open escalations — two will wait. Cost = estimated hours for each engineer on each ticket given their domain depth.",
11700
+ agents: [
11701
+ {
11702
+ id: 0,
11703
+ name: "Imani"
11704
+ },
11705
+ {
11706
+ id: 1,
11707
+ name: "Jonas"
11708
+ },
11709
+ {
11710
+ id: 2,
11711
+ name: "Kai"
11712
+ },
11713
+ {
11714
+ id: 3,
11715
+ name: "Lena"
11716
+ },
11717
+ {
11718
+ id: 4,
11719
+ name: "Marcus"
11720
+ },
11721
+ {
11722
+ id: 5,
11723
+ name: "Noor"
11724
+ },
11725
+ {
11726
+ id: 6,
11727
+ name: "Owen"
11728
+ }
11729
+ ],
11730
+ tasks: [
11731
+ {
11732
+ id: 0,
11733
+ name: "Auth outage"
11734
+ },
11735
+ {
11736
+ id: 1,
11737
+ name: "Billing drift"
11738
+ },
11739
+ {
11740
+ id: 2,
11741
+ name: "Mobile crash"
11742
+ },
11743
+ {
11744
+ id: 3,
11745
+ name: "Search latency"
11746
+ },
11747
+ {
11748
+ id: 4,
11749
+ name: "Data export"
11750
+ },
11751
+ {
11752
+ id: 5,
11753
+ name: "Webhook storm"
11754
+ },
11755
+ {
11756
+ id: 6,
11757
+ name: "SSO config"
11758
+ },
11759
+ {
11760
+ id: 7,
11761
+ name: "Rate limiter"
11762
+ },
11763
+ {
11764
+ id: 8,
11765
+ name: "PDF renderer"
11766
+ }
11767
+ ],
11768
+ cost: [
11769
+ [
11770
+ 2,
11771
+ 7,
11772
+ 9,
11773
+ 6,
11774
+ 5,
11775
+ 4,
11776
+ 3,
11777
+ 5,
11778
+ 8
11779
+ ],
11780
+ [
11781
+ 8,
11782
+ 2,
11783
+ 7,
11784
+ 5,
11785
+ 3,
11786
+ 6,
11787
+ 7,
11788
+ 6,
11789
+ 4
11790
+ ],
11791
+ [
11792
+ 9,
11793
+ 6,
11794
+ 2,
11795
+ 7,
11796
+ 6,
11797
+ 5,
11798
+ 8,
11799
+ 7,
11800
+ 3
11801
+ ],
11802
+ [
11803
+ 4,
11804
+ 5,
11805
+ 6,
11806
+ 2,
11807
+ 4,
11808
+ 3,
11809
+ 5,
11810
+ 2,
11811
+ 7
11812
+ ],
11813
+ [
11814
+ 6,
11815
+ 3,
11816
+ 8,
11817
+ 4,
11818
+ 2,
11819
+ 7,
11820
+ 6,
11821
+ 5,
11822
+ 5
11823
+ ],
11824
+ [
11825
+ 3,
11826
+ 6,
11827
+ 5,
11828
+ 3,
11829
+ 7,
11830
+ 2,
11831
+ 2,
11832
+ 4,
11833
+ 6
11834
+ ],
11835
+ [
11836
+ 7,
11837
+ 4,
11838
+ 4,
11839
+ 8,
11840
+ 5,
11841
+ 6,
11842
+ 4,
11843
+ 3,
11844
+ 2
11845
+ ]
11846
+ ]
11847
+ }
11848
+ ];
11849
+ const networkSampleProblems = [
11850
+ {
11851
+ family: "network",
11852
+ name: "Office Seating Feud",
11853
+ description: "Ten coworkers, weighted rivalry edges (1 = mild eye-rolls, 5 = open warfare). Split them across two floors so the maximum total rivalry crosses the staircase.",
11854
+ nodes: [
11855
+ {
11856
+ id: 0,
11857
+ name: "Ava",
11858
+ x: 20,
11859
+ y: 20
11860
+ },
11861
+ {
11862
+ id: 1,
11863
+ name: "Ben",
11864
+ x: 50,
11865
+ y: 12
11866
+ },
11867
+ {
11868
+ id: 2,
11869
+ name: "Cleo",
11870
+ x: 80,
11871
+ y: 22
11872
+ },
11873
+ {
11874
+ id: 3,
11875
+ name: "Dex",
11876
+ x: 12,
11877
+ y: 50
11878
+ },
11879
+ {
11880
+ id: 4,
11881
+ name: "Elle",
11882
+ x: 42,
11883
+ y: 42
11884
+ },
11885
+ {
11886
+ id: 5,
11887
+ name: "Finn",
11888
+ x: 70,
11889
+ y: 48
11890
+ },
11891
+ {
11892
+ id: 6,
11893
+ name: "Gus",
11894
+ x: 92,
11895
+ y: 55
11896
+ },
11897
+ {
11898
+ id: 7,
11899
+ name: "Hana",
11900
+ x: 25,
11901
+ y: 80
11902
+ },
11903
+ {
11904
+ id: 8,
11905
+ name: "Iggy",
11906
+ x: 55,
11907
+ y: 84
11908
+ },
11909
+ {
11910
+ id: 9,
11911
+ name: "June",
11912
+ x: 82,
11913
+ y: 80
11914
+ }
11915
+ ],
11916
+ edges: [
11917
+ {
11918
+ a: 0,
11919
+ b: 1,
11920
+ w: 3
11921
+ },
11922
+ {
11923
+ a: 0,
11924
+ b: 3,
11925
+ w: 1
11926
+ },
11927
+ {
11928
+ a: 0,
11929
+ b: 4,
11930
+ w: 4
11931
+ },
11932
+ {
11933
+ a: 1,
11934
+ b: 2,
11935
+ w: 2
11936
+ },
11937
+ {
11938
+ a: 1,
11939
+ b: 4,
11940
+ w: 5
11941
+ },
11942
+ {
11943
+ a: 2,
11944
+ b: 5,
11945
+ w: 3
11946
+ },
11947
+ {
11948
+ a: 2,
11949
+ b: 6,
11950
+ w: 1
11951
+ },
11952
+ {
11953
+ a: 3,
11954
+ b: 4,
11955
+ w: 2
11956
+ },
11957
+ {
11958
+ a: 3,
11959
+ b: 7,
11960
+ w: 4
11961
+ },
11962
+ {
11963
+ a: 4,
11964
+ b: 5,
11965
+ w: 1
11966
+ },
11967
+ {
11968
+ a: 4,
11969
+ b: 8,
11970
+ w: 3
11971
+ },
11972
+ {
11973
+ a: 5,
11974
+ b: 6,
11975
+ w: 4
11976
+ },
11977
+ {
11978
+ a: 5,
11979
+ b: 9,
11980
+ w: 2
11981
+ },
11982
+ {
11983
+ a: 6,
11984
+ b: 9,
11985
+ w: 5
11986
+ },
11987
+ {
11988
+ a: 7,
11989
+ b: 8,
11990
+ w: 2
11991
+ },
11992
+ {
11993
+ a: 8,
11994
+ b: 9,
11995
+ w: 1
11996
+ }
11997
+ ]
11998
+ },
11999
+ {
12000
+ family: "network",
12001
+ name: "Microservice Fault Domains",
12002
+ description: "Twelve services, edges weighted by cross-talk volume. Split across two availability zones so the heaviest traffic crosses zones — forcing the chattiest pairs to prove they tolerate partition.",
12003
+ nodes: [
12004
+ {
12005
+ id: 0,
12006
+ name: "gateway",
12007
+ x: 50,
12008
+ y: 8
12009
+ },
12010
+ {
12011
+ id: 1,
12012
+ name: "auth",
12013
+ x: 25,
12014
+ y: 22
12015
+ },
12016
+ {
12017
+ id: 2,
12018
+ name: "users",
12019
+ x: 75,
12020
+ y: 22
12021
+ },
12022
+ {
12023
+ id: 3,
12024
+ name: "billing",
12025
+ x: 12,
12026
+ y: 46
12027
+ },
12028
+ {
12029
+ id: 4,
12030
+ name: "catalog",
12031
+ x: 40,
12032
+ y: 40
12033
+ },
12034
+ {
12035
+ id: 5,
12036
+ name: "orders",
12037
+ x: 64,
12038
+ y: 42
12039
+ },
12040
+ {
12041
+ id: 6,
12042
+ name: "search",
12043
+ x: 90,
12044
+ y: 44
12045
+ },
12046
+ {
12047
+ id: 7,
12048
+ name: "ledger",
12049
+ x: 20,
12050
+ y: 70
12051
+ },
12052
+ {
12053
+ id: 8,
12054
+ name: "shipping",
12055
+ x: 48,
12056
+ y: 68
12057
+ },
12058
+ {
12059
+ id: 9,
12060
+ name: "notify",
12061
+ x: 74,
12062
+ y: 70
12063
+ },
12064
+ {
12065
+ id: 10,
12066
+ name: "analytics",
12067
+ x: 34,
12068
+ y: 90
12069
+ },
12070
+ {
12071
+ id: 11,
12072
+ name: "ml-recs",
12073
+ x: 66,
12074
+ y: 90
12075
+ }
12076
+ ],
12077
+ edges: [
12078
+ {
12079
+ a: 0,
12080
+ b: 1,
12081
+ w: 5
12082
+ },
12083
+ {
12084
+ a: 0,
12085
+ b: 2,
12086
+ w: 4
12087
+ },
12088
+ {
12089
+ a: 0,
12090
+ b: 4,
12091
+ w: 3
12092
+ },
12093
+ {
12094
+ a: 1,
12095
+ b: 2,
12096
+ w: 2
12097
+ },
12098
+ {
12099
+ a: 1,
12100
+ b: 3,
12101
+ w: 3
12102
+ },
12103
+ {
12104
+ a: 2,
12105
+ b: 5,
12106
+ w: 4
12107
+ },
12108
+ {
12109
+ a: 2,
12110
+ b: 6,
12111
+ w: 2
12112
+ },
12113
+ {
12114
+ a: 3,
12115
+ b: 7,
12116
+ w: 5
12117
+ },
12118
+ {
12119
+ a: 4,
12120
+ b: 5,
12121
+ w: 3
12122
+ },
12123
+ {
12124
+ a: 4,
12125
+ b: 6,
12126
+ w: 2
12127
+ },
12128
+ {
12129
+ a: 4,
12130
+ b: 11,
12131
+ w: 2
12132
+ },
12133
+ {
12134
+ a: 5,
12135
+ b: 8,
12136
+ w: 4
12137
+ },
12138
+ {
12139
+ a: 5,
12140
+ b: 9,
12141
+ w: 2
12142
+ },
12143
+ {
12144
+ a: 7,
12145
+ b: 10,
12146
+ w: 2
12147
+ },
12148
+ {
12149
+ a: 8,
12150
+ b: 9,
12151
+ w: 3
12152
+ },
12153
+ {
12154
+ a: 8,
12155
+ b: 10,
12156
+ w: 1
12157
+ },
12158
+ {
12159
+ a: 9,
12160
+ b: 11,
12161
+ w: 2
12162
+ },
12163
+ {
12164
+ a: 10,
12165
+ b: 11,
12166
+ w: 3
12167
+ }
12168
+ ]
12169
+ },
12170
+ {
12171
+ family: "network",
12172
+ name: "Power Grid Islanding",
12173
+ description: "During a storm event, the grid must split into two self-sufficient islands. Maximize the inter-island line capacity you can afford to sever cleanly.",
12174
+ nodes: [
12175
+ {
12176
+ id: 0,
12177
+ name: "Plant North",
12178
+ x: 30,
12179
+ y: 10
12180
+ },
12181
+ {
12182
+ id: 1,
12183
+ name: "Plant South",
12184
+ x: 65,
12185
+ y: 90
12186
+ },
12187
+ {
12188
+ id: 2,
12189
+ name: "Sub Alpha",
12190
+ x: 15,
12191
+ y: 35
12192
+ },
12193
+ {
12194
+ id: 3,
12195
+ name: "Sub Bravo",
12196
+ x: 50,
12197
+ y: 28
12198
+ },
12199
+ {
12200
+ id: 4,
12201
+ name: "Sub Charlie",
12202
+ x: 85,
12203
+ y: 32
12204
+ },
12205
+ {
12206
+ id: 5,
12207
+ name: "Sub Delta",
12208
+ x: 25,
12209
+ y: 62
12210
+ },
12211
+ {
12212
+ id: 6,
12213
+ name: "Sub Echo",
12214
+ x: 58,
12215
+ y: 58
12216
+ },
12217
+ {
12218
+ id: 7,
12219
+ name: "Sub Foxtrot",
12220
+ x: 88,
12221
+ y: 66
12222
+ },
12223
+ {
12224
+ id: 8,
12225
+ name: "City West",
12226
+ x: 12,
12227
+ y: 88
12228
+ }
12229
+ ],
12230
+ edges: [
12231
+ {
12232
+ a: 0,
12233
+ b: 2,
12234
+ w: 4
12235
+ },
12236
+ {
12237
+ a: 0,
12238
+ b: 3,
12239
+ w: 5
12240
+ },
12241
+ {
12242
+ a: 0,
12243
+ b: 4,
12244
+ w: 3
12245
+ },
12246
+ {
12247
+ a: 2,
12248
+ b: 3,
12249
+ w: 2
12250
+ },
12251
+ {
12252
+ a: 2,
12253
+ b: 5,
12254
+ w: 4
12255
+ },
12256
+ {
12257
+ a: 3,
12258
+ b: 4,
12259
+ w: 2
12260
+ },
12261
+ {
12262
+ a: 3,
12263
+ b: 6,
12264
+ w: 4
12265
+ },
12266
+ {
12267
+ a: 4,
12268
+ b: 7,
12269
+ w: 4
12270
+ },
12271
+ {
12272
+ a: 5,
12273
+ b: 6,
12274
+ w: 3
12275
+ },
12276
+ {
12277
+ a: 5,
12278
+ b: 8,
12279
+ w: 5
12280
+ },
12281
+ {
12282
+ a: 6,
12283
+ b: 7,
12284
+ w: 2
12285
+ },
12286
+ {
12287
+ a: 6,
12288
+ b: 1,
12289
+ w: 5
12290
+ },
12291
+ {
12292
+ a: 7,
12293
+ b: 1,
12294
+ w: 3
12295
+ },
12296
+ {
12297
+ a: 8,
12298
+ b: 1,
12299
+ w: 2
12300
+ }
12301
+ ]
12302
+ }
12303
+ ];
12304
+ /** Build a jittered grid mesh: cells at (col,row), edges between face-sharing neighbors. */
12305
+ function gridMesh(cols, rows, cellName, weightAt, jitter) {
12306
+ const nodes = [];
12307
+ const padX = 100 / (cols + 1);
12308
+ const padY = 100 / (rows + 1);
12309
+ for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
12310
+ const i = r * cols + c;
12311
+ const { dx, dy } = jitter(i);
12312
+ nodes.push({
12313
+ id: i,
12314
+ name: cellName(i),
12315
+ x: Math.round(padX * (c + 1) + dx),
12316
+ y: Math.round(padY * (r + 1) + dy)
12317
+ });
12318
+ }
12319
+ const edges = [];
12320
+ for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
12321
+ const i = r * cols + c;
12322
+ if (c + 1 < cols) edges.push({
12323
+ a: i,
12324
+ b: i + 1,
12325
+ w: weightAt(i, i + 1)
12326
+ });
12327
+ if (r + 1 < rows) edges.push({
12328
+ a: i,
12329
+ b: i + cols,
12330
+ w: weightAt(i, i + cols)
12331
+ });
12332
+ }
12333
+ return {
12334
+ nodes,
12335
+ edges
12336
+ };
12337
+ }
12338
+ const wobble = (i) => i * 7919 % 11 / 10 - .5;
12339
+ const plate = gridMesh(4, 3, (i) => `cell ${i}`, (a, b) => 1 + (a * 31 + b * 17) % 4, (i) => ({
12340
+ dx: wobble(i) * 6,
12341
+ dy: wobble(i + 3) * 6
12342
+ }));
12343
+ const reservoir = gridMesh(6, 3, (i) => `block ${i}`, (a, b) => 1 + (a * 13 + b * 29) % 5, (i) => ({
12344
+ dx: wobble(i + 1) * 8,
12345
+ dy: wobble(i + 5) * 8
12346
+ }));
12347
+ const blade = gridMesh(6, 4, (i) => `elem ${i}`, (a, b) => 1 + (a * 23 + b * 19) % 4, (i) => ({
12348
+ dx: wobble(i + 2) * 7,
12349
+ dy: wobble(i + 7) * 7
12350
+ }));
12351
+ const partitioningSampleProblems = [
12352
+ {
12353
+ family: "partitioning",
12354
+ name: "Cantilever Plate Mesh",
12355
+ description: "A 4×3 finite-element plate mesh split across two solver ranks. Edge weights are the face data exchanged every timestep — each cut edge is halo traffic both ranks pay forever. Classic balanced bisection, and small enough to race on the QAOA simulator in seconds.",
12356
+ nodes: plate.nodes,
12357
+ edges: plate.edges,
12358
+ k: 2,
12359
+ balanceTolerance: 1
12360
+ },
12361
+ {
12362
+ family: "partitioning",
12363
+ name: "Reservoir Grid Sectors",
12364
+ description: "An 18-block reservoir simulation grid decomposed across three compute nodes. The big version of this problem has billions of cells; the math of where to draw the seams is identical at every scale.",
12365
+ nodes: reservoir.nodes,
12366
+ edges: reservoir.edges,
12367
+ k: 3,
12368
+ balanceTolerance: 1
12369
+ },
12370
+ {
12371
+ family: "partitioning",
12372
+ name: "Turbine Blade Mesh",
12373
+ description: "A 24-element blade section spread over four solver ranks for a transient thermal run. Four-way splits punish lazy seams — every extra cut edge multiplies across thousands of timesteps.",
12374
+ nodes: blade.nodes,
12375
+ edges: blade.edges,
12376
+ k: 4,
12377
+ balanceTolerance: 1
12378
+ }
12379
+ ];
12380
+ const selectionSampleProblems = [
12381
+ {
12382
+ family: "selection",
12383
+ name: "Indie Game Fund",
12384
+ description: "Twelve pitches, a $2M fund, and every project carries risk. Maximize expected portfolio value without betting the firm on moonshots.",
12385
+ budget: 200,
12386
+ riskWeight: 4,
12387
+ items: [
12388
+ {
12389
+ id: 0,
12390
+ name: "Cozy Farm Sim",
12391
+ value: 60,
12392
+ cost: 40,
12393
+ risk: 2
12394
+ },
12395
+ {
12396
+ id: 1,
12397
+ name: "Roguelike Deckbuilder",
12398
+ value: 85,
12399
+ cost: 55,
12400
+ risk: 4
12401
+ },
12402
+ {
12403
+ id: 2,
12404
+ name: "VR Escape Room",
12405
+ value: 95,
12406
+ cost: 70,
12407
+ risk: 8
12408
+ },
12409
+ {
12410
+ id: 3,
12411
+ name: "Pixel Metroidvania",
12412
+ value: 55,
12413
+ cost: 35,
12414
+ risk: 3
12415
+ },
12416
+ {
12417
+ id: 4,
12418
+ name: "MMO Sandbox",
12419
+ value: 160,
12420
+ cost: 120,
12421
+ risk: 10
12422
+ },
12423
+ {
12424
+ id: 5,
12425
+ name: "Mobile Puzzler",
12426
+ value: 35,
12427
+ cost: 18,
12428
+ risk: 1
12429
+ },
12430
+ {
12431
+ id: 6,
12432
+ name: "Co-op Heist Game",
12433
+ value: 75,
12434
+ cost: 50,
12435
+ risk: 5
12436
+ },
12437
+ {
12438
+ id: 7,
12439
+ name: "Narrative Adventure",
12440
+ value: 45,
12441
+ cost: 30,
12442
+ risk: 2
12443
+ },
12444
+ {
12445
+ id: 8,
12446
+ name: "Auto-Battler",
12447
+ value: 50,
12448
+ cost: 28,
12449
+ risk: 4
12450
+ },
12451
+ {
12452
+ id: 9,
12453
+ name: "Speedrun Platformer",
12454
+ value: 30,
12455
+ cost: 15,
12456
+ risk: 2
12457
+ },
12458
+ {
12459
+ id: 10,
12460
+ name: "City Builder",
12461
+ value: 90,
12462
+ cost: 65,
12463
+ risk: 5
12464
+ },
12465
+ {
12466
+ id: 11,
12467
+ name: "Horror Anthology",
12468
+ value: 40,
12469
+ cost: 25,
12470
+ risk: 6
12471
+ }
12472
+ ]
12473
+ },
12474
+ {
12475
+ family: "selection",
12476
+ name: "Drone Sensor Loadout",
12477
+ description: "A recon drone has 48 units of payload mass. Pick the sensor suite that maximizes mission value — leave the gold-plated gear on the bench.",
12478
+ budget: 48,
12479
+ riskWeight: 0,
12480
+ items: [
12481
+ {
12482
+ id: 0,
12483
+ name: "EO Camera",
12484
+ value: 30,
12485
+ cost: 12
12486
+ },
12487
+ {
12488
+ id: 1,
12489
+ name: "IR Thermal",
12490
+ value: 28,
12491
+ cost: 10
12492
+ },
12493
+ {
12494
+ id: 2,
12495
+ name: "LIDAR",
12496
+ value: 35,
12497
+ cost: 18
12498
+ },
12499
+ {
12500
+ id: 3,
12501
+ name: "SIGINT Pod",
12502
+ value: 25,
12503
+ cost: 14
12504
+ },
12505
+ {
12506
+ id: 4,
12507
+ name: "Synthetic Aperture Radar",
12508
+ value: 40,
12509
+ cost: 22
12510
+ },
12511
+ {
12512
+ id: 5,
12513
+ name: "Laser Designator",
12514
+ value: 18,
12515
+ cost: 8
12516
+ },
12517
+ {
12518
+ id: 6,
12519
+ name: "Comms Relay",
12520
+ value: 15,
12521
+ cost: 6
12522
+ },
12523
+ {
12524
+ id: 7,
12525
+ name: "Magnetometer",
12526
+ value: 10,
12527
+ cost: 4
12528
+ },
12529
+ {
12530
+ id: 8,
12531
+ name: "Air Sampler",
12532
+ value: 8,
12533
+ cost: 5
12534
+ },
12535
+ {
12536
+ id: 9,
12537
+ name: "Spare Battery",
12538
+ value: 12,
12539
+ cost: 9
12540
+ }
12541
+ ]
12542
+ },
12543
+ {
12544
+ family: "selection",
12545
+ name: "Feature Flag Quarter",
12546
+ description: "Fourteen engineering initiatives, 90 days of team capacity. Each carries delivery risk. Choose the roadmap that maximizes shipped value.",
12547
+ budget: 90,
12548
+ riskWeight: 3,
12549
+ items: [
12550
+ {
12551
+ id: 0,
12552
+ name: "Realtime Collab",
12553
+ value: 55,
12554
+ cost: 30,
12555
+ risk: 6
12556
+ },
12557
+ {
12558
+ id: 1,
12559
+ name: "Mobile Offline Mode",
12560
+ value: 40,
12561
+ cost: 25,
12562
+ risk: 4
12563
+ },
12564
+ {
12565
+ id: 2,
12566
+ name: "SSO Everywhere",
12567
+ value: 35,
12568
+ cost: 15,
12569
+ risk: 2
12570
+ },
12571
+ {
12572
+ id: 3,
12573
+ name: "Billing v2",
12574
+ value: 50,
12575
+ cost: 35,
12576
+ risk: 7
12577
+ },
12578
+ {
12579
+ id: 4,
12580
+ name: "AI Summaries",
12581
+ value: 45,
12582
+ cost: 20,
12583
+ risk: 3
12584
+ },
12585
+ {
12586
+ id: 5,
12587
+ name: "Audit Logs",
12588
+ value: 25,
12589
+ cost: 10,
12590
+ risk: 1
12591
+ },
12592
+ {
12593
+ id: 6,
12594
+ name: "Perf: Cold Start",
12595
+ value: 30,
12596
+ cost: 12,
12597
+ risk: 2
12598
+ },
12599
+ {
12600
+ id: 7,
12601
+ name: "Theme System",
12602
+ value: 15,
12603
+ cost: 8,
12604
+ risk: 1
12605
+ },
12606
+ {
12607
+ id: 8,
12608
+ name: "Webhooks v2",
12609
+ value: 28,
12610
+ cost: 14,
12611
+ risk: 3
12612
+ },
12613
+ {
12614
+ id: 9,
12615
+ name: "Data Residency",
12616
+ value: 38,
12617
+ cost: 28,
12618
+ risk: 5
12619
+ },
12620
+ {
12621
+ id: 10,
12622
+ name: "Search Rewrite",
12623
+ value: 42,
12624
+ cost: 26,
12625
+ risk: 5
12626
+ },
12627
+ {
12628
+ id: 11,
12629
+ name: "Onboarding Flow",
12630
+ value: 22,
12631
+ cost: 9,
12632
+ risk: 1
12633
+ },
12634
+ {
12635
+ id: 12,
12636
+ name: "Plugin SDK",
12637
+ value: 48,
12638
+ cost: 32,
12639
+ risk: 6
12640
+ },
12641
+ {
12642
+ id: 13,
12643
+ name: "A11y Sweep",
12644
+ value: 20,
12645
+ cost: 11,
12646
+ risk: 1
12647
+ }
12648
+ ]
12649
+ }
12650
+ ];
12651
+ const economicSampleProblems = [
12652
+ {
12653
+ family: "economic",
12654
+ name: "Spectrum Auction",
12655
+ description: "Five regional spectrum licenses, nine carriers bidding on bundles. Sell each license once, maximize treasury revenue — the classic FCC winner-determination problem.",
12656
+ goods: [
12657
+ {
12658
+ id: 0,
12659
+ name: "North Band"
12660
+ },
12661
+ {
12662
+ id: 1,
12663
+ name: "South Band"
12664
+ },
12665
+ {
12666
+ id: 2,
12667
+ name: "East Band"
12668
+ },
12669
+ {
12670
+ id: 3,
12671
+ name: "West Band"
12672
+ },
12673
+ {
12674
+ id: 4,
12675
+ name: "Metro Core"
12676
+ }
12677
+ ],
12678
+ bids: [
12679
+ {
12680
+ id: 0,
12681
+ bidder: "AstraTel",
12682
+ price: 120,
12683
+ goodIds: [0, 4]
12684
+ },
12685
+ {
12686
+ id: 1,
12687
+ bidder: "AstraTel",
12688
+ price: 60,
12689
+ goodIds: [0]
12690
+ },
12691
+ {
12692
+ id: 2,
12693
+ bidder: "Borealis",
12694
+ price: 140,
12695
+ goodIds: [1, 2]
12696
+ },
12697
+ {
12698
+ id: 3,
12699
+ bidder: "Borealis",
12700
+ price: 65,
12701
+ goodIds: [1]
12702
+ },
12703
+ {
12704
+ id: 4,
12705
+ bidder: "CometNet",
12706
+ price: 95,
12707
+ goodIds: [2, 3]
12708
+ },
12709
+ {
12710
+ id: 5,
12711
+ bidder: "CometNet",
12712
+ price: 50,
12713
+ goodIds: [3]
12714
+ },
12715
+ {
12716
+ id: 6,
12717
+ bidder: "DyneMobile",
12718
+ price: 180,
12719
+ goodIds: [
12720
+ 0,
12721
+ 1,
12722
+ 4
12723
+ ]
12724
+ },
12725
+ {
12726
+ id: 7,
12727
+ bidder: "Equinox",
12728
+ price: 75,
12729
+ goodIds: [4]
12730
+ },
12731
+ {
12732
+ id: 8,
12733
+ bidder: "Equinox",
12734
+ price: 110,
12735
+ goodIds: [2, 4]
12736
+ }
12737
+ ]
12738
+ },
12739
+ {
12740
+ family: "economic",
12741
+ name: "Cloud Spot Market",
12742
+ description: "Six GPU node pools, ten batch workloads bidding for exclusive overnight windows. Maximize the operator&apos;s take across all-or-nothing reservations.",
12743
+ goods: [
12744
+ {
12745
+ id: 0,
12746
+ name: "A100 Pool 1"
12747
+ },
12748
+ {
12749
+ id: 1,
12750
+ name: "A100 Pool 2"
12751
+ },
12752
+ {
12753
+ id: 2,
12754
+ name: "H100 Pool 1"
12755
+ },
12756
+ {
12757
+ id: 3,
12758
+ name: "H100 Pool 2"
12759
+ },
12760
+ {
12761
+ id: 4,
12762
+ name: "TPU Pod"
12763
+ },
12764
+ {
12765
+ id: 5,
12766
+ name: "CPU Farm"
12767
+ }
12768
+ ],
12769
+ bids: [
12770
+ {
12771
+ id: 0,
12772
+ bidder: "BioFold Labs",
12773
+ price: 90,
12774
+ goodIds: [2, 3]
12775
+ },
12776
+ {
12777
+ id: 1,
12778
+ bidder: "BioFold Labs",
12779
+ price: 48,
12780
+ goodIds: [2]
12781
+ },
12782
+ {
12783
+ id: 2,
12784
+ bidder: "RenderWorks",
12785
+ price: 55,
12786
+ goodIds: [0, 1]
12787
+ },
12788
+ {
12789
+ id: 3,
12790
+ bidder: "RenderWorks",
12791
+ price: 30,
12792
+ goodIds: [0]
12793
+ },
12794
+ {
12795
+ id: 4,
12796
+ bidder: "LLM Foundry",
12797
+ price: 130,
12798
+ goodIds: [
12799
+ 2,
12800
+ 3,
12801
+ 4
12802
+ ]
12803
+ },
12804
+ {
12805
+ id: 5,
12806
+ bidder: "LLM Foundry",
12807
+ price: 70,
12808
+ goodIds: [4]
12809
+ },
12810
+ {
12811
+ id: 6,
12812
+ bidder: "QuantSim",
12813
+ price: 42,
12814
+ goodIds: [1, 5]
12815
+ },
12816
+ {
12817
+ id: 7,
12818
+ bidder: "QuantSim",
12819
+ price: 22,
12820
+ goodIds: [5]
12821
+ },
12822
+ {
12823
+ id: 8,
12824
+ bidder: "WeatherNet",
12825
+ price: 65,
12826
+ goodIds: [0, 5]
12827
+ },
12828
+ {
12829
+ id: 9,
12830
+ bidder: "GenomeScan",
12831
+ price: 38,
12832
+ goodIds: [1]
12833
+ }
12834
+ ]
12835
+ },
12836
+ {
12837
+ family: "economic",
12838
+ name: "Stadium Sponsorship Bundle",
12839
+ description: "Seven sponsorship assets — naming rights, jersey patch, tunnel club — and eight brands bidding on combinations. One sale per asset; maximize the franchise&apos;s deal sheet.",
12840
+ goods: [
12841
+ {
12842
+ id: 0,
12843
+ name: "Naming Rights"
12844
+ },
12845
+ {
12846
+ id: 1,
12847
+ name: "Jersey Patch"
12848
+ },
12849
+ {
12850
+ id: 2,
12851
+ name: "Tunnel Club"
12852
+ },
12853
+ {
12854
+ id: 3,
12855
+ name: "Scoreboard"
12856
+ },
12857
+ {
12858
+ id: 4,
12859
+ name: "Practice Facility"
12860
+ },
12861
+ {
12862
+ id: 5,
12863
+ name: "Beer Pouring Rights"
12864
+ },
12865
+ {
12866
+ id: 6,
12867
+ name: "Halftime Show"
12868
+ }
12869
+ ],
12870
+ bids: [
12871
+ {
12872
+ id: 0,
12873
+ bidder: "Vertex Air",
12874
+ price: 200,
12875
+ goodIds: [0, 3]
12876
+ },
12877
+ {
12878
+ id: 1,
12879
+ bidder: "Vertex Air",
12880
+ price: 130,
12881
+ goodIds: [0]
12882
+ },
12883
+ {
12884
+ id: 2,
12885
+ bidder: "Cascade Beer",
12886
+ price: 85,
12887
+ goodIds: [5, 6]
12888
+ },
12889
+ {
12890
+ id: 3,
12891
+ bidder: "Cascade Beer",
12892
+ price: 55,
12893
+ goodIds: [5]
12894
+ },
12895
+ {
12896
+ id: 4,
12897
+ bidder: "NovaBank",
12898
+ price: 150,
12899
+ goodIds: [1, 2]
12900
+ },
12901
+ {
12902
+ id: 5,
12903
+ bidder: "NovaBank",
12904
+ price: 80,
12905
+ goodIds: [1]
12906
+ },
12907
+ {
12908
+ id: 6,
12909
+ bidder: "GrindHouse Coffee",
12910
+ price: 45,
12911
+ goodIds: [2]
12912
+ },
12913
+ {
12914
+ id: 7,
12915
+ bidder: "Pixel Motors",
12916
+ price: 110,
12917
+ goodIds: [3, 4]
12918
+ },
12919
+ {
12920
+ id: 8,
12921
+ bidder: "Pixel Motors",
12922
+ price: 60,
12923
+ goodIds: [4]
12924
+ },
12925
+ {
12926
+ id: 9,
12927
+ bidder: "StreamCo",
12928
+ price: 70,
12929
+ goodIds: [6]
12930
+ }
12931
+ ]
10918
12932
  }
10919
- const edges = [];
10920
- for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
10921
- const i = r * cols + c;
10922
- if (c + 1 < cols) edges.push({
10923
- a: i,
10924
- b: i + 1,
10925
- w: weightAt(i, i + 1)
10926
- });
10927
- if (r + 1 < rows) edges.push({
10928
- a: i,
10929
- b: i + cols,
10930
- w: weightAt(i, i + cols)
10931
- });
12933
+ ];
12934
+ const continuousSampleProblems = [
12935
+ {
12936
+ family: "continuous",
12937
+ name: "Reactor Temperature Tuning",
12938
+ description: "One control dial, a yield curve full of local sweet spots, and exactly one true optimum. The wavy bowl eats naive hill climbers for breakfast.",
12939
+ functionId: "wavy-bowl",
12940
+ dims: 1,
12941
+ lo: -10,
12942
+ hi: 10,
12943
+ paramNames: ["temperature offset"]
12944
+ },
12945
+ {
12946
+ family: "continuous",
12947
+ name: "Antenna Sweet Spot",
12948
+ description: "Two placement coordinates on the roof; signal interference makes four equally good mounting points. Himmelblau&apos;s function — which optimum will your solver find?",
12949
+ functionId: "himmelblau",
12950
+ dims: 2,
12951
+ lo: -6,
12952
+ hi: 6,
12953
+ paramNames: ["east-west", "north-south"]
12954
+ },
12955
+ {
12956
+ family: "continuous",
12957
+ name: "Hyperparameter Canyon",
12958
+ description: "Two training knobs over a Rastrigin egg-carton — dozens of local minima arranged in a lattice, one global floor at the origin. Pure trap density.",
12959
+ functionId: "rastrigin",
12960
+ dims: 2,
12961
+ lo: -5.12,
12962
+ hi: 5.12,
12963
+ paramNames: ["learning rate (scaled)", "regularization (scaled)"]
10932
12964
  }
10933
- return {
10934
- nodes,
10935
- edges
10936
- };
10937
- }
10938
- const wobble = (i) => i * 7919 % 11 / 10 - .5;
10939
- const plate = gridMesh(4, 3, (i) => `cell ${i}`, (a, b) => 1 + (a * 31 + b * 17) % 4, (i) => ({
10940
- dx: wobble(i) * 6,
10941
- dy: wobble(i + 3) * 6
10942
- }));
10943
- const reservoir = gridMesh(6, 3, (i) => `block ${i}`, (a, b) => 1 + (a * 13 + b * 29) % 5, (i) => ({
10944
- dx: wobble(i + 1) * 8,
10945
- dy: wobble(i + 5) * 8
10946
- }));
10947
- const blade = gridMesh(6, 4, (i) => `elem ${i}`, (a, b) => 1 + (a * 23 + b * 19) % 4, (i) => ({
10948
- dx: wobble(i + 2) * 7,
10949
- dy: wobble(i + 7) * 7
10950
- }));
10951
- plate.nodes, plate.edges, reservoir.nodes, reservoir.edges, blade.nodes, blade.edges;
12965
+ ];
10952
12966
  var QuboBuilder = class {
10953
12967
  size;
10954
12968
  q = /* @__PURE__ */ new Map();
@@ -11280,8 +13294,270 @@ function continuousToy() {
11280
13294
  };
11281
13295
  }
11282
13296
  schedulingToy(), routingToy(), packingToy(), assignmentToy(), networkToy(), partitioningToy(), selectionToy(), economicToy(), continuousToy();
13297
+ /**
13298
+ * Per-family problem Zod schemas — the validation foundation for custom problem
13299
+ * EDITING (FamilyProblemEditor validates JSON against these) and LLM FORMULATION
13300
+ * (quantum_formulate validates the model's output before surfacing it).
13301
+ *
13302
+ * Authored to match the hand-written interfaces in `./types`; a type-level check
13303
+ * at the bottom keeps the two in sync. The family discriminant lets a single
13304
+ * editor/tool dispatch across all eight shapes.
13305
+ */
13306
+ const named = {
13307
+ id: z.number().int(),
13308
+ name: z.string().min(1)
13309
+ };
13310
+ const xy = {
13311
+ x: z.number(),
13312
+ y: z.number()
13313
+ };
13314
+ const edge = z.object({
13315
+ a: z.number().int().nonnegative(),
13316
+ b: z.number().int().nonnegative(),
13317
+ w: z.number().optional()
13318
+ });
13319
+ const RoutingProblemSchema = z.object({
13320
+ family: z.literal("routing"),
13321
+ name: z.string().min(1),
13322
+ description: z.string().optional(),
13323
+ nodes: z.array(z.object({
13324
+ ...named,
13325
+ ...xy
13326
+ })).min(2)
13327
+ });
13328
+ const PackingProblemSchema = z.object({
13329
+ family: z.literal("packing"),
13330
+ name: z.string().min(1),
13331
+ description: z.string().optional(),
13332
+ items: z.array(z.object({
13333
+ ...named,
13334
+ size: z.number().positive()
13335
+ })).min(1),
13336
+ binCapacity: z.number().positive(),
13337
+ binLabel: z.string().min(1)
13338
+ });
13339
+ const AssignmentProblemSchema = z.object({
13340
+ family: z.literal("assignment"),
13341
+ name: z.string().min(1),
13342
+ description: z.string().optional(),
13343
+ agents: z.array(z.object(named)).min(1),
13344
+ tasks: z.array(z.object(named)).min(1),
13345
+ cost: z.array(z.array(z.number())).min(1)
13346
+ });
13347
+ const NetworkProblemSchema = z.object({
13348
+ family: z.literal("network"),
13349
+ name: z.string().min(1),
13350
+ description: z.string().optional(),
13351
+ nodes: z.array(z.object({
13352
+ ...named,
13353
+ ...xy
13354
+ })).min(1),
13355
+ edges: z.array(edge)
13356
+ });
13357
+ const PartitionProblemSchema = z.object({
13358
+ family: z.literal("partitioning"),
13359
+ name: z.string().min(1),
13360
+ description: z.string().optional(),
13361
+ nodes: z.array(z.object({
13362
+ ...named,
13363
+ ...xy
13364
+ })).min(1),
13365
+ edges: z.array(edge),
13366
+ k: z.number().int().min(2),
13367
+ balanceTolerance: z.number().int().nonnegative().optional()
13368
+ });
13369
+ const SelectionProblemSchema = z.object({
13370
+ family: z.literal("selection"),
13371
+ name: z.string().min(1),
13372
+ description: z.string().optional(),
13373
+ items: z.array(z.object({
13374
+ ...named,
13375
+ value: z.number(),
13376
+ cost: z.number().positive(),
13377
+ risk: z.number().optional()
13378
+ })).min(1),
13379
+ budget: z.number().positive(),
13380
+ riskWeight: z.number().optional()
13381
+ });
13382
+ const EconomicProblemSchema = z.object({
13383
+ family: z.literal("economic"),
13384
+ name: z.string().min(1),
13385
+ description: z.string().optional(),
13386
+ goods: z.array(z.object(named)).min(1),
13387
+ bids: z.array(z.object({
13388
+ id: z.number().int(),
13389
+ bidder: z.string().min(1),
13390
+ price: z.number(),
13391
+ goodIds: z.array(z.number().int().nonnegative())
13392
+ })).min(1)
13393
+ });
13394
+ const ContinuousProblemSchema = z.object({
13395
+ family: z.literal("continuous"),
13396
+ name: z.string().min(1),
13397
+ description: z.string().optional(),
13398
+ functionId: z.enum([
13399
+ "rastrigin",
13400
+ "ackley",
13401
+ "himmelblau",
13402
+ "wavy-bowl"
13403
+ ]),
13404
+ dims: z.union([z.literal(1), z.literal(2)]),
13405
+ lo: z.number(),
13406
+ hi: z.number(),
13407
+ paramNames: z.array(z.string())
13408
+ });
13409
+ z.discriminatedUnion("family", [
13410
+ RoutingProblemSchema,
13411
+ PackingProblemSchema,
13412
+ AssignmentProblemSchema,
13413
+ NetworkProblemSchema,
13414
+ PartitionProblemSchema,
13415
+ SelectionProblemSchema,
13416
+ EconomicProblemSchema,
13417
+ ContinuousProblemSchema
13418
+ ]);
13419
+ const refineAssignment = (p, ctx) => {
13420
+ if (p.cost.length !== p.agents.length) ctx.addIssue({
13421
+ code: z.ZodIssueCode.custom,
13422
+ path: ["cost"],
13423
+ message: `cost must have one row per agent (${p.agents.length}); got ${p.cost.length}`
13424
+ });
13425
+ p.cost.forEach((row, i) => {
13426
+ if (row.length !== p.tasks.length) ctx.addIssue({
13427
+ code: z.ZodIssueCode.custom,
13428
+ path: ["cost", i],
13429
+ message: `cost row ${i} must have one column per task (${p.tasks.length}); got ${row.length}`
13430
+ });
13431
+ });
13432
+ };
13433
+ const refineEdgeBounds = (p, ctx) => {
13434
+ const n = p.nodes.length;
13435
+ p.edges.forEach((e, i) => {
13436
+ if (e.a >= n || e.b >= n) ctx.addIssue({
13437
+ code: z.ZodIssueCode.custom,
13438
+ path: ["edges", i],
13439
+ message: `edge endpoints must reference nodes 0..${n - 1}; got a=${e.a}, b=${e.b}`
13440
+ });
13441
+ });
13442
+ };
13443
+ const refineEconomic = (p, ctx) => {
13444
+ const n = p.goods.length;
13445
+ p.bids.forEach((bid, i) => {
13446
+ const seen = /* @__PURE__ */ new Set();
13447
+ bid.goodIds.forEach((g, j) => {
13448
+ if (g >= n) ctx.addIssue({
13449
+ code: z.ZodIssueCode.custom,
13450
+ path: [
13451
+ "bids",
13452
+ i,
13453
+ "goodIds",
13454
+ j
13455
+ ],
13456
+ message: `goodId must reference goods 0..${n - 1}; got ${g}`
13457
+ });
13458
+ if (seen.has(g)) ctx.addIssue({
13459
+ code: z.ZodIssueCode.custom,
13460
+ path: [
13461
+ "bids",
13462
+ i,
13463
+ "goodIds",
13464
+ j
13465
+ ],
13466
+ message: `duplicate goodId ${g} in bid ${i}`
13467
+ });
13468
+ seen.add(g);
13469
+ });
13470
+ });
13471
+ };
13472
+ /** Per-family schema lookup — the editor/tool pick the right one by familyId. */
13473
+ const FAMILY_PROBLEM_SCHEMAS = {
13474
+ routing: RoutingProblemSchema,
13475
+ packing: PackingProblemSchema,
13476
+ assignment: AssignmentProblemSchema.superRefine(refineAssignment),
13477
+ network: NetworkProblemSchema.superRefine(refineEdgeBounds),
13478
+ partitioning: PartitionProblemSchema.superRefine(refineEdgeBounds),
13479
+ selection: SelectionProblemSchema,
13480
+ economic: EconomicProblemSchema.superRefine(refineEconomic),
13481
+ continuous: ContinuousProblemSchema
13482
+ };
13483
+ const FAMILY_SAMPLE_PROBLEMS = {
13484
+ routing: routingSampleProblems,
13485
+ packing: packingSampleProblems,
13486
+ assignment: assignmentSampleProblems,
13487
+ network: networkSampleProblems,
13488
+ partitioning: partitioningSampleProblems,
13489
+ selection: selectionSampleProblems,
13490
+ economic: economicSampleProblems,
13491
+ continuous: continuousSampleProblems
13492
+ };
11283
13493
  //#endregion
11284
- //#region ../../b4m-core/services/dist/tools-B2RLL9dT.mjs
13494
+ //#region ../../b4m-core/services/dist/tools-BhiSgg5-.mjs
13495
+ const createFabFileSchema = z.object({
13496
+ fileName: z.string(),
13497
+ mimeType: z.string(),
13498
+ fileSize: z.number(),
13499
+ type: z.enum(KnowledgeType),
13500
+ content: z.union([z.string(), z.instanceof(Buffer)]).optional(),
13501
+ organizationId: z.string().optional(),
13502
+ /**
13503
+ * Content type of the file
13504
+ * @example 'text/markdown'
13505
+ * @example 'application/pdf'
13506
+ * @example 'application/octet-stream' for binary files
13507
+ */
13508
+ contentType: z.string().optional(),
13509
+ public: z.boolean().optional(),
13510
+ prefix: z.string().optional(),
13511
+ system: z.boolean().optional(),
13512
+ tags: z.array(z.object({
13513
+ name: z.string(),
13514
+ strength: z.number()
13515
+ })).optional(),
13516
+ systemPriority: z.number().optional(),
13517
+ sessionId: z.string().optional(),
13518
+ contentHash: z.string().optional(),
13519
+ batchId: z.string().optional(),
13520
+ relativePath: z.string().optional()
13521
+ });
13522
+ const DEFAULT_MAX_FILE_SIZE = 20;
13523
+ const DEFAULT_EXPIRE_IN_SECONDS = 3600 * 24 * 5;
13524
+ const createFabFile = async (userId, parameters, { db, storage }) => {
13525
+ const params = secureParameters(parameters, createFabFileSchema);
13526
+ const user = await db.users.findById(userId);
13527
+ if (!user) throw new BadRequestError("User not found");
13528
+ let ext = "";
13529
+ let mimeType = parameters.mimeType;
13530
+ ext = getFileExtension(params.fileName);
13531
+ mimeType = params.mimeType || getMimeTypeByExtension(ext);
13532
+ if (!mimeType) mimeType = SupportedFabFileMimeTypes.TXT_PLAIN;
13533
+ if (!Object.values(SupportedFabFileMimeTypes).some((type) => type === mimeType)) throw new BadRequestError(`File type ${mimeType} is not supported`);
13534
+ let filePath = params.prefix ? `${params.prefix}/` : "";
13535
+ filePath += `${v4()}${ext ? `.${ext}` : ".txt"}`;
13536
+ const maxFileSize = getSettingsValue("MaxFileSize", await getSettingsMap(db), DEFAULT_MAX_FILE_SIZE) * 1024 * 1024;
13537
+ if (params.fileSize >= maxFileSize) throw new BadRequestError("File size exceeds maximum file size");
13538
+ await checkStorageLimitForFile(user, params.fileSize, params.organizationId, db.organizations?.findById);
13539
+ const buildData = {
13540
+ userId,
13541
+ ...params,
13542
+ mimeType,
13543
+ filePath,
13544
+ users: [],
13545
+ groups: [],
13546
+ isGlobalRead: false,
13547
+ isGlobalWrite: false,
13548
+ createdAt: /* @__PURE__ */ new Date(),
13549
+ updatedAt: /* @__PURE__ */ new Date()
13550
+ };
13551
+ if (params.content) {
13552
+ await storage.upload(filePath, params.content, {
13553
+ ContentType: params.contentType,
13554
+ ContentLength: params.fileSize
13555
+ });
13556
+ buildData.fileUrl = await storage.generateSignedUrl(filePath, DEFAULT_EXPIRE_IN_SECONDS, "get");
13557
+ buildData.fileUrlExpireAt = new Date(Date.now() + DEFAULT_EXPIRE_IN_SECONDS * 1e3);
13558
+ } else buildData.presignedUrl = await storage.generateSignedUrl(filePath, 600, "put");
13559
+ return await db.fabFiles.create(buildData);
13560
+ };
11285
13561
  async function performDeepResearch(context, params, config = {}) {
11286
13562
  const maxDepth = config.maxDepth || 7;
11287
13563
  const duration = config.duration || 4.5;
@@ -11642,9 +13918,10 @@ const deepResearchTool = {
11642
13918
  async function getDynamicDataLakeAccess(context) {
11643
13919
  const userTags = context.user.tags || [];
11644
13920
  const entitlementKeys = context.entitlementKeys ?? [];
13921
+ const organizationId = context.user.organizationId ? String(context.user.organizationId) : void 0;
11645
13922
  let dynamicDataLakes;
11646
13923
  if (context.db.dataLakes) try {
11647
- dynamicDataLakes = (await context.db.dataLakes.findActiveByUserTagsAndEntitlements(userTags, entitlementKeys)).map(toDataLakeConfig);
13924
+ dynamicDataLakes = (await context.db.dataLakes.findActiveByUserTagsAndEntitlements(userTags, entitlementKeys, organizationId)).map(toDataLakeConfig);
11648
13925
  } catch {}
11649
13926
  const accessibleLakes = getAccessibleDataLakes(userTags, dynamicDataLakes, entitlementKeys);
11650
13927
  return {
@@ -11792,6 +14069,60 @@ const weatherTool = {
11792
14069
  }
11793
14070
  })
11794
14071
  };
14072
+ /**
14073
+ * Persist a tool-generated file (image, Excel, etc.) as a session-scoped FabFile so it
14074
+ * shows up in the Knowledge Base / WorkBench and is downloadable after the run.
14075
+ *
14076
+ * Why this exists: file-generating tools historically only pushed the generated filename
14077
+ * into `quest.images` via `statusUpdate`, which (a) renders fine inline for images but is
14078
+ * a broken `<img>` for non-image files like .xlsx, and (b) was never recorded as a FabFile,
14079
+ * so nothing the user can browse referenced it — the file was effectively orphaned in the
14080
+ * generated-content bucket (see prod session 6a41abae…, where 4 agent-generated images were
14081
+ * lost entirely). Creating a FabFile with `sessionId` set makes the file appear in the
14082
+ * Knowledge Viewer via `useGetFabFilesBySessionId` → `useMessageFiles`.
14083
+ *
14084
+ * Best-effort: a failure here (missing adapters, storage quota, unsupported mime) must never
14085
+ * break the tool itself — the inline `quest.images` render still works. We log and swallow.
14086
+ *
14087
+ * The file is re-uploaded into the FabFile bucket (`context.storage`) because the Knowledge
14088
+ * Viewer reads FabFiles through their signed `fileUrl`, which is issued against that bucket.
14089
+ */
14090
+ async function persistGeneratedFileAsFabFile(context, file) {
14091
+ const { sessionId, userId, db, storage, logger } = context;
14092
+ if (!sessionId) return;
14093
+ if (!db.fabfiles || !db.users || !db.adminSettings) {
14094
+ logger.warn("[persistGeneratedFileAsFabFile] missing db adapters — skipping FabFile persist");
14095
+ return;
14096
+ }
14097
+ try {
14098
+ await createFabFile(userId, {
14099
+ fileName: file.fileName,
14100
+ mimeType: file.mimeType,
14101
+ fileSize: file.content.length,
14102
+ type: KnowledgeType.FILE,
14103
+ content: file.content,
14104
+ contentType: file.mimeType,
14105
+ sessionId,
14106
+ prefix: "generated",
14107
+ tags: [{
14108
+ name: "generated",
14109
+ strength: 1
14110
+ }]
14111
+ }, {
14112
+ db: {
14113
+ fabFiles: db.fabfiles,
14114
+ adminSettings: db.adminSettings,
14115
+ users: db.users
14116
+ },
14117
+ storage: {
14118
+ upload: (path, content, options) => storage.upload(content, path, options),
14119
+ generateSignedUrl: (path, expireInSeconds, type) => storage.getSignedUrl(path, type ?? "get", { expiresIn: expireInSeconds })
14120
+ }
14121
+ });
14122
+ } catch (err) {
14123
+ logger.error("[persistGeneratedFileAsFabFile] failed to persist generated file (non-fatal)", err);
14124
+ }
14125
+ }
11795
14126
  async function downloadImage$1(url) {
11796
14127
  if (url.startsWith("data:image/")) {
11797
14128
  const base64Data = url.split(",")[1];
@@ -11825,8 +14156,15 @@ async function processAndStoreImages(images, context) {
11825
14156
  return Promise.all(images.map(async (image) => {
11826
14157
  const buffer = await downloadImage$1(image);
11827
14158
  const fileType = await fileTypeFromBuffer(buffer);
11828
- const filename = `${v4()}.${fileType?.ext}`;
11829
- return await context.imageGenerateStorage.upload(buffer, filename, {});
14159
+ const ext = fileType?.ext ?? "png";
14160
+ const filename = `${v4()}.${ext}`;
14161
+ const path = await context.imageGenerateStorage.upload(buffer, filename, {});
14162
+ await persistGeneratedFileAsFabFile(context, {
14163
+ fileName: `generated-image-${filename.slice(0, 8)}.${ext}`,
14164
+ mimeType: fileType?.mime ?? "image/png",
14165
+ content: buffer
14166
+ });
14167
+ return path;
11830
14168
  }));
11831
14169
  }
11832
14170
  async function updateQuestAndReturnMarkdown$1(storedImageUrls, context) {
@@ -15476,37 +17814,92 @@ The problem must define jobs (each with ordered operations) and machines. Each o
15476
17814
  }
15477
17815
  })
15478
17816
  };
17817
+ const FAMILY_IDS = [
17818
+ "routing",
17819
+ "packing",
17820
+ "assignment",
17821
+ "network",
17822
+ "partitioning",
17823
+ "selection",
17824
+ "economic",
17825
+ "continuous"
17826
+ ];
17827
+ /** Run the model and pull a JSON object out of the response (handles code fences). */
17828
+ async function completeToJson(context, systemPrompt, userDescription) {
17829
+ let rawContent = "";
17830
+ await context.llm.complete(context.model ?? "gpt-4", [{
17831
+ role: "system",
17832
+ content: systemPrompt
17833
+ }, {
17834
+ role: "user",
17835
+ content: userDescription
17836
+ }], {
17837
+ temperature: .2,
17838
+ stream: false
17839
+ }, async (texts) => {
17840
+ rawContent = texts.filter((t) => t !== null && t !== void 0).join("");
17841
+ });
17842
+ let jsonStr = rawContent.trim();
17843
+ const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
17844
+ if (codeBlockMatch) jsonStr = codeBlockMatch[1].trim();
17845
+ try {
17846
+ return { json: JSON.parse(jsonStr) };
17847
+ } catch {
17848
+ return { error: `Could not parse the model response as JSON. Raw response:\n${rawContent}` };
17849
+ }
17850
+ }
17851
+ /** Build the per-family system prompt from a real sample as the shape template. */
17852
+ function familyFormulationPrompt(familyId) {
17853
+ return `You convert a natural-language description into a "${familyId}" optimization problem as JSON.
17854
+
17855
+ Output ONLY a single JSON object matching this exact shape (a real ${familyId} example):
17856
+
17857
+ ${JSON.stringify(FAMILY_SAMPLE_PROBLEMS[familyId][0], null, 2)}
17858
+
17859
+ Rules:
17860
+ - "family" MUST be exactly "${familyId}".
17861
+ - Keep it small and browser-solvable, at a scale similar to the example.
17862
+ - ids are integers starting at 0; names are short human-readable strings.
17863
+ - Output ONLY the JSON object — no prose, no markdown fences.`;
17864
+ }
15479
17865
  const quantumFormulateTool = {
15480
17866
  name: "quantum_formulate",
15481
17867
  implementation: (context) => ({
15482
17868
  toolFn: async (value) => {
15483
- const userDescription = value.description?.trim();
15484
- if (!userDescription) return "Error: Please provide a description of the scheduling problem to formulate.";
17869
+ const params = value;
17870
+ const userDescription = params.description?.trim();
17871
+ if (!userDescription) return "Error: Please provide a description of the optimization problem to formulate.";
17872
+ if (params.familyId && !FAMILY_IDS.includes(params.familyId)) return `Error: unknown familyId "${String(params.familyId)}". Valid families: ${FAMILY_IDS.join(", ")}. Omit familyId to formulate a job-shop scheduling problem.`;
17873
+ const familyId = params.familyId || void 0;
15485
17874
  try {
15486
- let rawContent = "";
15487
- await context.llm.complete(context.model ?? "gpt-4", [{
15488
- role: "system",
15489
- content: PROBLEM_FORMULATION_PROMPT
15490
- }, {
15491
- role: "user",
15492
- content: userDescription
15493
- }], {
15494
- temperature: .2,
15495
- stream: false
15496
- }, async (texts) => {
15497
- rawContent = texts.filter((t) => t !== null && t !== void 0).join("");
15498
- });
15499
- let jsonStr = rawContent.trim();
15500
- const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
15501
- if (codeBlockMatch) jsonStr = codeBlockMatch[1].trim();
15502
- let parsed;
15503
- try {
15504
- parsed = JSON.parse(jsonStr);
15505
- } catch {
15506
- return `Error: Could not parse LLM response as JSON. Raw response:\n${rawContent}\n\nPlease try rephrasing your description with more specific details about jobs, machines, and processing times.`;
17875
+ if (familyId) {
17876
+ const result = await completeToJson(context, familyFormulationPrompt(familyId), userDescription);
17877
+ if ("error" in result) return `Error: ${result.error}\n\nPlease rephrase with more specifics for a ${familyId} problem.`;
17878
+ const validated = FAMILY_PROBLEM_SCHEMAS[familyId].safeParse(result.json);
17879
+ if (!validated.success) return `Error: the model produced an invalid ${familyId} problem.\n${validated.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n\nPlease rephrase your description.`;
17880
+ const problem = validated.data;
17881
+ const summary = [
17882
+ `## Formulated: "${problem.name}"`,
17883
+ problem.description ? `\n${problem.description}` : "",
17884
+ "",
17885
+ `**${familyId} problem** ready to load.`,
17886
+ "",
17887
+ `Open the ${familyId} console's Problem tab and click "Load problem" on the pending banner to review, tweak, and race solvers.`
17888
+ ].filter((l) => l !== void 0).join("\n");
17889
+ return JSON.stringify({
17890
+ __uiSideEffect: true,
17891
+ type: "populateFamilyProblem",
17892
+ payload: {
17893
+ familyId,
17894
+ problem
17895
+ },
17896
+ displayMessage: summary
17897
+ });
15507
17898
  }
15508
- const validated = SchedulingProblemSchema.safeParse(parsed);
15509
- if (!validated.success) return `Error: LLM generated an invalid problem structure.\n${validated.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n\nRaw output:\n${jsonStr}\n\nPlease try rephrasing your description.`;
17899
+ const result = await completeToJson(context, PROBLEM_FORMULATION_PROMPT, userDescription);
17900
+ if ("error" in result) return `Error: ${result.error}\n\nPlease try rephrasing with specific jobs, machines, and processing times.`;
17901
+ const validated = SchedulingProblemSchema.safeParse(result.json);
17902
+ if (!validated.success) return `Error: LLM generated an invalid problem structure.\n${validated.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n\nPlease try rephrasing your description.`;
15510
17903
  const problem = validated.data;
15511
17904
  const totalOps = problem.jobs.reduce((s, j) => s + j.operations.length, 0);
15512
17905
  const humanReadableSummary = [
@@ -15541,21 +17934,28 @@ const quantumFormulateTool = {
15541
17934
  },
15542
17935
  toolSchema: {
15543
17936
  name: "quantum_formulate",
15544
- description: `Convert a natural language description of a scheduling/optimization problem into a structured SchedulingProblem that can be solved by the quantum_schedule tool.
17937
+ description: `Convert a natural-language description of an optimization problem into a structured problem that the /opti solvers can run.
15545
17938
 
15546
- Describe the problem in plain English — what jobs need to be done, what machines or resources are available, how long each step takes, and any ordering constraints. The tool will produce a structured problem definition.
17939
+ Pass \`familyId\` to target one of the eight pattern families; omit it for job-shop scheduling:
17940
+ - routing (TSP / vehicle routing), packing (bin packing), assignment (who does what),
17941
+ network (max-cut), partitioning (balanced k-way), selection (knapsack / portfolio),
17942
+ economic (combinatorial auction), continuous (function minimization).
15547
17943
 
15548
- Examples:
15549
- - "I have 3 orders to manufacture. Each needs cutting, welding, and painting. Cutting takes 2h, welding 3h, painting 1h."
15550
- - "Schedule 4 software builds across 2 CI runners. Build A needs compile (5min) then test (3min)..."
15551
- - "A bakery needs to make 5 cakes. Each cake goes through mixing, baking, and decorating on separate stations."`,
15552
- strict: true,
17944
+ Describe the scenario in plain English — the entities, resources, costs/values, and constraints — and the tool produces a structured problem definition for that family.`,
17945
+ strict: false,
15553
17946
  parameters: {
15554
17947
  type: "object",
15555
- properties: { description: {
15556
- type: "string",
15557
- description: "Natural language description of the scheduling problem to formulate"
15558
- } },
17948
+ properties: {
17949
+ description: {
17950
+ type: "string",
17951
+ description: "Natural-language description of the optimization problem to formulate"
17952
+ },
17953
+ familyId: {
17954
+ type: "string",
17955
+ enum: FAMILY_IDS,
17956
+ description: "Optional optimization family. Omit for job-shop scheduling; otherwise one of the eight families."
17957
+ }
17958
+ },
15559
17959
  required: ["description"],
15560
17960
  additionalProperties: false
15561
17961
  }
@@ -15869,6 +18269,7 @@ Please generate a complete, well-structured notebook that performs this analysis
15869
18269
  }
15870
18270
  })
15871
18271
  };
18272
+ const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
15872
18273
  const LIMITS = {
15873
18274
  MAX_SHEETS: 10,
15874
18275
  MAX_CELLS_PER_SHEET: 1e4,
@@ -16187,9 +18588,14 @@ const excelGenerationTool = {
16187
18588
  const sanitizedName = sanitizeFilename(params.filename);
16188
18589
  const filename = `${sanitizedName}-${v4().slice(0, 8)}.xlsx`;
16189
18590
  await context.imageGenerateStorage.upload(buffer, filename, {
16190
- ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
18591
+ ContentType: XLSX_MIME,
16191
18592
  ContentDisposition: `attachment; filename="${sanitizedName}.xlsx"`
16192
18593
  });
18594
+ await persistGeneratedFileAsFabFile(context, {
18595
+ fileName: `${sanitizedName}.xlsx`,
18596
+ mimeType: XLSX_MIME,
18597
+ content: buffer
18598
+ });
16193
18599
  await context.onFinish?.("excel_generation", [filename]);
16194
18600
  await context.statusUpdate({ images: [filename] });
16195
18601
  return `Successfully generated Excel file: ${sanitizedName}.xlsx`;
@@ -17475,10 +19881,11 @@ const b4mTools = {
17475
19881
  fmp_financial_data: fmpTool,
17476
19882
  skill: skillTool
17477
19883
  };
17478
- const generateTools = (userId, user, logger, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model, imageProcessorLambdaName, tools = b4mTools, allowedDirectories, entitlementKeys = []) => {
19884
+ const generateTools = (userId, user, logger, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model, imageProcessorLambdaName, tools = b4mTools, allowedDirectories, entitlementKeys = [], sessionId) => {
17479
19885
  const context = {
17480
19886
  userId,
17481
19887
  user,
19888
+ sessionId,
17482
19889
  logger,
17483
19890
  db,
17484
19891
  storage,
@@ -18499,7 +20906,11 @@ var ReActAgent = class extends EventEmitter {
18499
20906
  * This can be overridden in the context or customized here
18500
20907
  */
18501
20908
  getSystemPrompt() {
18502
- if (this.context.systemPrompt) return this.context.systemPrompt;
20909
+ const base = this.context.systemPrompt ? this.context.systemPrompt : this.buildDefaultSystemPrompt();
20910
+ const persona = this.context.personaPrompt?.trim();
20911
+ return persona ? `${persona}\n\n${base}` : base;
20912
+ }
20913
+ buildDefaultSystemPrompt() {
18503
20914
  return `You are an autonomous AI agent with access to tools. Your job is to solve problems by taking action, not by asking for clarification.
18504
20915
 
18505
20916
  CORE PRINCIPLES:
@@ -19122,7 +21533,7 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
19122
21533
  })}</function>`;
19123
21534
  }
19124
21535
  }
19125
- return `Error: Tool ${toolUse.name} not found in agent context`;
21536
+ return `Error: Tool "${toolUse.name}" is not available in this context and cannot be called. Do NOT attempt to call it again. Proceed without it — call other available tools if your plan still needs them, or compose your final answer from the information already gathered if no more tools are needed.`;
19126
21537
  }
19127
21538
  try {
19128
21539
  const params = this.parseToolArguments(toolUse.arguments);
@@ -20490,33 +22901,6 @@ z.object({
20490
22901
  fabFileId: z.string(),
20491
22902
  chunkId: z.string()
20492
22903
  });
20493
- z.object({
20494
- fileName: z.string(),
20495
- mimeType: z.string(),
20496
- fileSize: z.number(),
20497
- type: z.enum(KnowledgeType),
20498
- content: z.union([z.string(), z.instanceof(Buffer)]).optional(),
20499
- organizationId: z.string().optional(),
20500
- /**
20501
- * Content type of the file
20502
- * @example 'text/markdown'
20503
- * @example 'application/pdf'
20504
- * @example 'application/octet-stream' for binary files
20505
- */
20506
- contentType: z.string().optional(),
20507
- public: z.boolean().optional(),
20508
- prefix: z.string().optional(),
20509
- system: z.boolean().optional(),
20510
- tags: z.array(z.object({
20511
- name: z.string(),
20512
- strength: z.number()
20513
- })).optional(),
20514
- systemPriority: z.number().optional(),
20515
- sessionId: z.string().optional(),
20516
- contentHash: z.string().optional(),
20517
- batchId: z.string().optional(),
20518
- relativePath: z.string().optional()
20519
- });
20520
22904
  z.object({ sessionId: z.string() });
20521
22905
  z.object({ questId: z.string() });
20522
22906
  z.object({ url: z.string().regex(/^(?!https?:\/\/(drive|docs)\.google\.com\/(?:file\/d\/|open\?id=|uc\?id=|document\/d\/|spreadsheets\/d\/|presentation\/d\/|forms\/d\/|drive\/folders\/)([a-zA-Z0-9_-]{10,})).+/) });