@bike4mind/cli 0.17.2 → 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 ProjectEvents, $t as parseRateLimitHeaders, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as obfuscateApiKey, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImageModel, G as ModalEvents, Gt as settingsMap, H as KnowledgeType, Ht as resolveNavigationIntents, I as ImageGenerationUsageTransaction, It as isModelAccessible, J as OpenAIEmbeddingModel, K as ModelBackend, Kt as substituteArguments$1, L as ImageModels, Lt as isSupportedEmbeddingModel, M as GenericCreditDeductTransaction, Mt as getMcpProviderMetadata, N as HTTPError, Nt as getViewById, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as isGPTImage2Model, Q as ProfileEvents, Qt as isNearLimit, R as InboxEvents, Rt as isZodError, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, Ut as sanitizeTelemetryError, V as InviteType, Vt as parseSkillArguments, W as MiscEvents, Wt as secureParameters, X as Permission, Xt as buildRateLimitLogEntry, Y as OpenAIImageGenerationInput, Z as PermissionDeniedError, Zt as extractSnippetMeta, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, en as CollectionType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as toDataLakeConfig, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as mapMimeTypeToArtifactType } from "./ConfigStore-7kcIJ-ux.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-DLM23Ze7.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";
@@ -8843,25 +8843,55 @@ function sanitizeJsonStringWithMeta(jsonStr, options) {
8843
8843
  }
8844
8844
  //#endregion
8845
8845
  //#region ../../b4m-core/quantum/dist/index.mjs
8846
+ /**
8847
+ * Scheduling's objective is makespan (lower is better), rendered in time units.
8848
+ * This is the `ObjectiveMeta` every scheduling `SolverResult` carries so that
8849
+ * shared, family-agnostic UI can render scheduling results too.
8850
+ */
8851
+ const SCHEDULING_OBJECTIVE_META = {
8852
+ label: "makespan",
8853
+ unit: "t",
8854
+ direction: "min",
8855
+ format: { suffix: "t" }
8856
+ };
8857
+ /**
8858
+ * Build a scheduling `SolverResult` from the domain-native quantities a solver
8859
+ * produces (a schedule + its makespan), filling in the shared-contract fields.
8860
+ * Keeps the makespan→objective mapping in one place across all 11 solvers.
8861
+ */
8862
+ function makeSchedulingResult(args) {
8863
+ return {
8864
+ solverId: args.solverId,
8865
+ solverName: args.solverName,
8866
+ objective: args.makespan,
8867
+ internalCost: args.makespan,
8868
+ objectiveMeta: SCHEDULING_OBJECTIVE_META,
8869
+ solution: args.schedule,
8870
+ elapsedMs: args.elapsedMs,
8871
+ iterations: args.iterations,
8872
+ trace: args.trace,
8873
+ metadata: args.metadata
8874
+ };
8875
+ }
8846
8876
  const OperationSchema = z.object({
8847
8877
  jobId: z.number(),
8848
8878
  machineId: z.number(),
8849
- duration: z.number().positive()
8879
+ duration: z.number().positive().max(1e6)
8850
8880
  });
8851
8881
  const JobSchema = z.object({
8852
8882
  id: z.number(),
8853
- name: z.string(),
8854
- operations: z.array(OperationSchema)
8883
+ name: z.string().max(200),
8884
+ operations: z.array(OperationSchema).max(1e3)
8855
8885
  });
8856
8886
  const MachineSchema = z.object({
8857
8887
  id: z.number(),
8858
- name: z.string()
8888
+ name: z.string().max(200)
8859
8889
  });
8860
8890
  const SchedulingProblemSchema = z.object({
8861
- name: z.string(),
8862
- description: z.string().optional(),
8863
- jobs: z.array(JobSchema),
8864
- 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),
8865
8895
  solvedJobUrl: z.string().url().optional()
8866
8896
  });
8867
8897
  const SolverIdSchema = z.enum([
@@ -8920,19 +8950,24 @@ const greedySolver = {
8920
8950
  if (options?.onProgress) options.onProgress({
8921
8951
  solverId: "greedy",
8922
8952
  percentage: Math.round(opsScheduled / totalOps * 100),
8923
- bestMakespan: opEnd,
8953
+ bestObjective: opEnd,
8924
8954
  currentIteration: opsScheduled,
8925
8955
  totalIterations: totalOps
8926
8956
  });
8927
8957
  }
8928
- return {
8958
+ const makespan = Math.max(...schedule.map((op) => op.endTime));
8959
+ return makeSchedulingResult({
8929
8960
  solverId: "greedy",
8930
8961
  solverName: "Greedy (Earliest Start)",
8931
- makespan: Math.max(...schedule.map((op) => op.endTime)),
8962
+ makespan,
8932
8963
  schedule,
8933
8964
  elapsedMs: performance.now() - startTime,
8934
- iterations: totalOps
8935
- };
8965
+ iterations: totalOps,
8966
+ trace: [{
8967
+ solution: schedule,
8968
+ objective: makespan
8969
+ }]
8970
+ });
8936
8971
  }
8937
8972
  };
8938
8973
  /**
@@ -9062,6 +9097,34 @@ function validateScheduleFeasibility(schedule) {
9062
9097
  return true;
9063
9098
  }
9064
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
+ /**
9065
9128
  * Simple seeded pseudo-random number generator (mulberry32).
9066
9129
  */
9067
9130
  function createRng$1(seed) {
@@ -9097,25 +9160,31 @@ const naiveSolver = {
9097
9160
  description: "Evaluates all permutations to find the optimal schedule. Only feasible for small problems.",
9098
9161
  async solve(problem, options) {
9099
9162
  const startTime = performance.now();
9163
+ const stop = makeScheduleStopper(options, startTime, 15e3);
9100
9164
  const ops = buildOperationList(problem);
9101
9165
  const totalPerms = factorial(ops.length);
9102
9166
  const cappedTotal = Math.min(totalPerms, MAX_PERMUTATIONS);
9103
- let bestMakespan = Infinity;
9104
9167
  let bestSchedule = decodePermutation(ops, problem);
9168
+ let bestMakespan = computeMakespan(bestSchedule);
9105
9169
  let count = 0;
9170
+ const trace = [{
9171
+ solution: bestSchedule,
9172
+ objective: bestMakespan
9173
+ }];
9106
9174
  for (const perm of permutations(ops)) {
9107
- if (count >= MAX_PERMUTATIONS) break;
9175
+ if (count >= MAX_PERMUTATIONS || stop()) break;
9108
9176
  const schedule = decodePermutation(perm, problem);
9109
9177
  const makespan = computeMakespan(schedule);
9110
9178
  if (makespan < bestMakespan) {
9111
9179
  bestMakespan = makespan;
9112
9180
  bestSchedule = schedule;
9181
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9113
9182
  }
9114
9183
  count++;
9115
9184
  if (options?.onProgress && count % 100 === 0) options.onProgress({
9116
9185
  solverId: "naive",
9117
9186
  percentage: Math.min(99, Math.round(count / cappedTotal * 100)),
9118
- bestMakespan,
9187
+ bestObjective: bestMakespan,
9119
9188
  currentIteration: count,
9120
9189
  totalIterations: cappedTotal
9121
9190
  });
@@ -9123,18 +9192,19 @@ const naiveSolver = {
9123
9192
  options?.onProgress?.({
9124
9193
  solverId: "naive",
9125
9194
  percentage: 100,
9126
- bestMakespan,
9195
+ bestObjective: bestMakespan,
9127
9196
  currentIteration: count,
9128
9197
  totalIterations: cappedTotal
9129
9198
  });
9130
- return {
9199
+ return makeSchedulingResult({
9131
9200
  solverId: "naive",
9132
9201
  solverName: "Naive (Brute Force)",
9133
9202
  makespan: bestMakespan,
9134
9203
  schedule: bestSchedule,
9135
9204
  elapsedMs: performance.now() - startTime,
9136
- iterations: count
9137
- };
9205
+ iterations: count,
9206
+ trace
9207
+ });
9138
9208
  }
9139
9209
  };
9140
9210
  function factorial(n) {
@@ -9158,18 +9228,22 @@ const randomRestartSolver = {
9158
9228
  description: "Multiple random starts with local search improvement via operation swaps.",
9159
9229
  async solve(problem, options) {
9160
9230
  const startTime = performance.now();
9161
- const timeoutMs = options?.timeoutMs ?? 1e4;
9231
+ const stop = makeScheduleStopper(options, startTime, 1e4);
9162
9232
  const rng = createRng$1(42);
9163
- let globalBestMakespan = Infinity;
9164
9233
  let globalBestSchedule = decodePermutation(randomPermutation(problem, rng), problem);
9234
+ let globalBestMakespan = computeMakespan(globalBestSchedule);
9165
9235
  let totalIterations = 0;
9236
+ const trace = [{
9237
+ solution: globalBestSchedule,
9238
+ objective: globalBestMakespan
9239
+ }];
9166
9240
  for (let restart = 0; restart < RESTARTS; restart++) {
9167
- if (performance.now() - startTime > timeoutMs) break;
9241
+ if (stop()) break;
9168
9242
  let currentPerm = randomPermutation(problem, rng);
9169
9243
  let currentSchedule = decodePermutation(currentPerm, problem);
9170
9244
  let currentMakespan = computeMakespan(currentSchedule);
9171
9245
  for (let i = 0; i < HILL_CLIMB_ITERATIONS; i++) {
9172
- if (performance.now() - startTime > timeoutMs) break;
9246
+ if (stop()) break;
9173
9247
  const neighborPerm = swapNeighbor(currentPerm, rng);
9174
9248
  const neighborSchedule = decodePermutation(neighborPerm, problem);
9175
9249
  const neighborMakespan = computeMakespan(neighborSchedule);
@@ -9183,23 +9257,25 @@ const randomRestartSolver = {
9183
9257
  if (currentMakespan < globalBestMakespan) {
9184
9258
  globalBestMakespan = currentMakespan;
9185
9259
  globalBestSchedule = currentSchedule;
9260
+ pushScheduleTrace(trace, globalBestSchedule, globalBestMakespan);
9186
9261
  }
9187
9262
  options?.onProgress?.({
9188
9263
  solverId: "random-restart",
9189
9264
  percentage: Math.round((restart + 1) / RESTARTS * 100),
9190
- bestMakespan: globalBestMakespan,
9265
+ bestObjective: globalBestMakespan,
9191
9266
  currentIteration: restart + 1,
9192
9267
  totalIterations: RESTARTS
9193
9268
  });
9194
9269
  }
9195
- return {
9270
+ return makeSchedulingResult({
9196
9271
  solverId: "random-restart",
9197
9272
  solverName: "Random Restart Hill Climbing",
9198
9273
  makespan: globalBestMakespan,
9199
9274
  schedule: globalBestSchedule,
9200
9275
  elapsedMs: performance.now() - startTime,
9201
- iterations: totalIterations
9202
- };
9276
+ iterations: totalIterations,
9277
+ trace
9278
+ });
9203
9279
  }
9204
9280
  };
9205
9281
  const SA_CONFIGS = [
@@ -9235,17 +9311,21 @@ function createSASolver(config) {
9235
9311
  description: config.description,
9236
9312
  async solve(problem, options) {
9237
9313
  const startTime = performance.now();
9238
- const timeoutMs = options?.timeoutMs ?? 3e4;
9314
+ const stop = makeScheduleStopper(options, startTime, 3e4);
9239
9315
  const rng = createRng$1(42);
9240
9316
  let currentPerm = randomPermutation(problem, rng);
9241
9317
  let currentSchedule = decodePermutation(currentPerm, problem);
9242
9318
  let currentMakespan = computeMakespan(currentSchedule);
9243
9319
  let bestSchedule = currentSchedule;
9244
9320
  let bestMakespan = currentMakespan;
9321
+ const trace = [{
9322
+ solution: bestSchedule,
9323
+ objective: bestMakespan
9324
+ }];
9245
9325
  let temperature = config.initialTemp;
9246
9326
  const progressInterval = Math.max(1, Math.floor(config.iterations / 100));
9247
9327
  for (let i = 0; i < config.iterations; i++) {
9248
- if (performance.now() - startTime > timeoutMs) break;
9328
+ if (stop()) break;
9249
9329
  const neighborPerm = swapNeighbor(currentPerm, rng);
9250
9330
  const neighborSchedule = decodePermutation(neighborPerm, problem);
9251
9331
  const neighborMakespan = computeMakespan(neighborSchedule);
@@ -9257,13 +9337,14 @@ function createSASolver(config) {
9257
9337
  if (currentMakespan < bestMakespan) {
9258
9338
  bestSchedule = currentSchedule;
9259
9339
  bestMakespan = currentMakespan;
9340
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9260
9341
  }
9261
9342
  }
9262
9343
  temperature *= config.coolingRate;
9263
9344
  if (options?.onProgress && i % progressInterval === 0) options.onProgress({
9264
9345
  solverId: config.id,
9265
9346
  percentage: Math.round(i / config.iterations * 100),
9266
- bestMakespan,
9347
+ bestObjective: bestMakespan,
9267
9348
  currentIteration: i,
9268
9349
  totalIterations: config.iterations
9269
9350
  });
@@ -9271,18 +9352,19 @@ function createSASolver(config) {
9271
9352
  options?.onProgress?.({
9272
9353
  solverId: config.id,
9273
9354
  percentage: 100,
9274
- bestMakespan,
9355
+ bestObjective: bestMakespan,
9275
9356
  currentIteration: config.iterations,
9276
9357
  totalIterations: config.iterations
9277
9358
  });
9278
- return {
9359
+ return makeSchedulingResult({
9279
9360
  solverId: config.id,
9280
9361
  solverName: config.name,
9281
9362
  makespan: bestMakespan,
9282
9363
  schedule: bestSchedule,
9283
9364
  elapsedMs: performance.now() - startTime,
9284
- iterations: config.iterations
9285
- };
9365
+ iterations: config.iterations,
9366
+ trace
9367
+ });
9286
9368
  }
9287
9369
  };
9288
9370
  }
@@ -9296,7 +9378,7 @@ const simulatedAnnealingLargeSolver = createSASolver(SA_CONFIGS[2]);
9296
9378
  * forbidden for a period. This prevents cycling and forces exploration
9297
9379
  * of new regions of the solution space.
9298
9380
  */
9299
- const ITERATIONS$1 = 1e4;
9381
+ const ITERATIONS$2 = 1e4;
9300
9382
  const TABU_TENURE = 15;
9301
9383
  const NEIGHBORHOOD_SIZE = 20;
9302
9384
  const tabuSolver = {
@@ -9305,17 +9387,21 @@ const tabuSolver = {
9305
9387
  description: "Hill climbing with short-term memory to avoid revisiting recent solutions.",
9306
9388
  async solve(problem, options) {
9307
9389
  const startTime = performance.now();
9308
- const timeoutMs = options?.timeoutMs ?? 15e3;
9390
+ const stop = makeScheduleStopper(options, startTime, 15e3);
9309
9391
  const rng = createRng$1(42);
9310
9392
  let currentPerm = randomPermutation(problem, rng);
9311
9393
  let currentSchedule = decodePermutation(currentPerm, problem);
9312
9394
  let currentMakespan = computeMakespan(currentSchedule);
9313
9395
  let bestSchedule = currentSchedule;
9314
9396
  let bestMakespan = currentMakespan;
9397
+ const trace = [{
9398
+ solution: bestSchedule,
9399
+ objective: bestMakespan
9400
+ }];
9315
9401
  const tabuList = /* @__PURE__ */ new Map();
9316
- const progressInterval = Math.max(1, Math.floor(ITERATIONS$1 / 100));
9317
- for (let iter = 0; iter < ITERATIONS$1; iter++) {
9318
- 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;
9319
9405
  let bestNeighborPerm = null;
9320
9406
  let bestNeighborMakespan = Infinity;
9321
9407
  let bestSwapKey = "";
@@ -9341,6 +9427,7 @@ const tabuSolver = {
9341
9427
  if (currentMakespan < bestMakespan) {
9342
9428
  bestMakespan = currentMakespan;
9343
9429
  bestSchedule = currentSchedule;
9430
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9344
9431
  }
9345
9432
  }
9346
9433
  if (iter % 100 === 0) {
@@ -9348,27 +9435,28 @@ const tabuSolver = {
9348
9435
  }
9349
9436
  if (options?.onProgress && iter % progressInterval === 0) options.onProgress({
9350
9437
  solverId: "tabu",
9351
- percentage: Math.round(iter / ITERATIONS$1 * 100),
9352
- bestMakespan,
9438
+ percentage: Math.round(iter / ITERATIONS$2 * 100),
9439
+ bestObjective: bestMakespan,
9353
9440
  currentIteration: iter,
9354
- totalIterations: ITERATIONS$1
9441
+ totalIterations: ITERATIONS$2
9355
9442
  });
9356
9443
  }
9357
9444
  options?.onProgress?.({
9358
9445
  solverId: "tabu",
9359
9446
  percentage: 100,
9360
- bestMakespan,
9361
- currentIteration: ITERATIONS$1,
9362
- totalIterations: ITERATIONS$1
9447
+ bestObjective: bestMakespan,
9448
+ currentIteration: ITERATIONS$2,
9449
+ totalIterations: ITERATIONS$2
9363
9450
  });
9364
- return {
9451
+ return makeSchedulingResult({
9365
9452
  solverId: "tabu",
9366
9453
  solverName: "Tabu Search",
9367
9454
  makespan: bestMakespan,
9368
9455
  schedule: bestSchedule,
9369
9456
  elapsedMs: performance.now() - startTime,
9370
- iterations: ITERATIONS$1
9371
- };
9457
+ iterations: ITERATIONS$2,
9458
+ trace
9459
+ });
9372
9460
  }
9373
9461
  };
9374
9462
  /**
@@ -9387,7 +9475,7 @@ const geneticAlgorithmSolver = {
9387
9475
  description: "Population-based search with tournament selection, crossover, and mutation.",
9388
9476
  async solve(problem, options) {
9389
9477
  const startTime = performance.now();
9390
- const timeoutMs = options?.timeoutMs ?? 2e4;
9478
+ const stop = makeScheduleStopper(options, startTime, 2e4);
9391
9479
  const rng = createRng$1(42);
9392
9480
  let population = [];
9393
9481
  for (let i = 0; i < POPULATION_SIZE; i++) {
@@ -9399,8 +9487,12 @@ const geneticAlgorithmSolver = {
9399
9487
  });
9400
9488
  }
9401
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
+ }];
9402
9494
  for (let gen = 0; gen < GENERATIONS; gen++) {
9403
- if (performance.now() - startTime > timeoutMs) break;
9495
+ if (stop()) break;
9404
9496
  const newPopulation = [];
9405
9497
  newPopulation.push({ ...bestIndividual });
9406
9498
  while (newPopulation.length < POPULATION_SIZE) {
@@ -9416,24 +9508,28 @@ const geneticAlgorithmSolver = {
9416
9508
  }
9417
9509
  population = newPopulation;
9418
9510
  const genBest = population.reduce((best, ind) => ind.makespan < best.makespan ? ind : best);
9419
- 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
+ }
9420
9515
  options?.onProgress?.({
9421
9516
  solverId: "genetic-algorithm",
9422
9517
  percentage: Math.round((gen + 1) / GENERATIONS * 100),
9423
- bestMakespan: bestIndividual.makespan,
9518
+ bestObjective: bestIndividual.makespan,
9424
9519
  currentIteration: gen + 1,
9425
9520
  totalIterations: GENERATIONS
9426
9521
  });
9427
9522
  }
9428
9523
  const bestSchedule = decodePermutation(bestIndividual.perm, problem);
9429
- return {
9524
+ return makeSchedulingResult({
9430
9525
  solverId: "genetic-algorithm",
9431
9526
  solverName: "Genetic Algorithm",
9432
9527
  makespan: bestIndividual.makespan,
9433
9528
  schedule: bestSchedule,
9434
9529
  elapsedMs: performance.now() - startTime,
9435
- iterations: GENERATIONS * POPULATION_SIZE
9436
- };
9530
+ iterations: GENERATIONS * POPULATION_SIZE,
9531
+ trace
9532
+ });
9437
9533
  }
9438
9534
  };
9439
9535
  function tournamentSelect(population, size, rng) {
@@ -9477,10 +9573,10 @@ function crossover(parent1, parent2, rng) {
9477
9573
  * assignments) that appear in good solutions and evaporate over time.
9478
9574
  */
9479
9575
  const NUM_ANTS = 20;
9480
- const ITERATIONS = 100;
9576
+ const ITERATIONS$1 = 100;
9481
9577
  const ALPHA = 1;
9482
9578
  const BETA = 2;
9483
- const EVAPORATION = .1;
9579
+ const EVAPORATION$1 = .1;
9484
9580
  const Q = 100;
9485
9581
  const antColonySolver = {
9486
9582
  id: "ant-colony",
@@ -9488,7 +9584,7 @@ const antColonySolver = {
9488
9584
  description: "Swarm intelligence using pheromone trails to guide solution construction.",
9489
9585
  async solve(problem, options) {
9490
9586
  const startTime = performance.now();
9491
- const timeoutMs = options?.timeoutMs ?? 2e4;
9587
+ const stop = makeScheduleStopper(options, startTime, 2e4);
9492
9588
  const rng = createRng$1(42);
9493
9589
  const allOps = buildOperationList(problem);
9494
9590
  const numOps = allOps.length;
@@ -9496,10 +9592,14 @@ const antColonySolver = {
9496
9592
  const heuristic = allOps.map((ref) => {
9497
9593
  return 1 / problem.jobs.find((j) => j.id === ref.jobId).operations[ref.opIndex].duration;
9498
9594
  });
9499
- let bestMakespan = Infinity;
9500
9595
  let bestSchedule = decodePermutation(allOps, problem);
9501
- for (let iter = 0; iter < ITERATIONS; iter++) {
9502
- 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;
9503
9603
  const antSolutions = [];
9504
9604
  for (let ant = 0; ant < NUM_ANTS; ant++) {
9505
9605
  const perm = constructSolution(allOps, pheromone, heuristic, rng);
@@ -9512,9 +9612,10 @@ const antColonySolver = {
9512
9612
  if (makespan < bestMakespan) {
9513
9613
  bestMakespan = makespan;
9514
9614
  bestSchedule = schedule;
9615
+ pushScheduleTrace(trace, bestSchedule, bestMakespan);
9515
9616
  }
9516
9617
  }
9517
- 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;
9518
9619
  for (const solution of antSolutions) {
9519
9620
  const deposit = Q / solution.makespan;
9520
9621
  for (let pos = 0; pos < solution.perm.length; pos++) {
@@ -9524,20 +9625,21 @@ const antColonySolver = {
9524
9625
  }
9525
9626
  options?.onProgress?.({
9526
9627
  solverId: "ant-colony",
9527
- percentage: Math.round((iter + 1) / ITERATIONS * 100),
9528
- bestMakespan,
9628
+ percentage: Math.round((iter + 1) / ITERATIONS$1 * 100),
9629
+ bestObjective: bestMakespan,
9529
9630
  currentIteration: iter + 1,
9530
- totalIterations: ITERATIONS
9631
+ totalIterations: ITERATIONS$1
9531
9632
  });
9532
9633
  }
9533
- return {
9634
+ return makeSchedulingResult({
9534
9635
  solverId: "ant-colony",
9535
9636
  solverName: "Ant Colony Optimization",
9536
9637
  makespan: bestMakespan,
9537
9638
  schedule: bestSchedule,
9538
9639
  elapsedMs: performance.now() - startTime,
9539
- iterations: ITERATIONS * NUM_ANTS
9540
- };
9640
+ iterations: ITERATIONS$1 * NUM_ANTS,
9641
+ trace
9642
+ });
9541
9643
  }
9542
9644
  };
9543
9645
  /**
@@ -9783,23 +9885,18 @@ function validateSchedule(problem, schedule) {
9783
9885
  errors
9784
9886
  };
9785
9887
  }
9786
- const MAX_BINARY_VARS = 150;
9787
- const MAX_TOTAL_OPS = 80;
9788
- const MAX_OPS_PER_MACHINE = 8;
9789
- /**
9790
- * Load HiGHS - creates FRESH instance each time.
9791
- *
9792
- * We intentionally do NOT cache the HiGHS instance.
9793
- * Caching causes WASM corruption errors.
9794
- */
9888
+ /** Load a fresh HiGHS WASM instance (never cache it — see file header). */
9795
9889
  async function loadHiGHS() {
9796
9890
  const highsLoader = (await import("highs")).default;
9797
- return await highsLoader({ locateFile: (file) => {
9891
+ return highsLoader({ locateFile: (file) => {
9798
9892
  const globalSelf = globalThis;
9799
9893
  if (globalSelf.location?.origin) return `${globalSelf.location.origin}/${file}`;
9800
9894
  return file;
9801
9895
  } });
9802
9896
  }
9897
+ const MAX_BINARY_VARS = 150;
9898
+ const MAX_TOTAL_OPS = 80;
9899
+ const MAX_OPS_PER_MACHINE = 8;
9803
9900
  function elapsed(startTime) {
9804
9901
  return typeof performance !== "undefined" ? performance.now() - startTime : Date.now() - startTime;
9805
9902
  }
@@ -9813,7 +9910,7 @@ const highsSolver = {
9813
9910
  options?.onProgress?.({
9814
9911
  solverId: "highs",
9815
9912
  percentage,
9816
- bestMakespan
9913
+ bestObjective: bestMakespan
9817
9914
  });
9818
9915
  };
9819
9916
  reportProgress(0);
@@ -9831,6 +9928,21 @@ const highsSolver = {
9831
9928
  const lpString = generateLPFormulation(problem);
9832
9929
  if (!lpString.includes("Subject To") || !lpString.includes("Bounds")) throw new Error("Invalid LP formulation - missing required sections");
9833
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
+ }
9834
9946
  let solution;
9835
9947
  try {
9836
9948
  solution = highs.solve(lpString, {
@@ -9863,14 +9975,18 @@ const highsSolver = {
9863
9975
  throw new Error(`[HiGHS] Solution validation failed: ${errorDetails}`);
9864
9976
  }
9865
9977
  reportProgress(100, makespan);
9866
- return {
9978
+ return makeSchedulingResult({
9867
9979
  solverId: "highs",
9868
9980
  solverName: "HiGHS (WASM)",
9869
9981
  makespan,
9870
9982
  schedule,
9871
9983
  elapsedMs: elapsed(startTime),
9872
- iterations: 1
9873
- };
9984
+ iterations: 1,
9985
+ trace: [{
9986
+ solution: schedule,
9987
+ objective: makespan
9988
+ }]
9989
+ });
9874
9990
  } catch (error) {
9875
9991
  reportProgress(0);
9876
9992
  throw error;
@@ -10514,30 +10630,39 @@ const allSolvers = [
10514
10630
  solverId: "qaoa-local",
10515
10631
  percentage: pct
10516
10632
  });
10633
+ const cancelled = () => options.shouldCancel?.() ?? false;
10517
10634
  progress(0);
10518
10635
  const greedyResult = await greedySolver.solve(problem, {});
10519
10636
  progress(10);
10520
- const maxTime = greedyResult.makespan;
10521
- const encoded = encodeSchedulingWithMetadata(problem, maxTime);
10637
+ const greedyMakespan = greedyResult.objective;
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
+ });
10659
+ const encoded = encodeSchedulingWithMetadata(problem, greedyMakespan);
10522
10660
  const numQubits = encoded.qubo.size;
10523
10661
  if (numQubits > MAX_QUBITS || numQubits === 0) {
10524
10662
  progress(100);
10525
- return {
10526
- solverId: "qaoa-local",
10527
- solverName: "QAOA (Local)",
10528
- makespan: greedyResult.makespan,
10529
- schedule: greedyResult.schedule,
10530
- elapsedMs: elapsed(),
10531
- metadata: {
10532
- layers: LAYERS$1,
10533
- shots: 0,
10534
- gamma: 0,
10535
- beta: 0,
10536
- rawMakespan: greedyResult.makespan,
10537
- usedFallback: true
10538
- }
10539
- };
10663
+ return greedyFallback(numQubits === 0 ? "no qubits to encode" : `instance needs ${numQubits} qubits (cap ${MAX_QUBITS})`);
10540
10664
  }
10665
+ if (cancelled()) return greedyFallback("cancelled");
10541
10666
  const ising = quboToIsing(encoded.qubo);
10542
10667
  const backend = new LocalSimBackend();
10543
10668
  progress(20);
@@ -10547,30 +10672,39 @@ const allSolvers = [
10547
10672
  p: LAYERS$1
10548
10673
  });
10549
10674
  progress(75);
10675
+ if (cancelled()) return greedyFallback("cancelled");
10550
10676
  const gates = buildQAOACircuit(numQubits, ising, [gamma], [beta], LAYERS$1);
10551
10677
  const { probabilities } = await backend.run(numQubits, gates);
10552
10678
  const samples = sampleFromProbabilities(probabilities, FINAL_SHOTS$1);
10553
10679
  progress(90);
10554
- const candidates = [decodeFromArgmax(problem, encoded.variables, probabilities, greedyResult.schedule), decodeSamples(problem, encoded.variables, samples)].filter((d) => d !== null);
10680
+ const candidates = [decodeFromArgmax(problem, encoded.variables, probabilities, greedySchedule), decodeSamples(problem, encoded.variables, samples)].filter((d) => d !== null);
10555
10681
  const bestQuantum = candidates.length > 0 ? candidates.reduce((a, b) => b.makespan < a.makespan ? b : a) : null;
10556
- const usedFallback = bestQuantum === null || bestQuantum.makespan >= greedyResult.makespan;
10557
- const winner = usedFallback ? greedyResult : bestQuantum;
10682
+ const usedFallback = bestQuantum === null || bestQuantum.makespan >= greedyMakespan;
10683
+ const winnerMakespan = usedFallback ? greedyMakespan : bestQuantum.makespan;
10684
+ const winnerSchedule = usedFallback ? greedySchedule : bestQuantum.schedule;
10558
10685
  progress(100);
10559
- return {
10686
+ return makeSchedulingResult({
10560
10687
  solverId: "qaoa-local",
10561
10688
  solverName: "QAOA (Local)",
10562
- makespan: winner.makespan,
10563
- schedule: winner.schedule,
10689
+ makespan: winnerMakespan,
10690
+ schedule: winnerSchedule,
10564
10691
  elapsedMs: elapsed(),
10692
+ trace: [{
10693
+ solution: greedySchedule,
10694
+ objective: greedyMakespan
10695
+ }, {
10696
+ solution: winnerSchedule,
10697
+ objective: winnerMakespan
10698
+ }],
10565
10699
  metadata: {
10566
10700
  layers: LAYERS$1,
10567
10701
  shots: FINAL_SHOTS$1,
10568
10702
  gamma,
10569
10703
  beta,
10570
- rawMakespan: bestQuantum?.makespan ?? greedyResult.makespan,
10704
+ rawMakespan: bestQuantum?.makespan ?? greedyMakespan,
10571
10705
  usedFallback
10572
10706
  }
10573
- };
10707
+ });
10574
10708
  }
10575
10709
  }
10576
10710
  ];
@@ -10869,54 +11003,1966 @@ No markdown, no explanation, no code blocks — just the raw JSON object.`;
10869
11003
  " \\/ <- fit here",
10870
11004
  "Minimize: sum(error^2)"
10871
11005
  ].join("\n");
10872
- /** Build a jittered grid mesh: cells at (col,row), edges between face-sharing neighbors. */
10873
- function gridMesh(cols, rows, cellName, weightAt, jitter) {
10874
- const nodes = [];
10875
- const padX = 100 / (cols + 1);
10876
- const padY = 100 / (rows + 1);
10877
- for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
10878
- const i = r * cols + c;
10879
- const { dx, dy } = jitter(i);
10880
- nodes.push({
10881
- id: i,
10882
- name: cellName(i),
10883
- x: Math.round(padX * (c + 1) + dx),
10884
- y: Math.round(padY * (r + 1) + dy)
10885
- });
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
+ ]
10886
12932
  }
10887
- const edges = [];
10888
- for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
10889
- const i = r * cols + c;
10890
- if (c + 1 < cols) edges.push({
10891
- a: i,
10892
- b: i + 1,
10893
- w: weightAt(i, i + 1)
10894
- });
10895
- if (r + 1 < rows) edges.push({
10896
- a: i,
10897
- b: i + cols,
10898
- w: weightAt(i, i + cols)
10899
- });
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)"]
10900
12964
  }
10901
- return {
10902
- nodes,
10903
- edges
10904
- };
10905
- }
10906
- const wobble = (i) => i * 7919 % 11 / 10 - .5;
10907
- const plate = gridMesh(4, 3, (i) => `cell ${i}`, (a, b) => 1 + (a * 31 + b * 17) % 4, (i) => ({
10908
- dx: wobble(i) * 6,
10909
- dy: wobble(i + 3) * 6
10910
- }));
10911
- const reservoir = gridMesh(6, 3, (i) => `block ${i}`, (a, b) => 1 + (a * 13 + b * 29) % 5, (i) => ({
10912
- dx: wobble(i + 1) * 8,
10913
- dy: wobble(i + 5) * 8
10914
- }));
10915
- const blade = gridMesh(6, 4, (i) => `elem ${i}`, (a, b) => 1 + (a * 23 + b * 19) % 4, (i) => ({
10916
- dx: wobble(i + 2) * 7,
10917
- dy: wobble(i + 7) * 7
10918
- }));
10919
- plate.nodes, plate.edges, reservoir.nodes, reservoir.edges, blade.nodes, blade.edges;
12965
+ ];
10920
12966
  var QuboBuilder = class {
10921
12967
  size;
10922
12968
  q = /* @__PURE__ */ new Map();
@@ -11248,8 +13294,270 @@ function continuousToy() {
11248
13294
  };
11249
13295
  }
11250
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
+ };
11251
13493
  //#endregion
11252
- //#region ../../b4m-core/services/dist/tools-FLVnTwId.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
+ };
11253
13561
  async function performDeepResearch(context, params, config = {}) {
11254
13562
  const maxDepth = config.maxDepth || 7;
11255
13563
  const duration = config.duration || 4.5;
@@ -11610,9 +13918,10 @@ const deepResearchTool = {
11610
13918
  async function getDynamicDataLakeAccess(context) {
11611
13919
  const userTags = context.user.tags || [];
11612
13920
  const entitlementKeys = context.entitlementKeys ?? [];
13921
+ const organizationId = context.user.organizationId ? String(context.user.organizationId) : void 0;
11613
13922
  let dynamicDataLakes;
11614
13923
  if (context.db.dataLakes) try {
11615
- dynamicDataLakes = (await context.db.dataLakes.findActiveByUserTagsAndEntitlements(userTags, entitlementKeys)).map(toDataLakeConfig);
13924
+ dynamicDataLakes = (await context.db.dataLakes.findActiveByUserTagsAndEntitlements(userTags, entitlementKeys, organizationId)).map(toDataLakeConfig);
11616
13925
  } catch {}
11617
13926
  const accessibleLakes = getAccessibleDataLakes(userTags, dynamicDataLakes, entitlementKeys);
11618
13927
  return {
@@ -11760,6 +14069,60 @@ const weatherTool = {
11760
14069
  }
11761
14070
  })
11762
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
+ }
11763
14126
  async function downloadImage$1(url) {
11764
14127
  if (url.startsWith("data:image/")) {
11765
14128
  const base64Data = url.split(",")[1];
@@ -11793,8 +14156,15 @@ async function processAndStoreImages(images, context) {
11793
14156
  return Promise.all(images.map(async (image) => {
11794
14157
  const buffer = await downloadImage$1(image);
11795
14158
  const fileType = await fileTypeFromBuffer(buffer);
11796
- const filename = `${v4()}.${fileType?.ext}`;
11797
- 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;
11798
14168
  }));
11799
14169
  }
11800
14170
  async function updateQuestAndReturnMarkdown$1(storedImageUrls, context) {
@@ -15278,13 +17648,13 @@ const knowledgeBaseRetrieveTool = {
15278
17648
  function formatResult$1(result) {
15279
17649
  const lines = [
15280
17650
  `Solver: ${result.solverName} (${result.solverId})`,
15281
- `Makespan: ${result.makespan}`,
17651
+ `Makespan: ${result.objective}`,
15282
17652
  `Elapsed: ${result.elapsedMs.toFixed(1)}ms`
15283
17653
  ];
15284
17654
  if (result.iterations !== void 0) lines.push(`Iterations: ${result.iterations}`);
15285
17655
  lines.push("Schedule:");
15286
17656
  const byMachine = /* @__PURE__ */ new Map();
15287
- for (const op of result.schedule) {
17657
+ for (const op of result.solution) {
15288
17658
  if (!byMachine.has(op.machineId)) byMachine.set(op.machineId, []);
15289
17659
  byMachine.get(op.machineId).push(op);
15290
17660
  }
@@ -15321,13 +17691,13 @@ async function runQuantumSchedule(params) {
15321
17691
  }
15322
17692
  }
15323
17693
  if (results.length === 0) return `Error: No solvers completed successfully.\n${errors.join("\n")}`;
15324
- results.sort((a, b) => a.makespan - b.makespan);
17694
+ results.sort((a, b) => a.objective - b.objective);
15325
17695
  const lines = [];
15326
17696
  lines.push(`## Scheduling Results for "${problem.name}"`);
15327
17697
  lines.push(`Problem: ${problem.jobs.length} jobs, ${problem.machines.length} machines, ${problem.jobs.reduce((s, j) => s + j.operations.length, 0)} operations`);
15328
17698
  lines.push("");
15329
17699
  if (results.length > 1) {
15330
- lines.push(`### Winner: ${results[0].solverName} (makespan: ${results[0].makespan})`);
17700
+ lines.push(`### Winner: ${results[0].solverName} (makespan: ${results[0].objective})`);
15331
17701
  lines.push("");
15332
17702
  }
15333
17703
  for (const result of results) {
@@ -15444,37 +17814,92 @@ The problem must define jobs (each with ordered operations) and machines. Each o
15444
17814
  }
15445
17815
  })
15446
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
+ }
15447
17865
  const quantumFormulateTool = {
15448
17866
  name: "quantum_formulate",
15449
17867
  implementation: (context) => ({
15450
17868
  toolFn: async (value) => {
15451
- const userDescription = value.description?.trim();
15452
- 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;
15453
17874
  try {
15454
- let rawContent = "";
15455
- await context.llm.complete(context.model ?? "gpt-4", [{
15456
- role: "system",
15457
- content: PROBLEM_FORMULATION_PROMPT
15458
- }, {
15459
- role: "user",
15460
- content: userDescription
15461
- }], {
15462
- temperature: .2,
15463
- stream: false
15464
- }, async (texts) => {
15465
- rawContent = texts.filter((t) => t !== null && t !== void 0).join("");
15466
- });
15467
- let jsonStr = rawContent.trim();
15468
- const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
15469
- if (codeBlockMatch) jsonStr = codeBlockMatch[1].trim();
15470
- let parsed;
15471
- try {
15472
- parsed = JSON.parse(jsonStr);
15473
- } catch {
15474
- 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
+ });
15475
17898
  }
15476
- const validated = SchedulingProblemSchema.safeParse(parsed);
15477
- 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.`;
15478
17903
  const problem = validated.data;
15479
17904
  const totalOps = problem.jobs.reduce((s, j) => s + j.operations.length, 0);
15480
17905
  const humanReadableSummary = [
@@ -15509,21 +17934,28 @@ const quantumFormulateTool = {
15509
17934
  },
15510
17935
  toolSchema: {
15511
17936
  name: "quantum_formulate",
15512
- 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.
15513
17938
 
15514
- 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).
15515
17943
 
15516
- Examples:
15517
- - "I have 3 orders to manufacture. Each needs cutting, welding, and painting. Cutting takes 2h, welding 3h, painting 1h."
15518
- - "Schedule 4 software builds across 2 CI runners. Build A needs compile (5min) then test (3min)..."
15519
- - "A bakery needs to make 5 cakes. Each cake goes through mixing, baking, and decorating on separate stations."`,
15520
- 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,
15521
17946
  parameters: {
15522
17947
  type: "object",
15523
- properties: { description: {
15524
- type: "string",
15525
- description: "Natural language description of the scheduling problem to formulate"
15526
- } },
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
+ },
15527
17959
  required: ["description"],
15528
17960
  additionalProperties: false
15529
17961
  }
@@ -15837,6 +18269,7 @@ Please generate a complete, well-structured notebook that performs this analysis
15837
18269
  }
15838
18270
  })
15839
18271
  };
18272
+ const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
15840
18273
  const LIMITS = {
15841
18274
  MAX_SHEETS: 10,
15842
18275
  MAX_CELLS_PER_SHEET: 1e4,
@@ -16155,9 +18588,14 @@ const excelGenerationTool = {
16155
18588
  const sanitizedName = sanitizeFilename(params.filename);
16156
18589
  const filename = `${sanitizedName}-${v4().slice(0, 8)}.xlsx`;
16157
18590
  await context.imageGenerateStorage.upload(buffer, filename, {
16158
- ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
18591
+ ContentType: XLSX_MIME,
16159
18592
  ContentDisposition: `attachment; filename="${sanitizedName}.xlsx"`
16160
18593
  });
18594
+ await persistGeneratedFileAsFabFile(context, {
18595
+ fileName: `${sanitizedName}.xlsx`,
18596
+ mimeType: XLSX_MIME,
18597
+ content: buffer
18598
+ });
16161
18599
  await context.onFinish?.("excel_generation", [filename]);
16162
18600
  await context.statusUpdate({ images: [filename] });
16163
18601
  return `Successfully generated Excel file: ${sanitizedName}.xlsx`;
@@ -17443,10 +19881,11 @@ const b4mTools = {
17443
19881
  fmp_financial_data: fmpTool,
17444
19882
  skill: skillTool
17445
19883
  };
17446
- 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) => {
17447
19885
  const context = {
17448
19886
  userId,
17449
19887
  user,
19888
+ sessionId,
17450
19889
  logger,
17451
19890
  db,
17452
19891
  storage,
@@ -18467,7 +20906,11 @@ var ReActAgent = class extends EventEmitter {
18467
20906
  * This can be overridden in the context or customized here
18468
20907
  */
18469
20908
  getSystemPrompt() {
18470
- 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() {
18471
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.
18472
20915
 
18473
20916
  CORE PRINCIPLES:
@@ -19090,7 +21533,7 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
19090
21533
  })}</function>`;
19091
21534
  }
19092
21535
  }
19093
- 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.`;
19094
21537
  }
19095
21538
  try {
19096
21539
  const params = this.parseToolArguments(toolUse.arguments);
@@ -20458,33 +22901,6 @@ z.object({
20458
22901
  fabFileId: z.string(),
20459
22902
  chunkId: z.string()
20460
22903
  });
20461
- z.object({
20462
- fileName: z.string(),
20463
- mimeType: z.string(),
20464
- fileSize: z.number(),
20465
- type: z.enum(KnowledgeType),
20466
- content: z.union([z.string(), z.instanceof(Buffer)]).optional(),
20467
- organizationId: z.string().optional(),
20468
- /**
20469
- * Content type of the file
20470
- * @example 'text/markdown'
20471
- * @example 'application/pdf'
20472
- * @example 'application/octet-stream' for binary files
20473
- */
20474
- contentType: z.string().optional(),
20475
- public: z.boolean().optional(),
20476
- prefix: z.string().optional(),
20477
- system: z.boolean().optional(),
20478
- tags: z.array(z.object({
20479
- name: z.string(),
20480
- strength: z.number()
20481
- })).optional(),
20482
- systemPriority: z.number().optional(),
20483
- sessionId: z.string().optional(),
20484
- contentHash: z.string().optional(),
20485
- batchId: z.string().optional(),
20486
- relativePath: z.string().optional()
20487
- });
20488
22904
  z.object({ sessionId: z.string() });
20489
22905
  z.object({ questId: z.string() });
20490
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,})).+/) });