@bike4mind/cli 0.18.0 → 0.18.2
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.
- package/dist/{BackgroundAgentManager-ChmoEU9H.mjs → BackgroundAgentManager-d_iU7ati.mjs} +3151 -269
- package/dist/{ConfigStore-yJU1dxpP.mjs → ConfigStore-DL7p3ZiY.mjs} +281 -29
- package/dist/commands/apiCommand.mjs +1 -1
- package/dist/commands/doctorCommand.mjs +1 -1
- package/dist/commands/envCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +2 -2
- package/dist/commands/mcpCommand.mjs +1 -1
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/index.mjs +3 -3
- package/dist/{package-B3f78bSl.mjs → package-WpJJMKy_.mjs} +1 -1
- package/package.json +12 -12
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as PermissionDeniedError, $t as
|
|
2
|
+
import { $ as PermissionDeniedError, $t as wrapUntrustedSkillBody, A as FriendshipEvents, At as XAI_IMAGE_MODELS, B as InboxEvents, Bt as isSupportedEmbeddingModel, 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 resolveNavigationIntents, H as InviteEvents, Ht as mapMimeTypeToArtifactType, I as HttpStatus, It as intersectAllowedTools, J as ModelBackend, Jt as settingsMap, K as MiscEvents, Kt as sanitizeTelemetryError, L as ImageEditUsageTransaction, Lt as isGPTImage2Model, 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, R as ImageGenerationUsageTransaction, Rt as isGPTImageModel, S as CompletionApiUsageTransaction, St as ToolUsageTransaction, T as ElabsEvents, Tt as UnauthorizedError, U as InviteType, Ut as obfuscateApiKey, V as InternalServerError, Vt as isZodError, W as KnowledgeType, Wt as parseSkillArguments, X as OpenAIEmbeddingModel, Xt as toDataLakeConfig, Y as NotFoundError, Yt as substituteArguments$1, 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 buildRateLimitLogEntry, 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, in as CollectionType, 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 isNearLimit, nt as PromptIntentSchema, ot as RealtimeVoiceUsageTransaction, p as ArtifactTypeSchema, pt as ResearchTaskType, q as ModalEvents, qt as secureParameters, rn as parseRateLimitHeaders, rt as PromptMetaZodSchema, s as ALERT_THRESHOLDS, st as ReceivedCreditTransaction, t as ConfigStore, tn as extractSnippetMeta, 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 isModelAccessible } from "./ConfigStore-DL7p3ZiY.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-
|
|
5
|
+
import { t as version } from "./package-WpJJMKy_.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";
|
|
@@ -7086,7 +7086,9 @@ function shouldTriggerFallback(error) {
|
|
|
7086
7086
|
"api key",
|
|
7087
7087
|
"quota exceeded",
|
|
7088
7088
|
"model not found",
|
|
7089
|
-
"does not exist"
|
|
7089
|
+
"does not exist",
|
|
7090
|
+
"is not available",
|
|
7091
|
+
"safety classifier refusal"
|
|
7090
7092
|
].some((trigger) => message.includes(trigger));
|
|
7091
7093
|
}
|
|
7092
7094
|
/**
|
|
@@ -7149,17 +7151,20 @@ function findAutomaticFallback(originalModel, availableModels, apiKeyTable, logg
|
|
|
7149
7151
|
"claude-opus-4-8",
|
|
7150
7152
|
"claude-opus-4-7",
|
|
7151
7153
|
"claude-opus-4-6",
|
|
7154
|
+
"claude-sonnet-5",
|
|
7152
7155
|
"claude-sonnet-4-6",
|
|
7153
7156
|
"gpt-5"
|
|
7154
7157
|
],
|
|
7155
7158
|
"claude-opus-4-8": [
|
|
7156
7159
|
"claude-opus-4-7",
|
|
7157
7160
|
"claude-opus-4-6",
|
|
7161
|
+
"claude-sonnet-5",
|
|
7158
7162
|
"claude-sonnet-4-6",
|
|
7159
7163
|
"gpt-5"
|
|
7160
7164
|
],
|
|
7161
7165
|
"claude-opus-4-7": [
|
|
7162
7166
|
"claude-opus-4-6",
|
|
7167
|
+
"claude-sonnet-5",
|
|
7163
7168
|
"claude-sonnet-4-6",
|
|
7164
7169
|
"gpt-5"
|
|
7165
7170
|
],
|
|
@@ -7170,17 +7175,26 @@ function findAutomaticFallback(originalModel, availableModels, apiKeyTable, logg
|
|
|
7170
7175
|
"claude-haiku-4-5-20251001"
|
|
7171
7176
|
],
|
|
7172
7177
|
"claude-opus-4-6": [
|
|
7178
|
+
"claude-sonnet-5",
|
|
7173
7179
|
"claude-sonnet-4-6",
|
|
7174
7180
|
"claude-sonnet-4-5-20250929",
|
|
7175
7181
|
"gpt-5",
|
|
7176
7182
|
"claude-haiku-4-5-20251001"
|
|
7177
7183
|
],
|
|
7184
|
+
"claude-sonnet-5": [
|
|
7185
|
+
"claude-sonnet-4-6",
|
|
7186
|
+
"claude-sonnet-4-5-20250929",
|
|
7187
|
+
"gpt-5"
|
|
7188
|
+
],
|
|
7189
|
+
"global.anthropic.claude-sonnet-5": ["global.anthropic.claude-sonnet-4-6", "gpt-5"],
|
|
7178
7190
|
"claude-sonnet-4-6": [
|
|
7191
|
+
"claude-sonnet-5",
|
|
7179
7192
|
"claude-sonnet-4-5-20250929",
|
|
7180
7193
|
"claude-opus-4-5-20251101",
|
|
7181
7194
|
"gpt-5"
|
|
7182
7195
|
],
|
|
7183
7196
|
"claude-sonnet-4-5-20250929": [
|
|
7197
|
+
"claude-sonnet-5",
|
|
7184
7198
|
"claude-sonnet-4-6",
|
|
7185
7199
|
"claude-haiku-4-5-20251001",
|
|
7186
7200
|
"gpt-5"
|
|
@@ -7212,7 +7226,7 @@ function findAutomaticFallback(originalModel, availableModels, apiKeyTable, logg
|
|
|
7212
7226
|
],
|
|
7213
7227
|
"gpt-4o-mini": ["claude-haiku-4-5-20251001", "gpt-3.5-turbo"]
|
|
7214
7228
|
}[originalModel.id] || [];
|
|
7215
|
-
if (preferences.length === 0) preferences.push("claude-sonnet-4-6", "claude-sonnet-4-5-20250929", "gpt-5", "claude-haiku-4-5-20251001");
|
|
7229
|
+
if (preferences.length === 0) preferences.push("claude-sonnet-5", "claude-sonnet-4-6", "claude-sonnet-4-5-20250929", "gpt-5", "claude-haiku-4-5-20251001");
|
|
7216
7230
|
for (const modelId of preferences) {
|
|
7217
7231
|
const fallbackModel = availableModels.find((m) => m.id === modelId);
|
|
7218
7232
|
if (fallbackModel) {
|
|
@@ -7869,7 +7883,7 @@ function classifyError(error) {
|
|
|
7869
7883
|
return "unhandled_rejection";
|
|
7870
7884
|
}
|
|
7871
7885
|
/**
|
|
7872
|
-
* Builds a structured log entry for
|
|
7886
|
+
* Builds a structured log entry for the global error handlers.
|
|
7873
7887
|
*/
|
|
7874
7888
|
function buildLogEntry(error, category) {
|
|
7875
7889
|
return {
|
|
@@ -7884,29 +7898,33 @@ function buildLogEntry(error, category) {
|
|
|
7884
7898
|
/**
|
|
7885
7899
|
* Registers global handlers for unhandled promise rejections and uncaught exceptions.
|
|
7886
7900
|
*
|
|
7887
|
-
*
|
|
7888
|
-
*
|
|
7889
|
-
* 1. Log errors with structured context
|
|
7901
|
+
* Provider-agnostic core shared by Lambda handlers and the always-on
|
|
7902
|
+
* QuestProcessorService container. The handlers:
|
|
7903
|
+
* 1. Log errors with structured context
|
|
7890
7904
|
* 2. Classify error types for easier debugging
|
|
7891
|
-
* 3. Distinguish network errors from application errors
|
|
7905
|
+
* 3. Distinguish network errors from application errors and swallow the network
|
|
7906
|
+
* class (mark the rejection handled / don't re-throw) so a single transient
|
|
7907
|
+
* network failure doesn't escalate
|
|
7892
7908
|
*
|
|
7893
|
-
*
|
|
7909
|
+
* CRITICAL for long-running processes: these handlers NEVER call process.exit().
|
|
7910
|
+
* On AWS Lambda that lets the runtime own the lifecycle. On a long-running Fargate
|
|
7911
|
+
* task it means one quest's mid-stream EPIPE / orphaned undici rejection (#8055,
|
|
7912
|
+
* #8197) is logged and swallowed instead of taking Node's default
|
|
7913
|
+
* `uncaughtException` path, which would terminate the whole container and kill
|
|
7914
|
+
* every other in-flight quest.
|
|
7894
7915
|
*
|
|
7895
7916
|
* @param logger - Optional logger instance. Falls back to console if not provided.
|
|
7917
|
+
* @param context - Short label used in log message prefixes (default 'Process').
|
|
7896
7918
|
*
|
|
7897
7919
|
* @example
|
|
7898
7920
|
* ```typescript
|
|
7899
|
-
* import {
|
|
7921
|
+
* import { registerProcessErrorHandlers } from '@bike4mind/utils';
|
|
7900
7922
|
*
|
|
7901
|
-
* // Call at
|
|
7902
|
-
*
|
|
7903
|
-
*
|
|
7904
|
-
* export const handler = async (event) => {
|
|
7905
|
-
* // Your handler code
|
|
7906
|
-
* };
|
|
7923
|
+
* // Call once at startup, after imports
|
|
7924
|
+
* registerProcessErrorHandlers(logger, 'QuestProcessorService');
|
|
7907
7925
|
* ```
|
|
7908
7926
|
*/
|
|
7909
|
-
function
|
|
7927
|
+
function registerProcessErrorHandlers(logger, context = "Process") {
|
|
7910
7928
|
if (isRegistered) return;
|
|
7911
7929
|
isRegistered = true;
|
|
7912
7930
|
const log = logger || console;
|
|
@@ -7915,17 +7933,27 @@ function registerLambdaErrorHandlers(logger) {
|
|
|
7915
7933
|
const isNetworkError = category.startsWith("network_");
|
|
7916
7934
|
const logEntry = buildLogEntry(reason, category);
|
|
7917
7935
|
if (isNetworkError) {
|
|
7918
|
-
log.warn(
|
|
7936
|
+
log.warn(`[${context}] Network error (unhandled rejection)`, logEntry);
|
|
7919
7937
|
promise.catch(() => {});
|
|
7920
|
-
} else log.error(
|
|
7938
|
+
} else log.error(`[${context}] Unhandled promise rejection`, logEntry);
|
|
7921
7939
|
});
|
|
7922
7940
|
process.on("uncaughtException", (error) => {
|
|
7923
7941
|
const category = classifyError(error);
|
|
7924
|
-
if (category.startsWith("network_")) log.warn(
|
|
7925
|
-
else log.error(
|
|
7942
|
+
if (category.startsWith("network_")) log.warn(`[${context}] Network error (uncaught exception)`, buildLogEntry(error, category));
|
|
7943
|
+
else log.error(`[${context}] Uncaught exception`, buildLogEntry(error, "uncaught_exception"));
|
|
7926
7944
|
});
|
|
7927
7945
|
}
|
|
7928
7946
|
/**
|
|
7947
|
+
* Back-compat alias for Lambda handlers. Preserves the historical `[Lambda]` log
|
|
7948
|
+
* prefix so existing log-based filters/alerts are unaffected. New code on
|
|
7949
|
+
* non-Lambda runtimes should call {@link registerProcessErrorHandlers} directly.
|
|
7950
|
+
*
|
|
7951
|
+
* @param logger - Optional logger instance. Falls back to console if not provided.
|
|
7952
|
+
*/
|
|
7953
|
+
function registerLambdaErrorHandlers(logger) {
|
|
7954
|
+
registerProcessErrorHandlers(logger, "Lambda");
|
|
7955
|
+
}
|
|
7956
|
+
/**
|
|
7929
7957
|
* Reset the registration state. Only for testing purposes.
|
|
7930
7958
|
* @internal
|
|
7931
7959
|
*/
|
|
@@ -8055,6 +8083,7 @@ __reExport(/* @__PURE__ */ __exportAll$1({
|
|
|
8055
8083
|
processFabFilesServer: () => processFabFilesServer,
|
|
8056
8084
|
processUrlsFromPrompt: () => processUrlsFromPrompt,
|
|
8057
8085
|
registerLambdaErrorHandlers: () => registerLambdaErrorHandlers,
|
|
8086
|
+
registerProcessErrorHandlers: () => registerProcessErrorHandlers,
|
|
8058
8087
|
secureParameters: () => secureParameters,
|
|
8059
8088
|
sendToConnection: () => sendToConnection,
|
|
8060
8089
|
shouldTriggerFallback: () => shouldTriggerFallback,
|
|
@@ -8876,22 +8905,22 @@ function makeSchedulingResult(args) {
|
|
|
8876
8905
|
const OperationSchema = z.object({
|
|
8877
8906
|
jobId: z.number(),
|
|
8878
8907
|
machineId: z.number(),
|
|
8879
|
-
duration: z.number().positive()
|
|
8908
|
+
duration: z.number().positive().max(1e6)
|
|
8880
8909
|
});
|
|
8881
8910
|
const JobSchema = z.object({
|
|
8882
8911
|
id: z.number(),
|
|
8883
|
-
name: z.string(),
|
|
8884
|
-
operations: z.array(OperationSchema)
|
|
8912
|
+
name: z.string().max(200),
|
|
8913
|
+
operations: z.array(OperationSchema).max(1e3)
|
|
8885
8914
|
});
|
|
8886
8915
|
const MachineSchema = z.object({
|
|
8887
8916
|
id: z.number(),
|
|
8888
|
-
name: z.string()
|
|
8917
|
+
name: z.string().max(200)
|
|
8889
8918
|
});
|
|
8890
8919
|
const SchedulingProblemSchema = z.object({
|
|
8891
|
-
name: z.string(),
|
|
8892
|
-
description: z.string().optional(),
|
|
8893
|
-
jobs: z.array(JobSchema),
|
|
8894
|
-
machines: z.array(MachineSchema),
|
|
8920
|
+
name: z.string().max(200),
|
|
8921
|
+
description: z.string().max(2e3).optional(),
|
|
8922
|
+
jobs: z.array(JobSchema).max(1e3),
|
|
8923
|
+
machines: z.array(MachineSchema).max(1e3),
|
|
8895
8924
|
solvedJobUrl: z.string().url().optional()
|
|
8896
8925
|
});
|
|
8897
8926
|
const SolverIdSchema = z.enum([
|
|
@@ -8955,13 +8984,18 @@ const greedySolver = {
|
|
|
8955
8984
|
totalIterations: totalOps
|
|
8956
8985
|
});
|
|
8957
8986
|
}
|
|
8987
|
+
const makespan = Math.max(...schedule.map((op) => op.endTime));
|
|
8958
8988
|
return makeSchedulingResult({
|
|
8959
8989
|
solverId: "greedy",
|
|
8960
8990
|
solverName: "Greedy (Earliest Start)",
|
|
8961
|
-
makespan
|
|
8991
|
+
makespan,
|
|
8962
8992
|
schedule,
|
|
8963
8993
|
elapsedMs: performance.now() - startTime,
|
|
8964
|
-
iterations: totalOps
|
|
8994
|
+
iterations: totalOps,
|
|
8995
|
+
trace: [{
|
|
8996
|
+
solution: schedule,
|
|
8997
|
+
objective: makespan
|
|
8998
|
+
}]
|
|
8965
8999
|
});
|
|
8966
9000
|
}
|
|
8967
9001
|
};
|
|
@@ -9092,6 +9126,34 @@ function validateScheduleFeasibility(schedule) {
|
|
|
9092
9126
|
return true;
|
|
9093
9127
|
}
|
|
9094
9128
|
/**
|
|
9129
|
+
* Append a best-so-far snapshot, thinning the early history when over cap so the
|
|
9130
|
+
* trace stays bounded while preserving the first point and recent progress.
|
|
9131
|
+
* (Same thinning policy as the family engine's pushTrace.)
|
|
9132
|
+
*/
|
|
9133
|
+
function pushScheduleTrace(trace, schedule, makespan) {
|
|
9134
|
+
trace.push({
|
|
9135
|
+
solution: schedule,
|
|
9136
|
+
objective: makespan
|
|
9137
|
+
});
|
|
9138
|
+
if (trace.length > 48) {
|
|
9139
|
+
const first = trace[0];
|
|
9140
|
+
const rest = trace.slice(1).filter((_, i) => i % 2 === 1);
|
|
9141
|
+
trace.length = 0;
|
|
9142
|
+
trace.push(first, ...rest);
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
9145
|
+
const nowMs = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
9146
|
+
/**
|
|
9147
|
+
* Combine the timeoutMs budget with cooperative cancellation into one stop check
|
|
9148
|
+
* that iterative solvers poll between moves — so a UI cancel or a timeout actually
|
|
9149
|
+
* halts a long run instead of being decorative. `defaultTimeoutMs` preserves each
|
|
9150
|
+
* solver's historical default when the caller omits timeoutMs.
|
|
9151
|
+
*/
|
|
9152
|
+
function makeScheduleStopper(options, startTime, defaultTimeoutMs) {
|
|
9153
|
+
const deadline = startTime + (options?.timeoutMs ?? defaultTimeoutMs);
|
|
9154
|
+
return () => (options?.shouldCancel?.() ?? false) || nowMs() > deadline;
|
|
9155
|
+
}
|
|
9156
|
+
/**
|
|
9095
9157
|
* Simple seeded pseudo-random number generator (mulberry32).
|
|
9096
9158
|
*/
|
|
9097
9159
|
function createRng$1(seed) {
|
|
@@ -9127,19 +9189,25 @@ const naiveSolver = {
|
|
|
9127
9189
|
description: "Evaluates all permutations to find the optimal schedule. Only feasible for small problems.",
|
|
9128
9190
|
async solve(problem, options) {
|
|
9129
9191
|
const startTime = performance.now();
|
|
9192
|
+
const stop = makeScheduleStopper(options, startTime, 15e3);
|
|
9130
9193
|
const ops = buildOperationList(problem);
|
|
9131
9194
|
const totalPerms = factorial(ops.length);
|
|
9132
9195
|
const cappedTotal = Math.min(totalPerms, MAX_PERMUTATIONS);
|
|
9133
|
-
let bestMakespan = Infinity;
|
|
9134
9196
|
let bestSchedule = decodePermutation(ops, problem);
|
|
9197
|
+
let bestMakespan = computeMakespan(bestSchedule);
|
|
9135
9198
|
let count = 0;
|
|
9199
|
+
const trace = [{
|
|
9200
|
+
solution: bestSchedule,
|
|
9201
|
+
objective: bestMakespan
|
|
9202
|
+
}];
|
|
9136
9203
|
for (const perm of permutations(ops)) {
|
|
9137
|
-
if (count >= MAX_PERMUTATIONS) break;
|
|
9204
|
+
if (count >= MAX_PERMUTATIONS || stop()) break;
|
|
9138
9205
|
const schedule = decodePermutation(perm, problem);
|
|
9139
9206
|
const makespan = computeMakespan(schedule);
|
|
9140
9207
|
if (makespan < bestMakespan) {
|
|
9141
9208
|
bestMakespan = makespan;
|
|
9142
9209
|
bestSchedule = schedule;
|
|
9210
|
+
pushScheduleTrace(trace, bestSchedule, bestMakespan);
|
|
9143
9211
|
}
|
|
9144
9212
|
count++;
|
|
9145
9213
|
if (options?.onProgress && count % 100 === 0) options.onProgress({
|
|
@@ -9163,7 +9231,8 @@ const naiveSolver = {
|
|
|
9163
9231
|
makespan: bestMakespan,
|
|
9164
9232
|
schedule: bestSchedule,
|
|
9165
9233
|
elapsedMs: performance.now() - startTime,
|
|
9166
|
-
iterations: count
|
|
9234
|
+
iterations: count,
|
|
9235
|
+
trace
|
|
9167
9236
|
});
|
|
9168
9237
|
}
|
|
9169
9238
|
};
|
|
@@ -9188,18 +9257,22 @@ const randomRestartSolver = {
|
|
|
9188
9257
|
description: "Multiple random starts with local search improvement via operation swaps.",
|
|
9189
9258
|
async solve(problem, options) {
|
|
9190
9259
|
const startTime = performance.now();
|
|
9191
|
-
const
|
|
9260
|
+
const stop = makeScheduleStopper(options, startTime, 1e4);
|
|
9192
9261
|
const rng = createRng$1(42);
|
|
9193
|
-
let globalBestMakespan = Infinity;
|
|
9194
9262
|
let globalBestSchedule = decodePermutation(randomPermutation(problem, rng), problem);
|
|
9263
|
+
let globalBestMakespan = computeMakespan(globalBestSchedule);
|
|
9195
9264
|
let totalIterations = 0;
|
|
9265
|
+
const trace = [{
|
|
9266
|
+
solution: globalBestSchedule,
|
|
9267
|
+
objective: globalBestMakespan
|
|
9268
|
+
}];
|
|
9196
9269
|
for (let restart = 0; restart < RESTARTS; restart++) {
|
|
9197
|
-
if (
|
|
9270
|
+
if (stop()) break;
|
|
9198
9271
|
let currentPerm = randomPermutation(problem, rng);
|
|
9199
9272
|
let currentSchedule = decodePermutation(currentPerm, problem);
|
|
9200
9273
|
let currentMakespan = computeMakespan(currentSchedule);
|
|
9201
9274
|
for (let i = 0; i < HILL_CLIMB_ITERATIONS; i++) {
|
|
9202
|
-
if (
|
|
9275
|
+
if (stop()) break;
|
|
9203
9276
|
const neighborPerm = swapNeighbor(currentPerm, rng);
|
|
9204
9277
|
const neighborSchedule = decodePermutation(neighborPerm, problem);
|
|
9205
9278
|
const neighborMakespan = computeMakespan(neighborSchedule);
|
|
@@ -9213,6 +9286,7 @@ const randomRestartSolver = {
|
|
|
9213
9286
|
if (currentMakespan < globalBestMakespan) {
|
|
9214
9287
|
globalBestMakespan = currentMakespan;
|
|
9215
9288
|
globalBestSchedule = currentSchedule;
|
|
9289
|
+
pushScheduleTrace(trace, globalBestSchedule, globalBestMakespan);
|
|
9216
9290
|
}
|
|
9217
9291
|
options?.onProgress?.({
|
|
9218
9292
|
solverId: "random-restart",
|
|
@@ -9228,7 +9302,8 @@ const randomRestartSolver = {
|
|
|
9228
9302
|
makespan: globalBestMakespan,
|
|
9229
9303
|
schedule: globalBestSchedule,
|
|
9230
9304
|
elapsedMs: performance.now() - startTime,
|
|
9231
|
-
iterations: totalIterations
|
|
9305
|
+
iterations: totalIterations,
|
|
9306
|
+
trace
|
|
9232
9307
|
});
|
|
9233
9308
|
}
|
|
9234
9309
|
};
|
|
@@ -9265,17 +9340,21 @@ function createSASolver(config) {
|
|
|
9265
9340
|
description: config.description,
|
|
9266
9341
|
async solve(problem, options) {
|
|
9267
9342
|
const startTime = performance.now();
|
|
9268
|
-
const
|
|
9343
|
+
const stop = makeScheduleStopper(options, startTime, 3e4);
|
|
9269
9344
|
const rng = createRng$1(42);
|
|
9270
9345
|
let currentPerm = randomPermutation(problem, rng);
|
|
9271
9346
|
let currentSchedule = decodePermutation(currentPerm, problem);
|
|
9272
9347
|
let currentMakespan = computeMakespan(currentSchedule);
|
|
9273
9348
|
let bestSchedule = currentSchedule;
|
|
9274
9349
|
let bestMakespan = currentMakespan;
|
|
9350
|
+
const trace = [{
|
|
9351
|
+
solution: bestSchedule,
|
|
9352
|
+
objective: bestMakespan
|
|
9353
|
+
}];
|
|
9275
9354
|
let temperature = config.initialTemp;
|
|
9276
9355
|
const progressInterval = Math.max(1, Math.floor(config.iterations / 100));
|
|
9277
9356
|
for (let i = 0; i < config.iterations; i++) {
|
|
9278
|
-
if (
|
|
9357
|
+
if (stop()) break;
|
|
9279
9358
|
const neighborPerm = swapNeighbor(currentPerm, rng);
|
|
9280
9359
|
const neighborSchedule = decodePermutation(neighborPerm, problem);
|
|
9281
9360
|
const neighborMakespan = computeMakespan(neighborSchedule);
|
|
@@ -9287,6 +9366,7 @@ function createSASolver(config) {
|
|
|
9287
9366
|
if (currentMakespan < bestMakespan) {
|
|
9288
9367
|
bestSchedule = currentSchedule;
|
|
9289
9368
|
bestMakespan = currentMakespan;
|
|
9369
|
+
pushScheduleTrace(trace, bestSchedule, bestMakespan);
|
|
9290
9370
|
}
|
|
9291
9371
|
}
|
|
9292
9372
|
temperature *= config.coolingRate;
|
|
@@ -9311,7 +9391,8 @@ function createSASolver(config) {
|
|
|
9311
9391
|
makespan: bestMakespan,
|
|
9312
9392
|
schedule: bestSchedule,
|
|
9313
9393
|
elapsedMs: performance.now() - startTime,
|
|
9314
|
-
iterations: config.iterations
|
|
9394
|
+
iterations: config.iterations,
|
|
9395
|
+
trace
|
|
9315
9396
|
});
|
|
9316
9397
|
}
|
|
9317
9398
|
};
|
|
@@ -9326,7 +9407,7 @@ const simulatedAnnealingLargeSolver = createSASolver(SA_CONFIGS[2]);
|
|
|
9326
9407
|
* forbidden for a period. This prevents cycling and forces exploration
|
|
9327
9408
|
* of new regions of the solution space.
|
|
9328
9409
|
*/
|
|
9329
|
-
const ITERATIONS$
|
|
9410
|
+
const ITERATIONS$2 = 1e4;
|
|
9330
9411
|
const TABU_TENURE = 15;
|
|
9331
9412
|
const NEIGHBORHOOD_SIZE = 20;
|
|
9332
9413
|
const tabuSolver = {
|
|
@@ -9335,17 +9416,21 @@ const tabuSolver = {
|
|
|
9335
9416
|
description: "Hill climbing with short-term memory to avoid revisiting recent solutions.",
|
|
9336
9417
|
async solve(problem, options) {
|
|
9337
9418
|
const startTime = performance.now();
|
|
9338
|
-
const
|
|
9419
|
+
const stop = makeScheduleStopper(options, startTime, 15e3);
|
|
9339
9420
|
const rng = createRng$1(42);
|
|
9340
9421
|
let currentPerm = randomPermutation(problem, rng);
|
|
9341
9422
|
let currentSchedule = decodePermutation(currentPerm, problem);
|
|
9342
9423
|
let currentMakespan = computeMakespan(currentSchedule);
|
|
9343
9424
|
let bestSchedule = currentSchedule;
|
|
9344
9425
|
let bestMakespan = currentMakespan;
|
|
9426
|
+
const trace = [{
|
|
9427
|
+
solution: bestSchedule,
|
|
9428
|
+
objective: bestMakespan
|
|
9429
|
+
}];
|
|
9345
9430
|
const tabuList = /* @__PURE__ */ new Map();
|
|
9346
|
-
const progressInterval = Math.max(1, Math.floor(ITERATIONS$
|
|
9347
|
-
for (let iter = 0; iter < ITERATIONS$
|
|
9348
|
-
if (
|
|
9431
|
+
const progressInterval = Math.max(1, Math.floor(ITERATIONS$2 / 100));
|
|
9432
|
+
for (let iter = 0; iter < ITERATIONS$2; iter++) {
|
|
9433
|
+
if (stop()) break;
|
|
9349
9434
|
let bestNeighborPerm = null;
|
|
9350
9435
|
let bestNeighborMakespan = Infinity;
|
|
9351
9436
|
let bestSwapKey = "";
|
|
@@ -9371,6 +9456,7 @@ const tabuSolver = {
|
|
|
9371
9456
|
if (currentMakespan < bestMakespan) {
|
|
9372
9457
|
bestMakespan = currentMakespan;
|
|
9373
9458
|
bestSchedule = currentSchedule;
|
|
9459
|
+
pushScheduleTrace(trace, bestSchedule, bestMakespan);
|
|
9374
9460
|
}
|
|
9375
9461
|
}
|
|
9376
9462
|
if (iter % 100 === 0) {
|
|
@@ -9378,18 +9464,18 @@ const tabuSolver = {
|
|
|
9378
9464
|
}
|
|
9379
9465
|
if (options?.onProgress && iter % progressInterval === 0) options.onProgress({
|
|
9380
9466
|
solverId: "tabu",
|
|
9381
|
-
percentage: Math.round(iter / ITERATIONS$
|
|
9467
|
+
percentage: Math.round(iter / ITERATIONS$2 * 100),
|
|
9382
9468
|
bestObjective: bestMakespan,
|
|
9383
9469
|
currentIteration: iter,
|
|
9384
|
-
totalIterations: ITERATIONS$
|
|
9470
|
+
totalIterations: ITERATIONS$2
|
|
9385
9471
|
});
|
|
9386
9472
|
}
|
|
9387
9473
|
options?.onProgress?.({
|
|
9388
9474
|
solverId: "tabu",
|
|
9389
9475
|
percentage: 100,
|
|
9390
9476
|
bestObjective: bestMakespan,
|
|
9391
|
-
currentIteration: ITERATIONS$
|
|
9392
|
-
totalIterations: ITERATIONS$
|
|
9477
|
+
currentIteration: ITERATIONS$2,
|
|
9478
|
+
totalIterations: ITERATIONS$2
|
|
9393
9479
|
});
|
|
9394
9480
|
return makeSchedulingResult({
|
|
9395
9481
|
solverId: "tabu",
|
|
@@ -9397,7 +9483,8 @@ const tabuSolver = {
|
|
|
9397
9483
|
makespan: bestMakespan,
|
|
9398
9484
|
schedule: bestSchedule,
|
|
9399
9485
|
elapsedMs: performance.now() - startTime,
|
|
9400
|
-
iterations: ITERATIONS$
|
|
9486
|
+
iterations: ITERATIONS$2,
|
|
9487
|
+
trace
|
|
9401
9488
|
});
|
|
9402
9489
|
}
|
|
9403
9490
|
};
|
|
@@ -9417,7 +9504,7 @@ const geneticAlgorithmSolver = {
|
|
|
9417
9504
|
description: "Population-based search with tournament selection, crossover, and mutation.",
|
|
9418
9505
|
async solve(problem, options) {
|
|
9419
9506
|
const startTime = performance.now();
|
|
9420
|
-
const
|
|
9507
|
+
const stop = makeScheduleStopper(options, startTime, 2e4);
|
|
9421
9508
|
const rng = createRng$1(42);
|
|
9422
9509
|
let population = [];
|
|
9423
9510
|
for (let i = 0; i < POPULATION_SIZE; i++) {
|
|
@@ -9429,8 +9516,12 @@ const geneticAlgorithmSolver = {
|
|
|
9429
9516
|
});
|
|
9430
9517
|
}
|
|
9431
9518
|
let bestIndividual = population.reduce((best, ind) => ind.makespan < best.makespan ? ind : best);
|
|
9519
|
+
const trace = [{
|
|
9520
|
+
solution: decodePermutation(bestIndividual.perm, problem),
|
|
9521
|
+
objective: bestIndividual.makespan
|
|
9522
|
+
}];
|
|
9432
9523
|
for (let gen = 0; gen < GENERATIONS; gen++) {
|
|
9433
|
-
if (
|
|
9524
|
+
if (stop()) break;
|
|
9434
9525
|
const newPopulation = [];
|
|
9435
9526
|
newPopulation.push({ ...bestIndividual });
|
|
9436
9527
|
while (newPopulation.length < POPULATION_SIZE) {
|
|
@@ -9446,7 +9537,10 @@ const geneticAlgorithmSolver = {
|
|
|
9446
9537
|
}
|
|
9447
9538
|
population = newPopulation;
|
|
9448
9539
|
const genBest = population.reduce((best, ind) => ind.makespan < best.makespan ? ind : best);
|
|
9449
|
-
if (genBest.makespan < bestIndividual.makespan)
|
|
9540
|
+
if (genBest.makespan < bestIndividual.makespan) {
|
|
9541
|
+
bestIndividual = genBest;
|
|
9542
|
+
pushScheduleTrace(trace, decodePermutation(bestIndividual.perm, problem), bestIndividual.makespan);
|
|
9543
|
+
}
|
|
9450
9544
|
options?.onProgress?.({
|
|
9451
9545
|
solverId: "genetic-algorithm",
|
|
9452
9546
|
percentage: Math.round((gen + 1) / GENERATIONS * 100),
|
|
@@ -9462,7 +9556,8 @@ const geneticAlgorithmSolver = {
|
|
|
9462
9556
|
makespan: bestIndividual.makespan,
|
|
9463
9557
|
schedule: bestSchedule,
|
|
9464
9558
|
elapsedMs: performance.now() - startTime,
|
|
9465
|
-
iterations: GENERATIONS * POPULATION_SIZE
|
|
9559
|
+
iterations: GENERATIONS * POPULATION_SIZE,
|
|
9560
|
+
trace
|
|
9466
9561
|
});
|
|
9467
9562
|
}
|
|
9468
9563
|
};
|
|
@@ -9507,10 +9602,10 @@ function crossover(parent1, parent2, rng) {
|
|
|
9507
9602
|
* assignments) that appear in good solutions and evaporate over time.
|
|
9508
9603
|
*/
|
|
9509
9604
|
const NUM_ANTS = 20;
|
|
9510
|
-
const ITERATIONS = 100;
|
|
9605
|
+
const ITERATIONS$1 = 100;
|
|
9511
9606
|
const ALPHA = 1;
|
|
9512
9607
|
const BETA = 2;
|
|
9513
|
-
const EVAPORATION = .1;
|
|
9608
|
+
const EVAPORATION$1 = .1;
|
|
9514
9609
|
const Q = 100;
|
|
9515
9610
|
const antColonySolver = {
|
|
9516
9611
|
id: "ant-colony",
|
|
@@ -9518,7 +9613,7 @@ const antColonySolver = {
|
|
|
9518
9613
|
description: "Swarm intelligence using pheromone trails to guide solution construction.",
|
|
9519
9614
|
async solve(problem, options) {
|
|
9520
9615
|
const startTime = performance.now();
|
|
9521
|
-
const
|
|
9616
|
+
const stop = makeScheduleStopper(options, startTime, 2e4);
|
|
9522
9617
|
const rng = createRng$1(42);
|
|
9523
9618
|
const allOps = buildOperationList(problem);
|
|
9524
9619
|
const numOps = allOps.length;
|
|
@@ -9526,10 +9621,14 @@ const antColonySolver = {
|
|
|
9526
9621
|
const heuristic = allOps.map((ref) => {
|
|
9527
9622
|
return 1 / problem.jobs.find((j) => j.id === ref.jobId).operations[ref.opIndex].duration;
|
|
9528
9623
|
});
|
|
9529
|
-
let bestMakespan = Infinity;
|
|
9530
9624
|
let bestSchedule = decodePermutation(allOps, problem);
|
|
9531
|
-
|
|
9532
|
-
|
|
9625
|
+
let bestMakespan = computeMakespan(bestSchedule);
|
|
9626
|
+
const trace = [{
|
|
9627
|
+
solution: bestSchedule,
|
|
9628
|
+
objective: bestMakespan
|
|
9629
|
+
}];
|
|
9630
|
+
for (let iter = 0; iter < ITERATIONS$1; iter++) {
|
|
9631
|
+
if (stop()) break;
|
|
9533
9632
|
const antSolutions = [];
|
|
9534
9633
|
for (let ant = 0; ant < NUM_ANTS; ant++) {
|
|
9535
9634
|
const perm = constructSolution(allOps, pheromone, heuristic, rng);
|
|
@@ -9542,9 +9641,10 @@ const antColonySolver = {
|
|
|
9542
9641
|
if (makespan < bestMakespan) {
|
|
9543
9642
|
bestMakespan = makespan;
|
|
9544
9643
|
bestSchedule = schedule;
|
|
9644
|
+
pushScheduleTrace(trace, bestSchedule, bestMakespan);
|
|
9545
9645
|
}
|
|
9546
9646
|
}
|
|
9547
|
-
for (let i = 0; i < numOps; i++) for (let j = 0; j < numOps; j++) pheromone[i][j] *= 1 - EVAPORATION;
|
|
9647
|
+
for (let i = 0; i < numOps; i++) for (let j = 0; j < numOps; j++) pheromone[i][j] *= 1 - EVAPORATION$1;
|
|
9548
9648
|
for (const solution of antSolutions) {
|
|
9549
9649
|
const deposit = Q / solution.makespan;
|
|
9550
9650
|
for (let pos = 0; pos < solution.perm.length; pos++) {
|
|
@@ -9554,10 +9654,10 @@ const antColonySolver = {
|
|
|
9554
9654
|
}
|
|
9555
9655
|
options?.onProgress?.({
|
|
9556
9656
|
solverId: "ant-colony",
|
|
9557
|
-
percentage: Math.round((iter + 1) / ITERATIONS * 100),
|
|
9657
|
+
percentage: Math.round((iter + 1) / ITERATIONS$1 * 100),
|
|
9558
9658
|
bestObjective: bestMakespan,
|
|
9559
9659
|
currentIteration: iter + 1,
|
|
9560
|
-
totalIterations: ITERATIONS
|
|
9660
|
+
totalIterations: ITERATIONS$1
|
|
9561
9661
|
});
|
|
9562
9662
|
}
|
|
9563
9663
|
return makeSchedulingResult({
|
|
@@ -9566,7 +9666,8 @@ const antColonySolver = {
|
|
|
9566
9666
|
makespan: bestMakespan,
|
|
9567
9667
|
schedule: bestSchedule,
|
|
9568
9668
|
elapsedMs: performance.now() - startTime,
|
|
9569
|
-
iterations: ITERATIONS * NUM_ANTS
|
|
9669
|
+
iterations: ITERATIONS$1 * NUM_ANTS,
|
|
9670
|
+
trace
|
|
9570
9671
|
});
|
|
9571
9672
|
}
|
|
9572
9673
|
};
|
|
@@ -9813,23 +9914,18 @@ function validateSchedule(problem, schedule) {
|
|
|
9813
9914
|
errors
|
|
9814
9915
|
};
|
|
9815
9916
|
}
|
|
9816
|
-
|
|
9817
|
-
const MAX_TOTAL_OPS = 80;
|
|
9818
|
-
const MAX_OPS_PER_MACHINE = 8;
|
|
9819
|
-
/**
|
|
9820
|
-
* Load HiGHS - creates FRESH instance each time.
|
|
9821
|
-
*
|
|
9822
|
-
* We intentionally do NOT cache the HiGHS instance.
|
|
9823
|
-
* Caching causes WASM corruption errors.
|
|
9824
|
-
*/
|
|
9917
|
+
/** Load a fresh HiGHS WASM instance (never cache it — see file header). */
|
|
9825
9918
|
async function loadHiGHS() {
|
|
9826
9919
|
const highsLoader = (await import("highs")).default;
|
|
9827
|
-
return
|
|
9920
|
+
return highsLoader({ locateFile: (file) => {
|
|
9828
9921
|
const globalSelf = globalThis;
|
|
9829
9922
|
if (globalSelf.location?.origin) return `${globalSelf.location.origin}/${file}`;
|
|
9830
9923
|
return file;
|
|
9831
9924
|
} });
|
|
9832
9925
|
}
|
|
9926
|
+
const MAX_BINARY_VARS = 150;
|
|
9927
|
+
const MAX_TOTAL_OPS = 80;
|
|
9928
|
+
const MAX_OPS_PER_MACHINE = 8;
|
|
9833
9929
|
function elapsed(startTime) {
|
|
9834
9930
|
return typeof performance !== "undefined" ? performance.now() - startTime : Date.now() - startTime;
|
|
9835
9931
|
}
|
|
@@ -9861,6 +9957,21 @@ const highsSolver = {
|
|
|
9861
9957
|
const lpString = generateLPFormulation(problem);
|
|
9862
9958
|
if (!lpString.includes("Subject To") || !lpString.includes("Bounds")) throw new Error("Invalid LP formulation - missing required sections");
|
|
9863
9959
|
reportProgress(20);
|
|
9960
|
+
if (options?.shouldCancel?.()) {
|
|
9961
|
+
const g = await greedySolver.solve(problem, {});
|
|
9962
|
+
return makeSchedulingResult({
|
|
9963
|
+
solverId: "highs",
|
|
9964
|
+
solverName: "HiGHS (WASM)",
|
|
9965
|
+
makespan: g.objective,
|
|
9966
|
+
schedule: g.solution,
|
|
9967
|
+
elapsedMs: elapsed(startTime),
|
|
9968
|
+
iterations: 0,
|
|
9969
|
+
metadata: {
|
|
9970
|
+
usedFallback: true,
|
|
9971
|
+
reason: "cancelled before HiGHS solve"
|
|
9972
|
+
}
|
|
9973
|
+
});
|
|
9974
|
+
}
|
|
9864
9975
|
let solution;
|
|
9865
9976
|
try {
|
|
9866
9977
|
solution = highs.solve(lpString, {
|
|
@@ -9899,7 +10010,11 @@ const highsSolver = {
|
|
|
9899
10010
|
makespan,
|
|
9900
10011
|
schedule,
|
|
9901
10012
|
elapsedMs: elapsed(startTime),
|
|
9902
|
-
iterations: 1
|
|
10013
|
+
iterations: 1,
|
|
10014
|
+
trace: [{
|
|
10015
|
+
solution: schedule,
|
|
10016
|
+
objective: makespan
|
|
10017
|
+
}]
|
|
9903
10018
|
});
|
|
9904
10019
|
} catch (error) {
|
|
9905
10020
|
reportProgress(0);
|
|
@@ -10544,31 +10659,39 @@ const allSolvers = [
|
|
|
10544
10659
|
solverId: "qaoa-local",
|
|
10545
10660
|
percentage: pct
|
|
10546
10661
|
});
|
|
10662
|
+
const cancelled = () => options.shouldCancel?.() ?? false;
|
|
10547
10663
|
progress(0);
|
|
10548
10664
|
const greedyResult = await greedySolver.solve(problem, {});
|
|
10549
10665
|
progress(10);
|
|
10550
10666
|
const greedyMakespan = greedyResult.objective;
|
|
10551
10667
|
const greedySchedule = greedyResult.solution;
|
|
10668
|
+
const greedyFallback = (reason) => makeSchedulingResult({
|
|
10669
|
+
solverId: "qaoa-local",
|
|
10670
|
+
solverName: "QAOA (Local)",
|
|
10671
|
+
makespan: greedyMakespan,
|
|
10672
|
+
schedule: greedySchedule,
|
|
10673
|
+
elapsedMs: elapsed(),
|
|
10674
|
+
trace: [{
|
|
10675
|
+
solution: greedySchedule,
|
|
10676
|
+
objective: greedyMakespan
|
|
10677
|
+
}],
|
|
10678
|
+
metadata: {
|
|
10679
|
+
layers: LAYERS$1,
|
|
10680
|
+
shots: 0,
|
|
10681
|
+
gamma: 0,
|
|
10682
|
+
beta: 0,
|
|
10683
|
+
rawMakespan: greedyMakespan,
|
|
10684
|
+
usedFallback: true,
|
|
10685
|
+
reason
|
|
10686
|
+
}
|
|
10687
|
+
});
|
|
10552
10688
|
const encoded = encodeSchedulingWithMetadata(problem, greedyMakespan);
|
|
10553
10689
|
const numQubits = encoded.qubo.size;
|
|
10554
10690
|
if (numQubits > MAX_QUBITS || numQubits === 0) {
|
|
10555
10691
|
progress(100);
|
|
10556
|
-
return
|
|
10557
|
-
solverId: "qaoa-local",
|
|
10558
|
-
solverName: "QAOA (Local)",
|
|
10559
|
-
makespan: greedyMakespan,
|
|
10560
|
-
schedule: greedySchedule,
|
|
10561
|
-
elapsedMs: elapsed(),
|
|
10562
|
-
metadata: {
|
|
10563
|
-
layers: LAYERS$1,
|
|
10564
|
-
shots: 0,
|
|
10565
|
-
gamma: 0,
|
|
10566
|
-
beta: 0,
|
|
10567
|
-
rawMakespan: greedyMakespan,
|
|
10568
|
-
usedFallback: true
|
|
10569
|
-
}
|
|
10570
|
-
});
|
|
10692
|
+
return greedyFallback(numQubits === 0 ? "no qubits to encode" : `instance needs ${numQubits} qubits (cap ${MAX_QUBITS})`);
|
|
10571
10693
|
}
|
|
10694
|
+
if (cancelled()) return greedyFallback("cancelled");
|
|
10572
10695
|
const ising = quboToIsing(encoded.qubo);
|
|
10573
10696
|
const backend = new LocalSimBackend();
|
|
10574
10697
|
progress(20);
|
|
@@ -10578,6 +10701,7 @@ const allSolvers = [
|
|
|
10578
10701
|
p: LAYERS$1
|
|
10579
10702
|
});
|
|
10580
10703
|
progress(75);
|
|
10704
|
+
if (cancelled()) return greedyFallback("cancelled");
|
|
10581
10705
|
const gates = buildQAOACircuit(numQubits, ising, [gamma], [beta], LAYERS$1);
|
|
10582
10706
|
const { probabilities } = await backend.run(numQubits, gates);
|
|
10583
10707
|
const samples = sampleFromProbabilities(probabilities, FINAL_SHOTS$1);
|
|
@@ -10594,6 +10718,13 @@ const allSolvers = [
|
|
|
10594
10718
|
makespan: winnerMakespan,
|
|
10595
10719
|
schedule: winnerSchedule,
|
|
10596
10720
|
elapsedMs: elapsed(),
|
|
10721
|
+
trace: [{
|
|
10722
|
+
solution: greedySchedule,
|
|
10723
|
+
objective: greedyMakespan
|
|
10724
|
+
}, {
|
|
10725
|
+
solution: winnerSchedule,
|
|
10726
|
+
objective: winnerMakespan
|
|
10727
|
+
}],
|
|
10597
10728
|
metadata: {
|
|
10598
10729
|
layers: LAYERS$1,
|
|
10599
10730
|
shots: FINAL_SHOTS$1,
|
|
@@ -10636,11 +10767,12 @@ RULES:
|
|
|
10636
10767
|
5. Each machine can only process one operation at a time
|
|
10637
10768
|
6. Assign reasonable durations if not specified (use whole numbers)
|
|
10638
10769
|
7. Create meaningful names for jobs and machines based on context
|
|
10770
|
+
8. "description" MUST be a single plain-English sentence summarizing the real-world scenario this models (what the user just described) — it is shown on the on-screen brief card so a human can recognize at a glance that it matches the conversation. e.g. "Scheduling the 6 high-priority sprint issues across 5 developers to minimize total completion time."
|
|
10639
10771
|
|
|
10640
10772
|
RESPOND WITH ONLY A JSON OBJECT matching this schema:
|
|
10641
10773
|
{
|
|
10642
|
-
"name": "string - descriptive problem name",
|
|
10643
|
-
"description": "string -
|
|
10774
|
+
"name": "string - short descriptive problem name",
|
|
10775
|
+
"description": "string - one plain-English sentence summarizing the scenario (see rule 8)",
|
|
10644
10776
|
"jobs": [
|
|
10645
10777
|
{
|
|
10646
10778
|
"id": 0,
|
|
@@ -10901,54 +11033,1966 @@ No markdown, no explanation, no code blocks — just the raw JSON object.`;
|
|
|
10901
11033
|
" \\/ <- fit here",
|
|
10902
11034
|
"Minimize: sum(error^2)"
|
|
10903
11035
|
].join("\n");
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
11036
|
+
const routingSampleProblems = [
|
|
11037
|
+
{
|
|
11038
|
+
family: "routing",
|
|
11039
|
+
name: "Austin Food Truck Circuit",
|
|
11040
|
+
description: "A food truck owner wants the shortest nightly loop hitting ten Austin hotspots, starting and ending at the South Lamar commissary.",
|
|
11041
|
+
nodes: [
|
|
11042
|
+
{
|
|
11043
|
+
id: 0,
|
|
11044
|
+
name: "Commissary (S Lamar)",
|
|
11045
|
+
x: 38,
|
|
11046
|
+
y: 72
|
|
11047
|
+
},
|
|
11048
|
+
{
|
|
11049
|
+
id: 1,
|
|
11050
|
+
name: "Zilker Park",
|
|
11051
|
+
x: 30,
|
|
11052
|
+
y: 58
|
|
11053
|
+
},
|
|
11054
|
+
{
|
|
11055
|
+
id: 2,
|
|
11056
|
+
name: "Rainey St",
|
|
11057
|
+
x: 56,
|
|
11058
|
+
y: 60
|
|
11059
|
+
},
|
|
11060
|
+
{
|
|
11061
|
+
id: 3,
|
|
11062
|
+
name: "6th & Red River",
|
|
11063
|
+
x: 54,
|
|
11064
|
+
y: 48
|
|
11065
|
+
},
|
|
11066
|
+
{
|
|
11067
|
+
id: 4,
|
|
11068
|
+
name: "UT Campus",
|
|
11069
|
+
x: 52,
|
|
11070
|
+
y: 32
|
|
11071
|
+
},
|
|
11072
|
+
{
|
|
11073
|
+
id: 5,
|
|
11074
|
+
name: "Mueller Market",
|
|
11075
|
+
x: 72,
|
|
11076
|
+
y: 30
|
|
11077
|
+
},
|
|
11078
|
+
{
|
|
11079
|
+
id: 6,
|
|
11080
|
+
name: "East Cesar Chavez",
|
|
11081
|
+
x: 70,
|
|
11082
|
+
y: 56
|
|
11083
|
+
},
|
|
11084
|
+
{
|
|
11085
|
+
id: 7,
|
|
11086
|
+
name: "Barton Springs",
|
|
11087
|
+
x: 34,
|
|
11088
|
+
y: 64
|
|
11089
|
+
},
|
|
11090
|
+
{
|
|
11091
|
+
id: 8,
|
|
11092
|
+
name: "The Domain",
|
|
11093
|
+
x: 44,
|
|
11094
|
+
y: 8
|
|
11095
|
+
},
|
|
11096
|
+
{
|
|
11097
|
+
id: 9,
|
|
11098
|
+
name: "Oak Hill",
|
|
11099
|
+
x: 12,
|
|
11100
|
+
y: 86
|
|
11101
|
+
}
|
|
11102
|
+
]
|
|
11103
|
+
},
|
|
11104
|
+
{
|
|
11105
|
+
family: "routing",
|
|
11106
|
+
name: "Kyiv Drone Resupply Run",
|
|
11107
|
+
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.",
|
|
11108
|
+
nodes: [
|
|
11109
|
+
{
|
|
11110
|
+
id: 0,
|
|
11111
|
+
name: "Base",
|
|
11112
|
+
x: 50,
|
|
11113
|
+
y: 92
|
|
11114
|
+
},
|
|
11115
|
+
{
|
|
11116
|
+
id: 1,
|
|
11117
|
+
name: "Cache Alpha",
|
|
11118
|
+
x: 18,
|
|
11119
|
+
y: 74
|
|
11120
|
+
},
|
|
11121
|
+
{
|
|
11122
|
+
id: 2,
|
|
11123
|
+
name: "Cache Bravo",
|
|
11124
|
+
x: 10,
|
|
11125
|
+
y: 48
|
|
11126
|
+
},
|
|
11127
|
+
{
|
|
11128
|
+
id: 3,
|
|
11129
|
+
name: "Cache Charlie",
|
|
11130
|
+
x: 24,
|
|
11131
|
+
y: 22
|
|
11132
|
+
},
|
|
11133
|
+
{
|
|
11134
|
+
id: 4,
|
|
11135
|
+
name: "Cache Delta",
|
|
11136
|
+
x: 44,
|
|
11137
|
+
y: 10
|
|
11138
|
+
},
|
|
11139
|
+
{
|
|
11140
|
+
id: 5,
|
|
11141
|
+
name: "Cache Echo",
|
|
11142
|
+
x: 66,
|
|
11143
|
+
y: 14
|
|
11144
|
+
},
|
|
11145
|
+
{
|
|
11146
|
+
id: 6,
|
|
11147
|
+
name: "Cache Foxtrot",
|
|
11148
|
+
x: 86,
|
|
11149
|
+
y: 26
|
|
11150
|
+
},
|
|
11151
|
+
{
|
|
11152
|
+
id: 7,
|
|
11153
|
+
name: "Cache Golf",
|
|
11154
|
+
x: 92,
|
|
11155
|
+
y: 52
|
|
11156
|
+
},
|
|
11157
|
+
{
|
|
11158
|
+
id: 8,
|
|
11159
|
+
name: "Cache Hotel",
|
|
11160
|
+
x: 80,
|
|
11161
|
+
y: 72
|
|
11162
|
+
},
|
|
11163
|
+
{
|
|
11164
|
+
id: 9,
|
|
11165
|
+
name: "Cache India",
|
|
11166
|
+
x: 62,
|
|
11167
|
+
y: 56
|
|
11168
|
+
},
|
|
11169
|
+
{
|
|
11170
|
+
id: 10,
|
|
11171
|
+
name: "Cache Juliet",
|
|
11172
|
+
x: 38,
|
|
11173
|
+
y: 44
|
|
11174
|
+
},
|
|
11175
|
+
{
|
|
11176
|
+
id: 11,
|
|
11177
|
+
name: "Cache Kilo",
|
|
11178
|
+
x: 30,
|
|
11179
|
+
y: 60
|
|
11180
|
+
}
|
|
11181
|
+
]
|
|
11182
|
+
},
|
|
11183
|
+
{
|
|
11184
|
+
family: "routing",
|
|
11185
|
+
name: "Hill Country Brewery Tour",
|
|
11186
|
+
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.",
|
|
11187
|
+
nodes: [
|
|
11188
|
+
{
|
|
11189
|
+
id: 0,
|
|
11190
|
+
name: "Depot (Dripping Springs)",
|
|
11191
|
+
x: 20,
|
|
11192
|
+
y: 60
|
|
11193
|
+
},
|
|
11194
|
+
{
|
|
11195
|
+
id: 1,
|
|
11196
|
+
name: "Jester King",
|
|
11197
|
+
x: 32,
|
|
11198
|
+
y: 48
|
|
11199
|
+
},
|
|
11200
|
+
{
|
|
11201
|
+
id: 2,
|
|
11202
|
+
name: "Vista Brewing",
|
|
11203
|
+
x: 16,
|
|
11204
|
+
y: 78
|
|
11205
|
+
},
|
|
11206
|
+
{
|
|
11207
|
+
id: 3,
|
|
11208
|
+
name: "Treaty Oak",
|
|
11209
|
+
x: 28,
|
|
11210
|
+
y: 70
|
|
11211
|
+
},
|
|
11212
|
+
{
|
|
11213
|
+
id: 4,
|
|
11214
|
+
name: "Last Stand",
|
|
11215
|
+
x: 46,
|
|
11216
|
+
y: 38
|
|
11217
|
+
},
|
|
11218
|
+
{
|
|
11219
|
+
id: 5,
|
|
11220
|
+
name: "Fitzhugh Brewing",
|
|
11221
|
+
x: 40,
|
|
11222
|
+
y: 56
|
|
11223
|
+
},
|
|
11224
|
+
{
|
|
11225
|
+
id: 6,
|
|
11226
|
+
name: "Family Business",
|
|
11227
|
+
x: 60,
|
|
11228
|
+
y: 30
|
|
11229
|
+
},
|
|
11230
|
+
{
|
|
11231
|
+
id: 7,
|
|
11232
|
+
name: "Suds Monkey",
|
|
11233
|
+
x: 24,
|
|
11234
|
+
y: 52
|
|
11235
|
+
}
|
|
11236
|
+
]
|
|
10918
11237
|
}
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
11238
|
+
];
|
|
11239
|
+
const packingSampleProblems = [
|
|
11240
|
+
{
|
|
11241
|
+
family: "packing",
|
|
11242
|
+
name: "Mars Cargo Manifest",
|
|
11243
|
+
description: "Fourteen payload modules must ride to orbit in standardized cargo pods. Every pod you do not launch saves nine figures.",
|
|
11244
|
+
binCapacity: 100,
|
|
11245
|
+
binLabel: "pod",
|
|
11246
|
+
items: [
|
|
11247
|
+
{
|
|
11248
|
+
id: 0,
|
|
11249
|
+
name: "Habitat Frame",
|
|
11250
|
+
size: 62
|
|
11251
|
+
},
|
|
11252
|
+
{
|
|
11253
|
+
id: 1,
|
|
11254
|
+
name: "Water Recycler",
|
|
11255
|
+
size: 48
|
|
11256
|
+
},
|
|
11257
|
+
{
|
|
11258
|
+
id: 2,
|
|
11259
|
+
name: "Solar Array A",
|
|
11260
|
+
size: 55
|
|
11261
|
+
},
|
|
11262
|
+
{
|
|
11263
|
+
id: 3,
|
|
11264
|
+
name: "Solar Array B",
|
|
11265
|
+
size: 55
|
|
11266
|
+
},
|
|
11267
|
+
{
|
|
11268
|
+
id: 4,
|
|
11269
|
+
name: "Rover Chassis",
|
|
11270
|
+
size: 70
|
|
11271
|
+
},
|
|
11272
|
+
{
|
|
11273
|
+
id: 5,
|
|
11274
|
+
name: "Lab Module",
|
|
11275
|
+
size: 38
|
|
11276
|
+
},
|
|
11277
|
+
{
|
|
11278
|
+
id: 6,
|
|
11279
|
+
name: "Greenhouse Kit",
|
|
11280
|
+
size: 42
|
|
11281
|
+
},
|
|
11282
|
+
{
|
|
11283
|
+
id: 7,
|
|
11284
|
+
name: "Battery Bank",
|
|
11285
|
+
size: 30
|
|
11286
|
+
},
|
|
11287
|
+
{
|
|
11288
|
+
id: 8,
|
|
11289
|
+
name: "Antenna Mast",
|
|
11290
|
+
size: 22
|
|
11291
|
+
},
|
|
11292
|
+
{
|
|
11293
|
+
id: 9,
|
|
11294
|
+
name: "Med Bay Kit",
|
|
11295
|
+
size: 26
|
|
11296
|
+
},
|
|
11297
|
+
{
|
|
11298
|
+
id: 10,
|
|
11299
|
+
name: "Spare Parts Crate",
|
|
11300
|
+
size: 18
|
|
11301
|
+
},
|
|
11302
|
+
{
|
|
11303
|
+
id: 11,
|
|
11304
|
+
name: "Food Stores",
|
|
11305
|
+
size: 44
|
|
11306
|
+
},
|
|
11307
|
+
{
|
|
11308
|
+
id: 12,
|
|
11309
|
+
name: "Drill Assembly",
|
|
11310
|
+
size: 34
|
|
11311
|
+
},
|
|
11312
|
+
{
|
|
11313
|
+
id: 13,
|
|
11314
|
+
name: "3D Printer",
|
|
11315
|
+
size: 16
|
|
11316
|
+
}
|
|
11317
|
+
]
|
|
11318
|
+
},
|
|
11319
|
+
{
|
|
11320
|
+
family: "packing",
|
|
11321
|
+
name: "Moving Day — Oak Hill",
|
|
11322
|
+
description: "Everything Erik owns has to fit into rented trucks. Each truck costs $180 a day, so the optimizer IS the budget.",
|
|
11323
|
+
binCapacity: 80,
|
|
11324
|
+
binLabel: "truck",
|
|
11325
|
+
items: [
|
|
11326
|
+
{
|
|
11327
|
+
id: 0,
|
|
11328
|
+
name: "Couch",
|
|
11329
|
+
size: 40
|
|
11330
|
+
},
|
|
11331
|
+
{
|
|
11332
|
+
id: 1,
|
|
11333
|
+
name: "Bed Frame",
|
|
11334
|
+
size: 32
|
|
11335
|
+
},
|
|
11336
|
+
{
|
|
11337
|
+
id: 2,
|
|
11338
|
+
name: "Mattress",
|
|
11339
|
+
size: 28
|
|
11340
|
+
},
|
|
11341
|
+
{
|
|
11342
|
+
id: 3,
|
|
11343
|
+
name: "Dining Table",
|
|
11344
|
+
size: 30
|
|
11345
|
+
},
|
|
11346
|
+
{
|
|
11347
|
+
id: 4,
|
|
11348
|
+
name: "Bookshelves",
|
|
11349
|
+
size: 24
|
|
11350
|
+
},
|
|
11351
|
+
{
|
|
11352
|
+
id: 5,
|
|
11353
|
+
name: "Desk Battlestation",
|
|
11354
|
+
size: 36
|
|
11355
|
+
},
|
|
11356
|
+
{
|
|
11357
|
+
id: 6,
|
|
11358
|
+
name: "Arcade Cabinet",
|
|
11359
|
+
size: 44
|
|
11360
|
+
},
|
|
11361
|
+
{
|
|
11362
|
+
id: 7,
|
|
11363
|
+
name: "Kitchen Boxes",
|
|
11364
|
+
size: 20
|
|
11365
|
+
},
|
|
11366
|
+
{
|
|
11367
|
+
id: 8,
|
|
11368
|
+
name: "Book Boxes",
|
|
11369
|
+
size: 26
|
|
11370
|
+
},
|
|
11371
|
+
{
|
|
11372
|
+
id: 9,
|
|
11373
|
+
name: "Garage Tools",
|
|
11374
|
+
size: 18
|
|
11375
|
+
},
|
|
11376
|
+
{
|
|
11377
|
+
id: 10,
|
|
11378
|
+
name: "Bikes",
|
|
11379
|
+
size: 16
|
|
11380
|
+
},
|
|
11381
|
+
{
|
|
11382
|
+
id: 11,
|
|
11383
|
+
name: "Server Rack",
|
|
11384
|
+
size: 22
|
|
11385
|
+
}
|
|
11386
|
+
]
|
|
11387
|
+
},
|
|
11388
|
+
{
|
|
11389
|
+
family: "packing",
|
|
11390
|
+
name: "Server Rack Tetris",
|
|
11391
|
+
description: "Thirteen VM workloads need hosts. Each host burns power whether it is full or idle — consolidate or pay.",
|
|
11392
|
+
binCapacity: 64,
|
|
11393
|
+
binLabel: "host",
|
|
11394
|
+
items: [
|
|
11395
|
+
{
|
|
11396
|
+
id: 0,
|
|
11397
|
+
name: "Postgres Primary",
|
|
11398
|
+
size: 32
|
|
11399
|
+
},
|
|
11400
|
+
{
|
|
11401
|
+
id: 1,
|
|
11402
|
+
name: "Postgres Replica",
|
|
11403
|
+
size: 24
|
|
11404
|
+
},
|
|
11405
|
+
{
|
|
11406
|
+
id: 2,
|
|
11407
|
+
name: "Web Frontend x4",
|
|
11408
|
+
size: 16
|
|
11409
|
+
},
|
|
11410
|
+
{
|
|
11411
|
+
id: 3,
|
|
11412
|
+
name: "API Cluster",
|
|
11413
|
+
size: 28
|
|
11414
|
+
},
|
|
11415
|
+
{
|
|
11416
|
+
id: 4,
|
|
11417
|
+
name: "Redis Cache",
|
|
11418
|
+
size: 12
|
|
11419
|
+
},
|
|
11420
|
+
{
|
|
11421
|
+
id: 5,
|
|
11422
|
+
name: "ML Inference",
|
|
11423
|
+
size: 48
|
|
11424
|
+
},
|
|
11425
|
+
{
|
|
11426
|
+
id: 6,
|
|
11427
|
+
name: "CI Runners",
|
|
11428
|
+
size: 20
|
|
11429
|
+
},
|
|
11430
|
+
{
|
|
11431
|
+
id: 7,
|
|
11432
|
+
name: "Log Pipeline",
|
|
11433
|
+
size: 14
|
|
11434
|
+
},
|
|
11435
|
+
{
|
|
11436
|
+
id: 8,
|
|
11437
|
+
name: "Metrics Stack",
|
|
11438
|
+
size: 18
|
|
11439
|
+
},
|
|
11440
|
+
{
|
|
11441
|
+
id: 9,
|
|
11442
|
+
name: "Batch ETL",
|
|
11443
|
+
size: 26
|
|
11444
|
+
},
|
|
11445
|
+
{
|
|
11446
|
+
id: 10,
|
|
11447
|
+
name: "Search Index",
|
|
11448
|
+
size: 22
|
|
11449
|
+
},
|
|
11450
|
+
{
|
|
11451
|
+
id: 11,
|
|
11452
|
+
name: "Mail Service",
|
|
11453
|
+
size: 8
|
|
11454
|
+
},
|
|
11455
|
+
{
|
|
11456
|
+
id: 12,
|
|
11457
|
+
name: "Backup Agent",
|
|
11458
|
+
size: 10
|
|
11459
|
+
}
|
|
11460
|
+
]
|
|
10932
11461
|
}
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
11462
|
+
];
|
|
11463
|
+
const assignmentSampleProblems = [
|
|
11464
|
+
{
|
|
11465
|
+
family: "assignment",
|
|
11466
|
+
name: "Raid Roster",
|
|
11467
|
+
description: "Eight raiders, eight roles, one progression night. The cost matrix is each player's discomfort in each role — minimize total grumbling.",
|
|
11468
|
+
agents: [
|
|
11469
|
+
{
|
|
11470
|
+
id: 0,
|
|
11471
|
+
name: "Thorgrim"
|
|
11472
|
+
},
|
|
11473
|
+
{
|
|
11474
|
+
id: 1,
|
|
11475
|
+
name: "Lyralei"
|
|
11476
|
+
},
|
|
11477
|
+
{
|
|
11478
|
+
id: 2,
|
|
11479
|
+
name: "Mogrash"
|
|
11480
|
+
},
|
|
11481
|
+
{
|
|
11482
|
+
id: 3,
|
|
11483
|
+
name: "Seraphine"
|
|
11484
|
+
},
|
|
11485
|
+
{
|
|
11486
|
+
id: 4,
|
|
11487
|
+
name: "Kettle"
|
|
11488
|
+
},
|
|
11489
|
+
{
|
|
11490
|
+
id: 5,
|
|
11491
|
+
name: "Vexx"
|
|
11492
|
+
},
|
|
11493
|
+
{
|
|
11494
|
+
id: 6,
|
|
11495
|
+
name: "Oakenheart"
|
|
11496
|
+
},
|
|
11497
|
+
{
|
|
11498
|
+
id: 7,
|
|
11499
|
+
name: "Zinnia"
|
|
11500
|
+
}
|
|
11501
|
+
],
|
|
11502
|
+
tasks: [
|
|
11503
|
+
{
|
|
11504
|
+
id: 0,
|
|
11505
|
+
name: "Main Tank"
|
|
11506
|
+
},
|
|
11507
|
+
{
|
|
11508
|
+
id: 1,
|
|
11509
|
+
name: "Off Tank"
|
|
11510
|
+
},
|
|
11511
|
+
{
|
|
11512
|
+
id: 2,
|
|
11513
|
+
name: "Healer 1"
|
|
11514
|
+
},
|
|
11515
|
+
{
|
|
11516
|
+
id: 3,
|
|
11517
|
+
name: "Healer 2"
|
|
11518
|
+
},
|
|
11519
|
+
{
|
|
11520
|
+
id: 4,
|
|
11521
|
+
name: "Melee DPS"
|
|
11522
|
+
},
|
|
11523
|
+
{
|
|
11524
|
+
id: 5,
|
|
11525
|
+
name: "Ranged DPS"
|
|
11526
|
+
},
|
|
11527
|
+
{
|
|
11528
|
+
id: 6,
|
|
11529
|
+
name: "Raid Lead"
|
|
11530
|
+
},
|
|
11531
|
+
{
|
|
11532
|
+
id: 7,
|
|
11533
|
+
name: "Loot Council"
|
|
11534
|
+
}
|
|
11535
|
+
],
|
|
11536
|
+
cost: [
|
|
11537
|
+
[
|
|
11538
|
+
2,
|
|
11539
|
+
4,
|
|
11540
|
+
9,
|
|
11541
|
+
9,
|
|
11542
|
+
5,
|
|
11543
|
+
7,
|
|
11544
|
+
6,
|
|
11545
|
+
8
|
|
11546
|
+
],
|
|
11547
|
+
[
|
|
11548
|
+
9,
|
|
11549
|
+
8,
|
|
11550
|
+
2,
|
|
11551
|
+
3,
|
|
11552
|
+
7,
|
|
11553
|
+
5,
|
|
11554
|
+
6,
|
|
11555
|
+
4
|
|
11556
|
+
],
|
|
11557
|
+
[
|
|
11558
|
+
3,
|
|
11559
|
+
2,
|
|
11560
|
+
9,
|
|
11561
|
+
8,
|
|
11562
|
+
4,
|
|
11563
|
+
6,
|
|
11564
|
+
7,
|
|
11565
|
+
9
|
|
11566
|
+
],
|
|
11567
|
+
[
|
|
11568
|
+
8,
|
|
11569
|
+
9,
|
|
11570
|
+
3,
|
|
11571
|
+
2,
|
|
11572
|
+
6,
|
|
11573
|
+
4,
|
|
11574
|
+
5,
|
|
11575
|
+
6
|
|
11576
|
+
],
|
|
11577
|
+
[
|
|
11578
|
+
7,
|
|
11579
|
+
6,
|
|
11580
|
+
5,
|
|
11581
|
+
5,
|
|
11582
|
+
2,
|
|
11583
|
+
3,
|
|
11584
|
+
8,
|
|
11585
|
+
7
|
|
11586
|
+
],
|
|
11587
|
+
[
|
|
11588
|
+
6,
|
|
11589
|
+
7,
|
|
11590
|
+
6,
|
|
11591
|
+
6,
|
|
11592
|
+
3,
|
|
11593
|
+
2,
|
|
11594
|
+
4,
|
|
11595
|
+
5
|
|
11596
|
+
],
|
|
11597
|
+
[
|
|
11598
|
+
4,
|
|
11599
|
+
3,
|
|
11600
|
+
7,
|
|
11601
|
+
7,
|
|
11602
|
+
5,
|
|
11603
|
+
6,
|
|
11604
|
+
2,
|
|
11605
|
+
3
|
|
11606
|
+
],
|
|
11607
|
+
[
|
|
11608
|
+
9,
|
|
11609
|
+
8,
|
|
11610
|
+
4,
|
|
11611
|
+
4,
|
|
11612
|
+
6,
|
|
11613
|
+
5,
|
|
11614
|
+
3,
|
|
11615
|
+
2
|
|
11616
|
+
]
|
|
11617
|
+
]
|
|
11618
|
+
},
|
|
11619
|
+
{
|
|
11620
|
+
family: "assignment",
|
|
11621
|
+
name: "Surgery Suite Staffing",
|
|
11622
|
+
description: "Six surgeons, six operating rooms with different specialties on the board today. Cost = mismatch between surgeon expertise and case type.",
|
|
11623
|
+
agents: [
|
|
11624
|
+
{
|
|
11625
|
+
id: 0,
|
|
11626
|
+
name: "Dr. Asante"
|
|
11627
|
+
},
|
|
11628
|
+
{
|
|
11629
|
+
id: 1,
|
|
11630
|
+
name: "Dr. Brook"
|
|
11631
|
+
},
|
|
11632
|
+
{
|
|
11633
|
+
id: 2,
|
|
11634
|
+
name: "Dr. Chen"
|
|
11635
|
+
},
|
|
11636
|
+
{
|
|
11637
|
+
id: 3,
|
|
11638
|
+
name: "Dr. Duarte"
|
|
11639
|
+
},
|
|
11640
|
+
{
|
|
11641
|
+
id: 4,
|
|
11642
|
+
name: "Dr. Eze"
|
|
11643
|
+
},
|
|
11644
|
+
{
|
|
11645
|
+
id: 5,
|
|
11646
|
+
name: "Dr. Farrow"
|
|
11647
|
+
}
|
|
11648
|
+
],
|
|
11649
|
+
tasks: [
|
|
11650
|
+
{
|
|
11651
|
+
id: 0,
|
|
11652
|
+
name: "OR-1 Cardiac"
|
|
11653
|
+
},
|
|
11654
|
+
{
|
|
11655
|
+
id: 1,
|
|
11656
|
+
name: "OR-2 Ortho"
|
|
11657
|
+
},
|
|
11658
|
+
{
|
|
11659
|
+
id: 2,
|
|
11660
|
+
name: "OR-3 Neuro"
|
|
11661
|
+
},
|
|
11662
|
+
{
|
|
11663
|
+
id: 3,
|
|
11664
|
+
name: "OR-4 General"
|
|
11665
|
+
},
|
|
11666
|
+
{
|
|
11667
|
+
id: 4,
|
|
11668
|
+
name: "OR-5 Pediatric"
|
|
11669
|
+
},
|
|
11670
|
+
{
|
|
11671
|
+
id: 5,
|
|
11672
|
+
name: "OR-6 Trauma"
|
|
11673
|
+
}
|
|
11674
|
+
],
|
|
11675
|
+
cost: [
|
|
11676
|
+
[
|
|
11677
|
+
1,
|
|
11678
|
+
8,
|
|
11679
|
+
9,
|
|
11680
|
+
4,
|
|
11681
|
+
7,
|
|
11682
|
+
5
|
|
11683
|
+
],
|
|
11684
|
+
[
|
|
11685
|
+
9,
|
|
11686
|
+
1,
|
|
11687
|
+
8,
|
|
11688
|
+
5,
|
|
11689
|
+
6,
|
|
11690
|
+
3
|
|
11691
|
+
],
|
|
11692
|
+
[
|
|
11693
|
+
8,
|
|
11694
|
+
9,
|
|
11695
|
+
1,
|
|
11696
|
+
6,
|
|
11697
|
+
5,
|
|
11698
|
+
7
|
|
11699
|
+
],
|
|
11700
|
+
[
|
|
11701
|
+
5,
|
|
11702
|
+
4,
|
|
11703
|
+
7,
|
|
11704
|
+
1,
|
|
11705
|
+
3,
|
|
11706
|
+
2
|
|
11707
|
+
],
|
|
11708
|
+
[
|
|
11709
|
+
7,
|
|
11710
|
+
6,
|
|
11711
|
+
5,
|
|
11712
|
+
3,
|
|
11713
|
+
1,
|
|
11714
|
+
4
|
|
11715
|
+
],
|
|
11716
|
+
[
|
|
11717
|
+
4,
|
|
11718
|
+
3,
|
|
11719
|
+
6,
|
|
11720
|
+
2,
|
|
11721
|
+
4,
|
|
11722
|
+
1
|
|
11723
|
+
]
|
|
11724
|
+
]
|
|
11725
|
+
},
|
|
11726
|
+
{
|
|
11727
|
+
family: "assignment",
|
|
11728
|
+
name: "Support Ticket Triage",
|
|
11729
|
+
description: "Seven engineers, nine open escalations — two will wait. Cost = estimated hours for each engineer on each ticket given their domain depth.",
|
|
11730
|
+
agents: [
|
|
11731
|
+
{
|
|
11732
|
+
id: 0,
|
|
11733
|
+
name: "Imani"
|
|
11734
|
+
},
|
|
11735
|
+
{
|
|
11736
|
+
id: 1,
|
|
11737
|
+
name: "Jonas"
|
|
11738
|
+
},
|
|
11739
|
+
{
|
|
11740
|
+
id: 2,
|
|
11741
|
+
name: "Kai"
|
|
11742
|
+
},
|
|
11743
|
+
{
|
|
11744
|
+
id: 3,
|
|
11745
|
+
name: "Lena"
|
|
11746
|
+
},
|
|
11747
|
+
{
|
|
11748
|
+
id: 4,
|
|
11749
|
+
name: "Marcus"
|
|
11750
|
+
},
|
|
11751
|
+
{
|
|
11752
|
+
id: 5,
|
|
11753
|
+
name: "Noor"
|
|
11754
|
+
},
|
|
11755
|
+
{
|
|
11756
|
+
id: 6,
|
|
11757
|
+
name: "Owen"
|
|
11758
|
+
}
|
|
11759
|
+
],
|
|
11760
|
+
tasks: [
|
|
11761
|
+
{
|
|
11762
|
+
id: 0,
|
|
11763
|
+
name: "Auth outage"
|
|
11764
|
+
},
|
|
11765
|
+
{
|
|
11766
|
+
id: 1,
|
|
11767
|
+
name: "Billing drift"
|
|
11768
|
+
},
|
|
11769
|
+
{
|
|
11770
|
+
id: 2,
|
|
11771
|
+
name: "Mobile crash"
|
|
11772
|
+
},
|
|
11773
|
+
{
|
|
11774
|
+
id: 3,
|
|
11775
|
+
name: "Search latency"
|
|
11776
|
+
},
|
|
11777
|
+
{
|
|
11778
|
+
id: 4,
|
|
11779
|
+
name: "Data export"
|
|
11780
|
+
},
|
|
11781
|
+
{
|
|
11782
|
+
id: 5,
|
|
11783
|
+
name: "Webhook storm"
|
|
11784
|
+
},
|
|
11785
|
+
{
|
|
11786
|
+
id: 6,
|
|
11787
|
+
name: "SSO config"
|
|
11788
|
+
},
|
|
11789
|
+
{
|
|
11790
|
+
id: 7,
|
|
11791
|
+
name: "Rate limiter"
|
|
11792
|
+
},
|
|
11793
|
+
{
|
|
11794
|
+
id: 8,
|
|
11795
|
+
name: "PDF renderer"
|
|
11796
|
+
}
|
|
11797
|
+
],
|
|
11798
|
+
cost: [
|
|
11799
|
+
[
|
|
11800
|
+
2,
|
|
11801
|
+
7,
|
|
11802
|
+
9,
|
|
11803
|
+
6,
|
|
11804
|
+
5,
|
|
11805
|
+
4,
|
|
11806
|
+
3,
|
|
11807
|
+
5,
|
|
11808
|
+
8
|
|
11809
|
+
],
|
|
11810
|
+
[
|
|
11811
|
+
8,
|
|
11812
|
+
2,
|
|
11813
|
+
7,
|
|
11814
|
+
5,
|
|
11815
|
+
3,
|
|
11816
|
+
6,
|
|
11817
|
+
7,
|
|
11818
|
+
6,
|
|
11819
|
+
4
|
|
11820
|
+
],
|
|
11821
|
+
[
|
|
11822
|
+
9,
|
|
11823
|
+
6,
|
|
11824
|
+
2,
|
|
11825
|
+
7,
|
|
11826
|
+
6,
|
|
11827
|
+
5,
|
|
11828
|
+
8,
|
|
11829
|
+
7,
|
|
11830
|
+
3
|
|
11831
|
+
],
|
|
11832
|
+
[
|
|
11833
|
+
4,
|
|
11834
|
+
5,
|
|
11835
|
+
6,
|
|
11836
|
+
2,
|
|
11837
|
+
4,
|
|
11838
|
+
3,
|
|
11839
|
+
5,
|
|
11840
|
+
2,
|
|
11841
|
+
7
|
|
11842
|
+
],
|
|
11843
|
+
[
|
|
11844
|
+
6,
|
|
11845
|
+
3,
|
|
11846
|
+
8,
|
|
11847
|
+
4,
|
|
11848
|
+
2,
|
|
11849
|
+
7,
|
|
11850
|
+
6,
|
|
11851
|
+
5,
|
|
11852
|
+
5
|
|
11853
|
+
],
|
|
11854
|
+
[
|
|
11855
|
+
3,
|
|
11856
|
+
6,
|
|
11857
|
+
5,
|
|
11858
|
+
3,
|
|
11859
|
+
7,
|
|
11860
|
+
2,
|
|
11861
|
+
2,
|
|
11862
|
+
4,
|
|
11863
|
+
6
|
|
11864
|
+
],
|
|
11865
|
+
[
|
|
11866
|
+
7,
|
|
11867
|
+
4,
|
|
11868
|
+
4,
|
|
11869
|
+
8,
|
|
11870
|
+
5,
|
|
11871
|
+
6,
|
|
11872
|
+
4,
|
|
11873
|
+
3,
|
|
11874
|
+
2
|
|
11875
|
+
]
|
|
11876
|
+
]
|
|
11877
|
+
}
|
|
11878
|
+
];
|
|
11879
|
+
const networkSampleProblems = [
|
|
11880
|
+
{
|
|
11881
|
+
family: "network",
|
|
11882
|
+
name: "Office Seating Feud",
|
|
11883
|
+
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.",
|
|
11884
|
+
nodes: [
|
|
11885
|
+
{
|
|
11886
|
+
id: 0,
|
|
11887
|
+
name: "Ava",
|
|
11888
|
+
x: 20,
|
|
11889
|
+
y: 20
|
|
11890
|
+
},
|
|
11891
|
+
{
|
|
11892
|
+
id: 1,
|
|
11893
|
+
name: "Ben",
|
|
11894
|
+
x: 50,
|
|
11895
|
+
y: 12
|
|
11896
|
+
},
|
|
11897
|
+
{
|
|
11898
|
+
id: 2,
|
|
11899
|
+
name: "Cleo",
|
|
11900
|
+
x: 80,
|
|
11901
|
+
y: 22
|
|
11902
|
+
},
|
|
11903
|
+
{
|
|
11904
|
+
id: 3,
|
|
11905
|
+
name: "Dex",
|
|
11906
|
+
x: 12,
|
|
11907
|
+
y: 50
|
|
11908
|
+
},
|
|
11909
|
+
{
|
|
11910
|
+
id: 4,
|
|
11911
|
+
name: "Elle",
|
|
11912
|
+
x: 42,
|
|
11913
|
+
y: 42
|
|
11914
|
+
},
|
|
11915
|
+
{
|
|
11916
|
+
id: 5,
|
|
11917
|
+
name: "Finn",
|
|
11918
|
+
x: 70,
|
|
11919
|
+
y: 48
|
|
11920
|
+
},
|
|
11921
|
+
{
|
|
11922
|
+
id: 6,
|
|
11923
|
+
name: "Gus",
|
|
11924
|
+
x: 92,
|
|
11925
|
+
y: 55
|
|
11926
|
+
},
|
|
11927
|
+
{
|
|
11928
|
+
id: 7,
|
|
11929
|
+
name: "Hana",
|
|
11930
|
+
x: 25,
|
|
11931
|
+
y: 80
|
|
11932
|
+
},
|
|
11933
|
+
{
|
|
11934
|
+
id: 8,
|
|
11935
|
+
name: "Iggy",
|
|
11936
|
+
x: 55,
|
|
11937
|
+
y: 84
|
|
11938
|
+
},
|
|
11939
|
+
{
|
|
11940
|
+
id: 9,
|
|
11941
|
+
name: "June",
|
|
11942
|
+
x: 82,
|
|
11943
|
+
y: 80
|
|
11944
|
+
}
|
|
11945
|
+
],
|
|
11946
|
+
edges: [
|
|
11947
|
+
{
|
|
11948
|
+
a: 0,
|
|
11949
|
+
b: 1,
|
|
11950
|
+
w: 3
|
|
11951
|
+
},
|
|
11952
|
+
{
|
|
11953
|
+
a: 0,
|
|
11954
|
+
b: 3,
|
|
11955
|
+
w: 1
|
|
11956
|
+
},
|
|
11957
|
+
{
|
|
11958
|
+
a: 0,
|
|
11959
|
+
b: 4,
|
|
11960
|
+
w: 4
|
|
11961
|
+
},
|
|
11962
|
+
{
|
|
11963
|
+
a: 1,
|
|
11964
|
+
b: 2,
|
|
11965
|
+
w: 2
|
|
11966
|
+
},
|
|
11967
|
+
{
|
|
11968
|
+
a: 1,
|
|
11969
|
+
b: 4,
|
|
11970
|
+
w: 5
|
|
11971
|
+
},
|
|
11972
|
+
{
|
|
11973
|
+
a: 2,
|
|
11974
|
+
b: 5,
|
|
11975
|
+
w: 3
|
|
11976
|
+
},
|
|
11977
|
+
{
|
|
11978
|
+
a: 2,
|
|
11979
|
+
b: 6,
|
|
11980
|
+
w: 1
|
|
11981
|
+
},
|
|
11982
|
+
{
|
|
11983
|
+
a: 3,
|
|
11984
|
+
b: 4,
|
|
11985
|
+
w: 2
|
|
11986
|
+
},
|
|
11987
|
+
{
|
|
11988
|
+
a: 3,
|
|
11989
|
+
b: 7,
|
|
11990
|
+
w: 4
|
|
11991
|
+
},
|
|
11992
|
+
{
|
|
11993
|
+
a: 4,
|
|
11994
|
+
b: 5,
|
|
11995
|
+
w: 1
|
|
11996
|
+
},
|
|
11997
|
+
{
|
|
11998
|
+
a: 4,
|
|
11999
|
+
b: 8,
|
|
12000
|
+
w: 3
|
|
12001
|
+
},
|
|
12002
|
+
{
|
|
12003
|
+
a: 5,
|
|
12004
|
+
b: 6,
|
|
12005
|
+
w: 4
|
|
12006
|
+
},
|
|
12007
|
+
{
|
|
12008
|
+
a: 5,
|
|
12009
|
+
b: 9,
|
|
12010
|
+
w: 2
|
|
12011
|
+
},
|
|
12012
|
+
{
|
|
12013
|
+
a: 6,
|
|
12014
|
+
b: 9,
|
|
12015
|
+
w: 5
|
|
12016
|
+
},
|
|
12017
|
+
{
|
|
12018
|
+
a: 7,
|
|
12019
|
+
b: 8,
|
|
12020
|
+
w: 2
|
|
12021
|
+
},
|
|
12022
|
+
{
|
|
12023
|
+
a: 8,
|
|
12024
|
+
b: 9,
|
|
12025
|
+
w: 1
|
|
12026
|
+
}
|
|
12027
|
+
]
|
|
12028
|
+
},
|
|
12029
|
+
{
|
|
12030
|
+
family: "network",
|
|
12031
|
+
name: "Microservice Fault Domains",
|
|
12032
|
+
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.",
|
|
12033
|
+
nodes: [
|
|
12034
|
+
{
|
|
12035
|
+
id: 0,
|
|
12036
|
+
name: "gateway",
|
|
12037
|
+
x: 50,
|
|
12038
|
+
y: 8
|
|
12039
|
+
},
|
|
12040
|
+
{
|
|
12041
|
+
id: 1,
|
|
12042
|
+
name: "auth",
|
|
12043
|
+
x: 25,
|
|
12044
|
+
y: 22
|
|
12045
|
+
},
|
|
12046
|
+
{
|
|
12047
|
+
id: 2,
|
|
12048
|
+
name: "users",
|
|
12049
|
+
x: 75,
|
|
12050
|
+
y: 22
|
|
12051
|
+
},
|
|
12052
|
+
{
|
|
12053
|
+
id: 3,
|
|
12054
|
+
name: "billing",
|
|
12055
|
+
x: 12,
|
|
12056
|
+
y: 46
|
|
12057
|
+
},
|
|
12058
|
+
{
|
|
12059
|
+
id: 4,
|
|
12060
|
+
name: "catalog",
|
|
12061
|
+
x: 40,
|
|
12062
|
+
y: 40
|
|
12063
|
+
},
|
|
12064
|
+
{
|
|
12065
|
+
id: 5,
|
|
12066
|
+
name: "orders",
|
|
12067
|
+
x: 64,
|
|
12068
|
+
y: 42
|
|
12069
|
+
},
|
|
12070
|
+
{
|
|
12071
|
+
id: 6,
|
|
12072
|
+
name: "search",
|
|
12073
|
+
x: 90,
|
|
12074
|
+
y: 44
|
|
12075
|
+
},
|
|
12076
|
+
{
|
|
12077
|
+
id: 7,
|
|
12078
|
+
name: "ledger",
|
|
12079
|
+
x: 20,
|
|
12080
|
+
y: 70
|
|
12081
|
+
},
|
|
12082
|
+
{
|
|
12083
|
+
id: 8,
|
|
12084
|
+
name: "shipping",
|
|
12085
|
+
x: 48,
|
|
12086
|
+
y: 68
|
|
12087
|
+
},
|
|
12088
|
+
{
|
|
12089
|
+
id: 9,
|
|
12090
|
+
name: "notify",
|
|
12091
|
+
x: 74,
|
|
12092
|
+
y: 70
|
|
12093
|
+
},
|
|
12094
|
+
{
|
|
12095
|
+
id: 10,
|
|
12096
|
+
name: "analytics",
|
|
12097
|
+
x: 34,
|
|
12098
|
+
y: 90
|
|
12099
|
+
},
|
|
12100
|
+
{
|
|
12101
|
+
id: 11,
|
|
12102
|
+
name: "ml-recs",
|
|
12103
|
+
x: 66,
|
|
12104
|
+
y: 90
|
|
12105
|
+
}
|
|
12106
|
+
],
|
|
12107
|
+
edges: [
|
|
12108
|
+
{
|
|
12109
|
+
a: 0,
|
|
12110
|
+
b: 1,
|
|
12111
|
+
w: 5
|
|
12112
|
+
},
|
|
12113
|
+
{
|
|
12114
|
+
a: 0,
|
|
12115
|
+
b: 2,
|
|
12116
|
+
w: 4
|
|
12117
|
+
},
|
|
12118
|
+
{
|
|
12119
|
+
a: 0,
|
|
12120
|
+
b: 4,
|
|
12121
|
+
w: 3
|
|
12122
|
+
},
|
|
12123
|
+
{
|
|
12124
|
+
a: 1,
|
|
12125
|
+
b: 2,
|
|
12126
|
+
w: 2
|
|
12127
|
+
},
|
|
12128
|
+
{
|
|
12129
|
+
a: 1,
|
|
12130
|
+
b: 3,
|
|
12131
|
+
w: 3
|
|
12132
|
+
},
|
|
12133
|
+
{
|
|
12134
|
+
a: 2,
|
|
12135
|
+
b: 5,
|
|
12136
|
+
w: 4
|
|
12137
|
+
},
|
|
12138
|
+
{
|
|
12139
|
+
a: 2,
|
|
12140
|
+
b: 6,
|
|
12141
|
+
w: 2
|
|
12142
|
+
},
|
|
12143
|
+
{
|
|
12144
|
+
a: 3,
|
|
12145
|
+
b: 7,
|
|
12146
|
+
w: 5
|
|
12147
|
+
},
|
|
12148
|
+
{
|
|
12149
|
+
a: 4,
|
|
12150
|
+
b: 5,
|
|
12151
|
+
w: 3
|
|
12152
|
+
},
|
|
12153
|
+
{
|
|
12154
|
+
a: 4,
|
|
12155
|
+
b: 6,
|
|
12156
|
+
w: 2
|
|
12157
|
+
},
|
|
12158
|
+
{
|
|
12159
|
+
a: 4,
|
|
12160
|
+
b: 11,
|
|
12161
|
+
w: 2
|
|
12162
|
+
},
|
|
12163
|
+
{
|
|
12164
|
+
a: 5,
|
|
12165
|
+
b: 8,
|
|
12166
|
+
w: 4
|
|
12167
|
+
},
|
|
12168
|
+
{
|
|
12169
|
+
a: 5,
|
|
12170
|
+
b: 9,
|
|
12171
|
+
w: 2
|
|
12172
|
+
},
|
|
12173
|
+
{
|
|
12174
|
+
a: 7,
|
|
12175
|
+
b: 10,
|
|
12176
|
+
w: 2
|
|
12177
|
+
},
|
|
12178
|
+
{
|
|
12179
|
+
a: 8,
|
|
12180
|
+
b: 9,
|
|
12181
|
+
w: 3
|
|
12182
|
+
},
|
|
12183
|
+
{
|
|
12184
|
+
a: 8,
|
|
12185
|
+
b: 10,
|
|
12186
|
+
w: 1
|
|
12187
|
+
},
|
|
12188
|
+
{
|
|
12189
|
+
a: 9,
|
|
12190
|
+
b: 11,
|
|
12191
|
+
w: 2
|
|
12192
|
+
},
|
|
12193
|
+
{
|
|
12194
|
+
a: 10,
|
|
12195
|
+
b: 11,
|
|
12196
|
+
w: 3
|
|
12197
|
+
}
|
|
12198
|
+
]
|
|
12199
|
+
},
|
|
12200
|
+
{
|
|
12201
|
+
family: "network",
|
|
12202
|
+
name: "Power Grid Islanding",
|
|
12203
|
+
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.",
|
|
12204
|
+
nodes: [
|
|
12205
|
+
{
|
|
12206
|
+
id: 0,
|
|
12207
|
+
name: "Plant North",
|
|
12208
|
+
x: 30,
|
|
12209
|
+
y: 10
|
|
12210
|
+
},
|
|
12211
|
+
{
|
|
12212
|
+
id: 1,
|
|
12213
|
+
name: "Plant South",
|
|
12214
|
+
x: 65,
|
|
12215
|
+
y: 90
|
|
12216
|
+
},
|
|
12217
|
+
{
|
|
12218
|
+
id: 2,
|
|
12219
|
+
name: "Sub Alpha",
|
|
12220
|
+
x: 15,
|
|
12221
|
+
y: 35
|
|
12222
|
+
},
|
|
12223
|
+
{
|
|
12224
|
+
id: 3,
|
|
12225
|
+
name: "Sub Bravo",
|
|
12226
|
+
x: 50,
|
|
12227
|
+
y: 28
|
|
12228
|
+
},
|
|
12229
|
+
{
|
|
12230
|
+
id: 4,
|
|
12231
|
+
name: "Sub Charlie",
|
|
12232
|
+
x: 85,
|
|
12233
|
+
y: 32
|
|
12234
|
+
},
|
|
12235
|
+
{
|
|
12236
|
+
id: 5,
|
|
12237
|
+
name: "Sub Delta",
|
|
12238
|
+
x: 25,
|
|
12239
|
+
y: 62
|
|
12240
|
+
},
|
|
12241
|
+
{
|
|
12242
|
+
id: 6,
|
|
12243
|
+
name: "Sub Echo",
|
|
12244
|
+
x: 58,
|
|
12245
|
+
y: 58
|
|
12246
|
+
},
|
|
12247
|
+
{
|
|
12248
|
+
id: 7,
|
|
12249
|
+
name: "Sub Foxtrot",
|
|
12250
|
+
x: 88,
|
|
12251
|
+
y: 66
|
|
12252
|
+
},
|
|
12253
|
+
{
|
|
12254
|
+
id: 8,
|
|
12255
|
+
name: "City West",
|
|
12256
|
+
x: 12,
|
|
12257
|
+
y: 88
|
|
12258
|
+
}
|
|
12259
|
+
],
|
|
12260
|
+
edges: [
|
|
12261
|
+
{
|
|
12262
|
+
a: 0,
|
|
12263
|
+
b: 2,
|
|
12264
|
+
w: 4
|
|
12265
|
+
},
|
|
12266
|
+
{
|
|
12267
|
+
a: 0,
|
|
12268
|
+
b: 3,
|
|
12269
|
+
w: 5
|
|
12270
|
+
},
|
|
12271
|
+
{
|
|
12272
|
+
a: 0,
|
|
12273
|
+
b: 4,
|
|
12274
|
+
w: 3
|
|
12275
|
+
},
|
|
12276
|
+
{
|
|
12277
|
+
a: 2,
|
|
12278
|
+
b: 3,
|
|
12279
|
+
w: 2
|
|
12280
|
+
},
|
|
12281
|
+
{
|
|
12282
|
+
a: 2,
|
|
12283
|
+
b: 5,
|
|
12284
|
+
w: 4
|
|
12285
|
+
},
|
|
12286
|
+
{
|
|
12287
|
+
a: 3,
|
|
12288
|
+
b: 4,
|
|
12289
|
+
w: 2
|
|
12290
|
+
},
|
|
12291
|
+
{
|
|
12292
|
+
a: 3,
|
|
12293
|
+
b: 6,
|
|
12294
|
+
w: 4
|
|
12295
|
+
},
|
|
12296
|
+
{
|
|
12297
|
+
a: 4,
|
|
12298
|
+
b: 7,
|
|
12299
|
+
w: 4
|
|
12300
|
+
},
|
|
12301
|
+
{
|
|
12302
|
+
a: 5,
|
|
12303
|
+
b: 6,
|
|
12304
|
+
w: 3
|
|
12305
|
+
},
|
|
12306
|
+
{
|
|
12307
|
+
a: 5,
|
|
12308
|
+
b: 8,
|
|
12309
|
+
w: 5
|
|
12310
|
+
},
|
|
12311
|
+
{
|
|
12312
|
+
a: 6,
|
|
12313
|
+
b: 7,
|
|
12314
|
+
w: 2
|
|
12315
|
+
},
|
|
12316
|
+
{
|
|
12317
|
+
a: 6,
|
|
12318
|
+
b: 1,
|
|
12319
|
+
w: 5
|
|
12320
|
+
},
|
|
12321
|
+
{
|
|
12322
|
+
a: 7,
|
|
12323
|
+
b: 1,
|
|
12324
|
+
w: 3
|
|
12325
|
+
},
|
|
12326
|
+
{
|
|
12327
|
+
a: 8,
|
|
12328
|
+
b: 1,
|
|
12329
|
+
w: 2
|
|
12330
|
+
}
|
|
12331
|
+
]
|
|
12332
|
+
}
|
|
12333
|
+
];
|
|
12334
|
+
/** Build a jittered grid mesh: cells at (col,row), edges between face-sharing neighbors. */
|
|
12335
|
+
function gridMesh(cols, rows, cellName, weightAt, jitter) {
|
|
12336
|
+
const nodes = [];
|
|
12337
|
+
const padX = 100 / (cols + 1);
|
|
12338
|
+
const padY = 100 / (rows + 1);
|
|
12339
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
12340
|
+
const i = r * cols + c;
|
|
12341
|
+
const { dx, dy } = jitter(i);
|
|
12342
|
+
nodes.push({
|
|
12343
|
+
id: i,
|
|
12344
|
+
name: cellName(i),
|
|
12345
|
+
x: Math.round(padX * (c + 1) + dx),
|
|
12346
|
+
y: Math.round(padY * (r + 1) + dy)
|
|
12347
|
+
});
|
|
12348
|
+
}
|
|
12349
|
+
const edges = [];
|
|
12350
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
12351
|
+
const i = r * cols + c;
|
|
12352
|
+
if (c + 1 < cols) edges.push({
|
|
12353
|
+
a: i,
|
|
12354
|
+
b: i + 1,
|
|
12355
|
+
w: weightAt(i, i + 1)
|
|
12356
|
+
});
|
|
12357
|
+
if (r + 1 < rows) edges.push({
|
|
12358
|
+
a: i,
|
|
12359
|
+
b: i + cols,
|
|
12360
|
+
w: weightAt(i, i + cols)
|
|
12361
|
+
});
|
|
12362
|
+
}
|
|
12363
|
+
return {
|
|
12364
|
+
nodes,
|
|
12365
|
+
edges
|
|
12366
|
+
};
|
|
12367
|
+
}
|
|
12368
|
+
const wobble = (i) => i * 7919 % 11 / 10 - .5;
|
|
12369
|
+
const plate = gridMesh(4, 3, (i) => `cell ${i}`, (a, b) => 1 + (a * 31 + b * 17) % 4, (i) => ({
|
|
12370
|
+
dx: wobble(i) * 6,
|
|
12371
|
+
dy: wobble(i + 3) * 6
|
|
12372
|
+
}));
|
|
12373
|
+
const reservoir = gridMesh(6, 3, (i) => `block ${i}`, (a, b) => 1 + (a * 13 + b * 29) % 5, (i) => ({
|
|
12374
|
+
dx: wobble(i + 1) * 8,
|
|
10945
12375
|
dy: wobble(i + 5) * 8
|
|
10946
12376
|
}));
|
|
10947
12377
|
const blade = gridMesh(6, 4, (i) => `elem ${i}`, (a, b) => 1 + (a * 23 + b * 19) % 4, (i) => ({
|
|
10948
12378
|
dx: wobble(i + 2) * 7,
|
|
10949
12379
|
dy: wobble(i + 7) * 7
|
|
10950
12380
|
}));
|
|
10951
|
-
|
|
12381
|
+
const partitioningSampleProblems = [
|
|
12382
|
+
{
|
|
12383
|
+
family: "partitioning",
|
|
12384
|
+
name: "Cantilever Plate Mesh",
|
|
12385
|
+
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.",
|
|
12386
|
+
nodes: plate.nodes,
|
|
12387
|
+
edges: plate.edges,
|
|
12388
|
+
k: 2,
|
|
12389
|
+
balanceTolerance: 1
|
|
12390
|
+
},
|
|
12391
|
+
{
|
|
12392
|
+
family: "partitioning",
|
|
12393
|
+
name: "Reservoir Grid Sectors",
|
|
12394
|
+
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.",
|
|
12395
|
+
nodes: reservoir.nodes,
|
|
12396
|
+
edges: reservoir.edges,
|
|
12397
|
+
k: 3,
|
|
12398
|
+
balanceTolerance: 1
|
|
12399
|
+
},
|
|
12400
|
+
{
|
|
12401
|
+
family: "partitioning",
|
|
12402
|
+
name: "Turbine Blade Mesh",
|
|
12403
|
+
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.",
|
|
12404
|
+
nodes: blade.nodes,
|
|
12405
|
+
edges: blade.edges,
|
|
12406
|
+
k: 4,
|
|
12407
|
+
balanceTolerance: 1
|
|
12408
|
+
}
|
|
12409
|
+
];
|
|
12410
|
+
const selectionSampleProblems = [
|
|
12411
|
+
{
|
|
12412
|
+
family: "selection",
|
|
12413
|
+
name: "Indie Game Fund",
|
|
12414
|
+
description: "Twelve pitches, a $2M fund, and every project carries risk. Maximize expected portfolio value without betting the firm on moonshots.",
|
|
12415
|
+
budget: 200,
|
|
12416
|
+
riskWeight: 4,
|
|
12417
|
+
items: [
|
|
12418
|
+
{
|
|
12419
|
+
id: 0,
|
|
12420
|
+
name: "Cozy Farm Sim",
|
|
12421
|
+
value: 60,
|
|
12422
|
+
cost: 40,
|
|
12423
|
+
risk: 2
|
|
12424
|
+
},
|
|
12425
|
+
{
|
|
12426
|
+
id: 1,
|
|
12427
|
+
name: "Roguelike Deckbuilder",
|
|
12428
|
+
value: 85,
|
|
12429
|
+
cost: 55,
|
|
12430
|
+
risk: 4
|
|
12431
|
+
},
|
|
12432
|
+
{
|
|
12433
|
+
id: 2,
|
|
12434
|
+
name: "VR Escape Room",
|
|
12435
|
+
value: 95,
|
|
12436
|
+
cost: 70,
|
|
12437
|
+
risk: 8
|
|
12438
|
+
},
|
|
12439
|
+
{
|
|
12440
|
+
id: 3,
|
|
12441
|
+
name: "Pixel Metroidvania",
|
|
12442
|
+
value: 55,
|
|
12443
|
+
cost: 35,
|
|
12444
|
+
risk: 3
|
|
12445
|
+
},
|
|
12446
|
+
{
|
|
12447
|
+
id: 4,
|
|
12448
|
+
name: "MMO Sandbox",
|
|
12449
|
+
value: 160,
|
|
12450
|
+
cost: 120,
|
|
12451
|
+
risk: 10
|
|
12452
|
+
},
|
|
12453
|
+
{
|
|
12454
|
+
id: 5,
|
|
12455
|
+
name: "Mobile Puzzler",
|
|
12456
|
+
value: 35,
|
|
12457
|
+
cost: 18,
|
|
12458
|
+
risk: 1
|
|
12459
|
+
},
|
|
12460
|
+
{
|
|
12461
|
+
id: 6,
|
|
12462
|
+
name: "Co-op Heist Game",
|
|
12463
|
+
value: 75,
|
|
12464
|
+
cost: 50,
|
|
12465
|
+
risk: 5
|
|
12466
|
+
},
|
|
12467
|
+
{
|
|
12468
|
+
id: 7,
|
|
12469
|
+
name: "Narrative Adventure",
|
|
12470
|
+
value: 45,
|
|
12471
|
+
cost: 30,
|
|
12472
|
+
risk: 2
|
|
12473
|
+
},
|
|
12474
|
+
{
|
|
12475
|
+
id: 8,
|
|
12476
|
+
name: "Auto-Battler",
|
|
12477
|
+
value: 50,
|
|
12478
|
+
cost: 28,
|
|
12479
|
+
risk: 4
|
|
12480
|
+
},
|
|
12481
|
+
{
|
|
12482
|
+
id: 9,
|
|
12483
|
+
name: "Speedrun Platformer",
|
|
12484
|
+
value: 30,
|
|
12485
|
+
cost: 15,
|
|
12486
|
+
risk: 2
|
|
12487
|
+
},
|
|
12488
|
+
{
|
|
12489
|
+
id: 10,
|
|
12490
|
+
name: "City Builder",
|
|
12491
|
+
value: 90,
|
|
12492
|
+
cost: 65,
|
|
12493
|
+
risk: 5
|
|
12494
|
+
},
|
|
12495
|
+
{
|
|
12496
|
+
id: 11,
|
|
12497
|
+
name: "Horror Anthology",
|
|
12498
|
+
value: 40,
|
|
12499
|
+
cost: 25,
|
|
12500
|
+
risk: 6
|
|
12501
|
+
}
|
|
12502
|
+
]
|
|
12503
|
+
},
|
|
12504
|
+
{
|
|
12505
|
+
family: "selection",
|
|
12506
|
+
name: "Drone Sensor Loadout",
|
|
12507
|
+
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.",
|
|
12508
|
+
budget: 48,
|
|
12509
|
+
riskWeight: 0,
|
|
12510
|
+
items: [
|
|
12511
|
+
{
|
|
12512
|
+
id: 0,
|
|
12513
|
+
name: "EO Camera",
|
|
12514
|
+
value: 30,
|
|
12515
|
+
cost: 12
|
|
12516
|
+
},
|
|
12517
|
+
{
|
|
12518
|
+
id: 1,
|
|
12519
|
+
name: "IR Thermal",
|
|
12520
|
+
value: 28,
|
|
12521
|
+
cost: 10
|
|
12522
|
+
},
|
|
12523
|
+
{
|
|
12524
|
+
id: 2,
|
|
12525
|
+
name: "LIDAR",
|
|
12526
|
+
value: 35,
|
|
12527
|
+
cost: 18
|
|
12528
|
+
},
|
|
12529
|
+
{
|
|
12530
|
+
id: 3,
|
|
12531
|
+
name: "SIGINT Pod",
|
|
12532
|
+
value: 25,
|
|
12533
|
+
cost: 14
|
|
12534
|
+
},
|
|
12535
|
+
{
|
|
12536
|
+
id: 4,
|
|
12537
|
+
name: "Synthetic Aperture Radar",
|
|
12538
|
+
value: 40,
|
|
12539
|
+
cost: 22
|
|
12540
|
+
},
|
|
12541
|
+
{
|
|
12542
|
+
id: 5,
|
|
12543
|
+
name: "Laser Designator",
|
|
12544
|
+
value: 18,
|
|
12545
|
+
cost: 8
|
|
12546
|
+
},
|
|
12547
|
+
{
|
|
12548
|
+
id: 6,
|
|
12549
|
+
name: "Comms Relay",
|
|
12550
|
+
value: 15,
|
|
12551
|
+
cost: 6
|
|
12552
|
+
},
|
|
12553
|
+
{
|
|
12554
|
+
id: 7,
|
|
12555
|
+
name: "Magnetometer",
|
|
12556
|
+
value: 10,
|
|
12557
|
+
cost: 4
|
|
12558
|
+
},
|
|
12559
|
+
{
|
|
12560
|
+
id: 8,
|
|
12561
|
+
name: "Air Sampler",
|
|
12562
|
+
value: 8,
|
|
12563
|
+
cost: 5
|
|
12564
|
+
},
|
|
12565
|
+
{
|
|
12566
|
+
id: 9,
|
|
12567
|
+
name: "Spare Battery",
|
|
12568
|
+
value: 12,
|
|
12569
|
+
cost: 9
|
|
12570
|
+
}
|
|
12571
|
+
]
|
|
12572
|
+
},
|
|
12573
|
+
{
|
|
12574
|
+
family: "selection",
|
|
12575
|
+
name: "Feature Flag Quarter",
|
|
12576
|
+
description: "Fourteen engineering initiatives, 90 days of team capacity. Each carries delivery risk. Choose the roadmap that maximizes shipped value.",
|
|
12577
|
+
budget: 90,
|
|
12578
|
+
riskWeight: 3,
|
|
12579
|
+
items: [
|
|
12580
|
+
{
|
|
12581
|
+
id: 0,
|
|
12582
|
+
name: "Realtime Collab",
|
|
12583
|
+
value: 55,
|
|
12584
|
+
cost: 30,
|
|
12585
|
+
risk: 6
|
|
12586
|
+
},
|
|
12587
|
+
{
|
|
12588
|
+
id: 1,
|
|
12589
|
+
name: "Mobile Offline Mode",
|
|
12590
|
+
value: 40,
|
|
12591
|
+
cost: 25,
|
|
12592
|
+
risk: 4
|
|
12593
|
+
},
|
|
12594
|
+
{
|
|
12595
|
+
id: 2,
|
|
12596
|
+
name: "SSO Everywhere",
|
|
12597
|
+
value: 35,
|
|
12598
|
+
cost: 15,
|
|
12599
|
+
risk: 2
|
|
12600
|
+
},
|
|
12601
|
+
{
|
|
12602
|
+
id: 3,
|
|
12603
|
+
name: "Billing v2",
|
|
12604
|
+
value: 50,
|
|
12605
|
+
cost: 35,
|
|
12606
|
+
risk: 7
|
|
12607
|
+
},
|
|
12608
|
+
{
|
|
12609
|
+
id: 4,
|
|
12610
|
+
name: "AI Summaries",
|
|
12611
|
+
value: 45,
|
|
12612
|
+
cost: 20,
|
|
12613
|
+
risk: 3
|
|
12614
|
+
},
|
|
12615
|
+
{
|
|
12616
|
+
id: 5,
|
|
12617
|
+
name: "Audit Logs",
|
|
12618
|
+
value: 25,
|
|
12619
|
+
cost: 10,
|
|
12620
|
+
risk: 1
|
|
12621
|
+
},
|
|
12622
|
+
{
|
|
12623
|
+
id: 6,
|
|
12624
|
+
name: "Perf: Cold Start",
|
|
12625
|
+
value: 30,
|
|
12626
|
+
cost: 12,
|
|
12627
|
+
risk: 2
|
|
12628
|
+
},
|
|
12629
|
+
{
|
|
12630
|
+
id: 7,
|
|
12631
|
+
name: "Theme System",
|
|
12632
|
+
value: 15,
|
|
12633
|
+
cost: 8,
|
|
12634
|
+
risk: 1
|
|
12635
|
+
},
|
|
12636
|
+
{
|
|
12637
|
+
id: 8,
|
|
12638
|
+
name: "Webhooks v2",
|
|
12639
|
+
value: 28,
|
|
12640
|
+
cost: 14,
|
|
12641
|
+
risk: 3
|
|
12642
|
+
},
|
|
12643
|
+
{
|
|
12644
|
+
id: 9,
|
|
12645
|
+
name: "Data Residency",
|
|
12646
|
+
value: 38,
|
|
12647
|
+
cost: 28,
|
|
12648
|
+
risk: 5
|
|
12649
|
+
},
|
|
12650
|
+
{
|
|
12651
|
+
id: 10,
|
|
12652
|
+
name: "Search Rewrite",
|
|
12653
|
+
value: 42,
|
|
12654
|
+
cost: 26,
|
|
12655
|
+
risk: 5
|
|
12656
|
+
},
|
|
12657
|
+
{
|
|
12658
|
+
id: 11,
|
|
12659
|
+
name: "Onboarding Flow",
|
|
12660
|
+
value: 22,
|
|
12661
|
+
cost: 9,
|
|
12662
|
+
risk: 1
|
|
12663
|
+
},
|
|
12664
|
+
{
|
|
12665
|
+
id: 12,
|
|
12666
|
+
name: "Plugin SDK",
|
|
12667
|
+
value: 48,
|
|
12668
|
+
cost: 32,
|
|
12669
|
+
risk: 6
|
|
12670
|
+
},
|
|
12671
|
+
{
|
|
12672
|
+
id: 13,
|
|
12673
|
+
name: "A11y Sweep",
|
|
12674
|
+
value: 20,
|
|
12675
|
+
cost: 11,
|
|
12676
|
+
risk: 1
|
|
12677
|
+
}
|
|
12678
|
+
]
|
|
12679
|
+
}
|
|
12680
|
+
];
|
|
12681
|
+
const economicSampleProblems = [
|
|
12682
|
+
{
|
|
12683
|
+
family: "economic",
|
|
12684
|
+
name: "Spectrum Auction",
|
|
12685
|
+
description: "Five regional spectrum licenses, nine carriers bidding on bundles. Sell each license once, maximize treasury revenue — the classic FCC winner-determination problem.",
|
|
12686
|
+
goods: [
|
|
12687
|
+
{
|
|
12688
|
+
id: 0,
|
|
12689
|
+
name: "North Band"
|
|
12690
|
+
},
|
|
12691
|
+
{
|
|
12692
|
+
id: 1,
|
|
12693
|
+
name: "South Band"
|
|
12694
|
+
},
|
|
12695
|
+
{
|
|
12696
|
+
id: 2,
|
|
12697
|
+
name: "East Band"
|
|
12698
|
+
},
|
|
12699
|
+
{
|
|
12700
|
+
id: 3,
|
|
12701
|
+
name: "West Band"
|
|
12702
|
+
},
|
|
12703
|
+
{
|
|
12704
|
+
id: 4,
|
|
12705
|
+
name: "Metro Core"
|
|
12706
|
+
}
|
|
12707
|
+
],
|
|
12708
|
+
bids: [
|
|
12709
|
+
{
|
|
12710
|
+
id: 0,
|
|
12711
|
+
bidder: "AstraTel",
|
|
12712
|
+
price: 120,
|
|
12713
|
+
goodIds: [0, 4]
|
|
12714
|
+
},
|
|
12715
|
+
{
|
|
12716
|
+
id: 1,
|
|
12717
|
+
bidder: "AstraTel",
|
|
12718
|
+
price: 60,
|
|
12719
|
+
goodIds: [0]
|
|
12720
|
+
},
|
|
12721
|
+
{
|
|
12722
|
+
id: 2,
|
|
12723
|
+
bidder: "Borealis",
|
|
12724
|
+
price: 140,
|
|
12725
|
+
goodIds: [1, 2]
|
|
12726
|
+
},
|
|
12727
|
+
{
|
|
12728
|
+
id: 3,
|
|
12729
|
+
bidder: "Borealis",
|
|
12730
|
+
price: 65,
|
|
12731
|
+
goodIds: [1]
|
|
12732
|
+
},
|
|
12733
|
+
{
|
|
12734
|
+
id: 4,
|
|
12735
|
+
bidder: "CometNet",
|
|
12736
|
+
price: 95,
|
|
12737
|
+
goodIds: [2, 3]
|
|
12738
|
+
},
|
|
12739
|
+
{
|
|
12740
|
+
id: 5,
|
|
12741
|
+
bidder: "CometNet",
|
|
12742
|
+
price: 50,
|
|
12743
|
+
goodIds: [3]
|
|
12744
|
+
},
|
|
12745
|
+
{
|
|
12746
|
+
id: 6,
|
|
12747
|
+
bidder: "DyneMobile",
|
|
12748
|
+
price: 180,
|
|
12749
|
+
goodIds: [
|
|
12750
|
+
0,
|
|
12751
|
+
1,
|
|
12752
|
+
4
|
|
12753
|
+
]
|
|
12754
|
+
},
|
|
12755
|
+
{
|
|
12756
|
+
id: 7,
|
|
12757
|
+
bidder: "Equinox",
|
|
12758
|
+
price: 75,
|
|
12759
|
+
goodIds: [4]
|
|
12760
|
+
},
|
|
12761
|
+
{
|
|
12762
|
+
id: 8,
|
|
12763
|
+
bidder: "Equinox",
|
|
12764
|
+
price: 110,
|
|
12765
|
+
goodIds: [2, 4]
|
|
12766
|
+
}
|
|
12767
|
+
]
|
|
12768
|
+
},
|
|
12769
|
+
{
|
|
12770
|
+
family: "economic",
|
|
12771
|
+
name: "Cloud Spot Market",
|
|
12772
|
+
description: "Six GPU node pools, ten batch workloads bidding for exclusive overnight windows. Maximize the operator's take across all-or-nothing reservations.",
|
|
12773
|
+
goods: [
|
|
12774
|
+
{
|
|
12775
|
+
id: 0,
|
|
12776
|
+
name: "A100 Pool 1"
|
|
12777
|
+
},
|
|
12778
|
+
{
|
|
12779
|
+
id: 1,
|
|
12780
|
+
name: "A100 Pool 2"
|
|
12781
|
+
},
|
|
12782
|
+
{
|
|
12783
|
+
id: 2,
|
|
12784
|
+
name: "H100 Pool 1"
|
|
12785
|
+
},
|
|
12786
|
+
{
|
|
12787
|
+
id: 3,
|
|
12788
|
+
name: "H100 Pool 2"
|
|
12789
|
+
},
|
|
12790
|
+
{
|
|
12791
|
+
id: 4,
|
|
12792
|
+
name: "TPU Pod"
|
|
12793
|
+
},
|
|
12794
|
+
{
|
|
12795
|
+
id: 5,
|
|
12796
|
+
name: "CPU Farm"
|
|
12797
|
+
}
|
|
12798
|
+
],
|
|
12799
|
+
bids: [
|
|
12800
|
+
{
|
|
12801
|
+
id: 0,
|
|
12802
|
+
bidder: "BioFold Labs",
|
|
12803
|
+
price: 90,
|
|
12804
|
+
goodIds: [2, 3]
|
|
12805
|
+
},
|
|
12806
|
+
{
|
|
12807
|
+
id: 1,
|
|
12808
|
+
bidder: "BioFold Labs",
|
|
12809
|
+
price: 48,
|
|
12810
|
+
goodIds: [2]
|
|
12811
|
+
},
|
|
12812
|
+
{
|
|
12813
|
+
id: 2,
|
|
12814
|
+
bidder: "RenderWorks",
|
|
12815
|
+
price: 55,
|
|
12816
|
+
goodIds: [0, 1]
|
|
12817
|
+
},
|
|
12818
|
+
{
|
|
12819
|
+
id: 3,
|
|
12820
|
+
bidder: "RenderWorks",
|
|
12821
|
+
price: 30,
|
|
12822
|
+
goodIds: [0]
|
|
12823
|
+
},
|
|
12824
|
+
{
|
|
12825
|
+
id: 4,
|
|
12826
|
+
bidder: "LLM Foundry",
|
|
12827
|
+
price: 130,
|
|
12828
|
+
goodIds: [
|
|
12829
|
+
2,
|
|
12830
|
+
3,
|
|
12831
|
+
4
|
|
12832
|
+
]
|
|
12833
|
+
},
|
|
12834
|
+
{
|
|
12835
|
+
id: 5,
|
|
12836
|
+
bidder: "LLM Foundry",
|
|
12837
|
+
price: 70,
|
|
12838
|
+
goodIds: [4]
|
|
12839
|
+
},
|
|
12840
|
+
{
|
|
12841
|
+
id: 6,
|
|
12842
|
+
bidder: "QuantSim",
|
|
12843
|
+
price: 42,
|
|
12844
|
+
goodIds: [1, 5]
|
|
12845
|
+
},
|
|
12846
|
+
{
|
|
12847
|
+
id: 7,
|
|
12848
|
+
bidder: "QuantSim",
|
|
12849
|
+
price: 22,
|
|
12850
|
+
goodIds: [5]
|
|
12851
|
+
},
|
|
12852
|
+
{
|
|
12853
|
+
id: 8,
|
|
12854
|
+
bidder: "WeatherNet",
|
|
12855
|
+
price: 65,
|
|
12856
|
+
goodIds: [0, 5]
|
|
12857
|
+
},
|
|
12858
|
+
{
|
|
12859
|
+
id: 9,
|
|
12860
|
+
bidder: "GenomeScan",
|
|
12861
|
+
price: 38,
|
|
12862
|
+
goodIds: [1]
|
|
12863
|
+
}
|
|
12864
|
+
]
|
|
12865
|
+
},
|
|
12866
|
+
{
|
|
12867
|
+
family: "economic",
|
|
12868
|
+
name: "Stadium Sponsorship Bundle",
|
|
12869
|
+
description: "Seven sponsorship assets — naming rights, jersey patch, tunnel club — and eight brands bidding on combinations. One sale per asset; maximize the franchise's deal sheet.",
|
|
12870
|
+
goods: [
|
|
12871
|
+
{
|
|
12872
|
+
id: 0,
|
|
12873
|
+
name: "Naming Rights"
|
|
12874
|
+
},
|
|
12875
|
+
{
|
|
12876
|
+
id: 1,
|
|
12877
|
+
name: "Jersey Patch"
|
|
12878
|
+
},
|
|
12879
|
+
{
|
|
12880
|
+
id: 2,
|
|
12881
|
+
name: "Tunnel Club"
|
|
12882
|
+
},
|
|
12883
|
+
{
|
|
12884
|
+
id: 3,
|
|
12885
|
+
name: "Scoreboard"
|
|
12886
|
+
},
|
|
12887
|
+
{
|
|
12888
|
+
id: 4,
|
|
12889
|
+
name: "Practice Facility"
|
|
12890
|
+
},
|
|
12891
|
+
{
|
|
12892
|
+
id: 5,
|
|
12893
|
+
name: "Beer Pouring Rights"
|
|
12894
|
+
},
|
|
12895
|
+
{
|
|
12896
|
+
id: 6,
|
|
12897
|
+
name: "Halftime Show"
|
|
12898
|
+
}
|
|
12899
|
+
],
|
|
12900
|
+
bids: [
|
|
12901
|
+
{
|
|
12902
|
+
id: 0,
|
|
12903
|
+
bidder: "Vertex Air",
|
|
12904
|
+
price: 200,
|
|
12905
|
+
goodIds: [0, 3]
|
|
12906
|
+
},
|
|
12907
|
+
{
|
|
12908
|
+
id: 1,
|
|
12909
|
+
bidder: "Vertex Air",
|
|
12910
|
+
price: 130,
|
|
12911
|
+
goodIds: [0]
|
|
12912
|
+
},
|
|
12913
|
+
{
|
|
12914
|
+
id: 2,
|
|
12915
|
+
bidder: "Cascade Beer",
|
|
12916
|
+
price: 85,
|
|
12917
|
+
goodIds: [5, 6]
|
|
12918
|
+
},
|
|
12919
|
+
{
|
|
12920
|
+
id: 3,
|
|
12921
|
+
bidder: "Cascade Beer",
|
|
12922
|
+
price: 55,
|
|
12923
|
+
goodIds: [5]
|
|
12924
|
+
},
|
|
12925
|
+
{
|
|
12926
|
+
id: 4,
|
|
12927
|
+
bidder: "NovaBank",
|
|
12928
|
+
price: 150,
|
|
12929
|
+
goodIds: [1, 2]
|
|
12930
|
+
},
|
|
12931
|
+
{
|
|
12932
|
+
id: 5,
|
|
12933
|
+
bidder: "NovaBank",
|
|
12934
|
+
price: 80,
|
|
12935
|
+
goodIds: [1]
|
|
12936
|
+
},
|
|
12937
|
+
{
|
|
12938
|
+
id: 6,
|
|
12939
|
+
bidder: "GrindHouse Coffee",
|
|
12940
|
+
price: 45,
|
|
12941
|
+
goodIds: [2]
|
|
12942
|
+
},
|
|
12943
|
+
{
|
|
12944
|
+
id: 7,
|
|
12945
|
+
bidder: "Pixel Motors",
|
|
12946
|
+
price: 110,
|
|
12947
|
+
goodIds: [3, 4]
|
|
12948
|
+
},
|
|
12949
|
+
{
|
|
12950
|
+
id: 8,
|
|
12951
|
+
bidder: "Pixel Motors",
|
|
12952
|
+
price: 60,
|
|
12953
|
+
goodIds: [4]
|
|
12954
|
+
},
|
|
12955
|
+
{
|
|
12956
|
+
id: 9,
|
|
12957
|
+
bidder: "StreamCo",
|
|
12958
|
+
price: 70,
|
|
12959
|
+
goodIds: [6]
|
|
12960
|
+
}
|
|
12961
|
+
]
|
|
12962
|
+
}
|
|
12963
|
+
];
|
|
12964
|
+
const continuousSampleProblems = [
|
|
12965
|
+
{
|
|
12966
|
+
family: "continuous",
|
|
12967
|
+
name: "Reactor Temperature Tuning",
|
|
12968
|
+
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.",
|
|
12969
|
+
functionId: "wavy-bowl",
|
|
12970
|
+
dims: 1,
|
|
12971
|
+
lo: -10,
|
|
12972
|
+
hi: 10,
|
|
12973
|
+
paramNames: ["temperature offset"]
|
|
12974
|
+
},
|
|
12975
|
+
{
|
|
12976
|
+
family: "continuous",
|
|
12977
|
+
name: "Antenna Sweet Spot",
|
|
12978
|
+
description: "Two placement coordinates on the roof; signal interference makes four equally good mounting points. Himmelblau's function — which optimum will your solver find?",
|
|
12979
|
+
functionId: "himmelblau",
|
|
12980
|
+
dims: 2,
|
|
12981
|
+
lo: -6,
|
|
12982
|
+
hi: 6,
|
|
12983
|
+
paramNames: ["east-west", "north-south"]
|
|
12984
|
+
},
|
|
12985
|
+
{
|
|
12986
|
+
family: "continuous",
|
|
12987
|
+
name: "Hyperparameter Canyon",
|
|
12988
|
+
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.",
|
|
12989
|
+
functionId: "rastrigin",
|
|
12990
|
+
dims: 2,
|
|
12991
|
+
lo: -5.12,
|
|
12992
|
+
hi: 5.12,
|
|
12993
|
+
paramNames: ["learning rate (scaled)", "regularization (scaled)"]
|
|
12994
|
+
}
|
|
12995
|
+
];
|
|
10952
12996
|
var QuboBuilder = class {
|
|
10953
12997
|
size;
|
|
10954
12998
|
q = /* @__PURE__ */ new Map();
|
|
@@ -11280,8 +13324,270 @@ function continuousToy() {
|
|
|
11280
13324
|
};
|
|
11281
13325
|
}
|
|
11282
13326
|
schedulingToy(), routingToy(), packingToy(), assignmentToy(), networkToy(), partitioningToy(), selectionToy(), economicToy(), continuousToy();
|
|
13327
|
+
/**
|
|
13328
|
+
* Per-family problem Zod schemas — the validation foundation for custom problem
|
|
13329
|
+
* EDITING (FamilyProblemEditor validates JSON against these) and LLM FORMULATION
|
|
13330
|
+
* (quantum_formulate validates the model's output before surfacing it).
|
|
13331
|
+
*
|
|
13332
|
+
* Authored to match the hand-written interfaces in `./types`; a type-level check
|
|
13333
|
+
* at the bottom keeps the two in sync. The family discriminant lets a single
|
|
13334
|
+
* editor/tool dispatch across all eight shapes.
|
|
13335
|
+
*/
|
|
13336
|
+
const named = {
|
|
13337
|
+
id: z.number().int(),
|
|
13338
|
+
name: z.string().min(1)
|
|
13339
|
+
};
|
|
13340
|
+
const xy = {
|
|
13341
|
+
x: z.number(),
|
|
13342
|
+
y: z.number()
|
|
13343
|
+
};
|
|
13344
|
+
const edge = z.object({
|
|
13345
|
+
a: z.number().int().nonnegative(),
|
|
13346
|
+
b: z.number().int().nonnegative(),
|
|
13347
|
+
w: z.number().optional()
|
|
13348
|
+
});
|
|
13349
|
+
const RoutingProblemSchema = z.object({
|
|
13350
|
+
family: z.literal("routing"),
|
|
13351
|
+
name: z.string().min(1),
|
|
13352
|
+
description: z.string().optional(),
|
|
13353
|
+
nodes: z.array(z.object({
|
|
13354
|
+
...named,
|
|
13355
|
+
...xy
|
|
13356
|
+
})).min(2)
|
|
13357
|
+
});
|
|
13358
|
+
const PackingProblemSchema = z.object({
|
|
13359
|
+
family: z.literal("packing"),
|
|
13360
|
+
name: z.string().min(1),
|
|
13361
|
+
description: z.string().optional(),
|
|
13362
|
+
items: z.array(z.object({
|
|
13363
|
+
...named,
|
|
13364
|
+
size: z.number().positive()
|
|
13365
|
+
})).min(1),
|
|
13366
|
+
binCapacity: z.number().positive(),
|
|
13367
|
+
binLabel: z.string().min(1)
|
|
13368
|
+
});
|
|
13369
|
+
const AssignmentProblemSchema = z.object({
|
|
13370
|
+
family: z.literal("assignment"),
|
|
13371
|
+
name: z.string().min(1),
|
|
13372
|
+
description: z.string().optional(),
|
|
13373
|
+
agents: z.array(z.object(named)).min(1),
|
|
13374
|
+
tasks: z.array(z.object(named)).min(1),
|
|
13375
|
+
cost: z.array(z.array(z.number())).min(1)
|
|
13376
|
+
});
|
|
13377
|
+
const NetworkProblemSchema = z.object({
|
|
13378
|
+
family: z.literal("network"),
|
|
13379
|
+
name: z.string().min(1),
|
|
13380
|
+
description: z.string().optional(),
|
|
13381
|
+
nodes: z.array(z.object({
|
|
13382
|
+
...named,
|
|
13383
|
+
...xy
|
|
13384
|
+
})).min(1),
|
|
13385
|
+
edges: z.array(edge)
|
|
13386
|
+
});
|
|
13387
|
+
const PartitionProblemSchema = z.object({
|
|
13388
|
+
family: z.literal("partitioning"),
|
|
13389
|
+
name: z.string().min(1),
|
|
13390
|
+
description: z.string().optional(),
|
|
13391
|
+
nodes: z.array(z.object({
|
|
13392
|
+
...named,
|
|
13393
|
+
...xy
|
|
13394
|
+
})).min(1),
|
|
13395
|
+
edges: z.array(edge),
|
|
13396
|
+
k: z.number().int().min(2),
|
|
13397
|
+
balanceTolerance: z.number().int().nonnegative().optional()
|
|
13398
|
+
});
|
|
13399
|
+
const SelectionProblemSchema = z.object({
|
|
13400
|
+
family: z.literal("selection"),
|
|
13401
|
+
name: z.string().min(1),
|
|
13402
|
+
description: z.string().optional(),
|
|
13403
|
+
items: z.array(z.object({
|
|
13404
|
+
...named,
|
|
13405
|
+
value: z.number(),
|
|
13406
|
+
cost: z.number().positive(),
|
|
13407
|
+
risk: z.number().optional()
|
|
13408
|
+
})).min(1),
|
|
13409
|
+
budget: z.number().positive(),
|
|
13410
|
+
riskWeight: z.number().optional()
|
|
13411
|
+
});
|
|
13412
|
+
const EconomicProblemSchema = z.object({
|
|
13413
|
+
family: z.literal("economic"),
|
|
13414
|
+
name: z.string().min(1),
|
|
13415
|
+
description: z.string().optional(),
|
|
13416
|
+
goods: z.array(z.object(named)).min(1),
|
|
13417
|
+
bids: z.array(z.object({
|
|
13418
|
+
id: z.number().int(),
|
|
13419
|
+
bidder: z.string().min(1),
|
|
13420
|
+
price: z.number(),
|
|
13421
|
+
goodIds: z.array(z.number().int().nonnegative())
|
|
13422
|
+
})).min(1)
|
|
13423
|
+
});
|
|
13424
|
+
const ContinuousProblemSchema = z.object({
|
|
13425
|
+
family: z.literal("continuous"),
|
|
13426
|
+
name: z.string().min(1),
|
|
13427
|
+
description: z.string().optional(),
|
|
13428
|
+
functionId: z.enum([
|
|
13429
|
+
"rastrigin",
|
|
13430
|
+
"ackley",
|
|
13431
|
+
"himmelblau",
|
|
13432
|
+
"wavy-bowl"
|
|
13433
|
+
]),
|
|
13434
|
+
dims: z.union([z.literal(1), z.literal(2)]),
|
|
13435
|
+
lo: z.number(),
|
|
13436
|
+
hi: z.number(),
|
|
13437
|
+
paramNames: z.array(z.string())
|
|
13438
|
+
});
|
|
13439
|
+
z.discriminatedUnion("family", [
|
|
13440
|
+
RoutingProblemSchema,
|
|
13441
|
+
PackingProblemSchema,
|
|
13442
|
+
AssignmentProblemSchema,
|
|
13443
|
+
NetworkProblemSchema,
|
|
13444
|
+
PartitionProblemSchema,
|
|
13445
|
+
SelectionProblemSchema,
|
|
13446
|
+
EconomicProblemSchema,
|
|
13447
|
+
ContinuousProblemSchema
|
|
13448
|
+
]);
|
|
13449
|
+
const refineAssignment = (p, ctx) => {
|
|
13450
|
+
if (p.cost.length !== p.agents.length) ctx.addIssue({
|
|
13451
|
+
code: z.ZodIssueCode.custom,
|
|
13452
|
+
path: ["cost"],
|
|
13453
|
+
message: `cost must have one row per agent (${p.agents.length}); got ${p.cost.length}`
|
|
13454
|
+
});
|
|
13455
|
+
p.cost.forEach((row, i) => {
|
|
13456
|
+
if (row.length !== p.tasks.length) ctx.addIssue({
|
|
13457
|
+
code: z.ZodIssueCode.custom,
|
|
13458
|
+
path: ["cost", i],
|
|
13459
|
+
message: `cost row ${i} must have one column per task (${p.tasks.length}); got ${row.length}`
|
|
13460
|
+
});
|
|
13461
|
+
});
|
|
13462
|
+
};
|
|
13463
|
+
const refineEdgeBounds = (p, ctx) => {
|
|
13464
|
+
const n = p.nodes.length;
|
|
13465
|
+
p.edges.forEach((e, i) => {
|
|
13466
|
+
if (e.a >= n || e.b >= n) ctx.addIssue({
|
|
13467
|
+
code: z.ZodIssueCode.custom,
|
|
13468
|
+
path: ["edges", i],
|
|
13469
|
+
message: `edge endpoints must reference nodes 0..${n - 1}; got a=${e.a}, b=${e.b}`
|
|
13470
|
+
});
|
|
13471
|
+
});
|
|
13472
|
+
};
|
|
13473
|
+
const refineEconomic = (p, ctx) => {
|
|
13474
|
+
const n = p.goods.length;
|
|
13475
|
+
p.bids.forEach((bid, i) => {
|
|
13476
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13477
|
+
bid.goodIds.forEach((g, j) => {
|
|
13478
|
+
if (g >= n) ctx.addIssue({
|
|
13479
|
+
code: z.ZodIssueCode.custom,
|
|
13480
|
+
path: [
|
|
13481
|
+
"bids",
|
|
13482
|
+
i,
|
|
13483
|
+
"goodIds",
|
|
13484
|
+
j
|
|
13485
|
+
],
|
|
13486
|
+
message: `goodId must reference goods 0..${n - 1}; got ${g}`
|
|
13487
|
+
});
|
|
13488
|
+
if (seen.has(g)) ctx.addIssue({
|
|
13489
|
+
code: z.ZodIssueCode.custom,
|
|
13490
|
+
path: [
|
|
13491
|
+
"bids",
|
|
13492
|
+
i,
|
|
13493
|
+
"goodIds",
|
|
13494
|
+
j
|
|
13495
|
+
],
|
|
13496
|
+
message: `duplicate goodId ${g} in bid ${i}`
|
|
13497
|
+
});
|
|
13498
|
+
seen.add(g);
|
|
13499
|
+
});
|
|
13500
|
+
});
|
|
13501
|
+
};
|
|
13502
|
+
/** Per-family schema lookup — the editor/tool pick the right one by familyId. */
|
|
13503
|
+
const FAMILY_PROBLEM_SCHEMAS = {
|
|
13504
|
+
routing: RoutingProblemSchema,
|
|
13505
|
+
packing: PackingProblemSchema,
|
|
13506
|
+
assignment: AssignmentProblemSchema.superRefine(refineAssignment),
|
|
13507
|
+
network: NetworkProblemSchema.superRefine(refineEdgeBounds),
|
|
13508
|
+
partitioning: PartitionProblemSchema.superRefine(refineEdgeBounds),
|
|
13509
|
+
selection: SelectionProblemSchema,
|
|
13510
|
+
economic: EconomicProblemSchema.superRefine(refineEconomic),
|
|
13511
|
+
continuous: ContinuousProblemSchema
|
|
13512
|
+
};
|
|
13513
|
+
const FAMILY_SAMPLE_PROBLEMS = {
|
|
13514
|
+
routing: routingSampleProblems,
|
|
13515
|
+
packing: packingSampleProblems,
|
|
13516
|
+
assignment: assignmentSampleProblems,
|
|
13517
|
+
network: networkSampleProblems,
|
|
13518
|
+
partitioning: partitioningSampleProblems,
|
|
13519
|
+
selection: selectionSampleProblems,
|
|
13520
|
+
economic: economicSampleProblems,
|
|
13521
|
+
continuous: continuousSampleProblems
|
|
13522
|
+
};
|
|
11283
13523
|
//#endregion
|
|
11284
|
-
//#region ../../b4m-core/services/dist/tools-
|
|
13524
|
+
//#region ../../b4m-core/services/dist/tools-D9uncH3M.mjs
|
|
13525
|
+
const createFabFileSchema = z.object({
|
|
13526
|
+
fileName: z.string(),
|
|
13527
|
+
mimeType: z.string(),
|
|
13528
|
+
fileSize: z.number(),
|
|
13529
|
+
type: z.enum(KnowledgeType),
|
|
13530
|
+
content: z.union([z.string(), z.instanceof(Buffer)]).optional(),
|
|
13531
|
+
organizationId: z.string().optional(),
|
|
13532
|
+
/**
|
|
13533
|
+
* Content type of the file
|
|
13534
|
+
* @example 'text/markdown'
|
|
13535
|
+
* @example 'application/pdf'
|
|
13536
|
+
* @example 'application/octet-stream' for binary files
|
|
13537
|
+
*/
|
|
13538
|
+
contentType: z.string().optional(),
|
|
13539
|
+
public: z.boolean().optional(),
|
|
13540
|
+
prefix: z.string().optional(),
|
|
13541
|
+
system: z.boolean().optional(),
|
|
13542
|
+
tags: z.array(z.object({
|
|
13543
|
+
name: z.string(),
|
|
13544
|
+
strength: z.number()
|
|
13545
|
+
})).optional(),
|
|
13546
|
+
systemPriority: z.number().optional(),
|
|
13547
|
+
sessionId: z.string().optional(),
|
|
13548
|
+
contentHash: z.string().optional(),
|
|
13549
|
+
batchId: z.string().optional(),
|
|
13550
|
+
relativePath: z.string().optional()
|
|
13551
|
+
});
|
|
13552
|
+
const DEFAULT_MAX_FILE_SIZE = 20;
|
|
13553
|
+
const DEFAULT_EXPIRE_IN_SECONDS = 3600 * 24 * 5;
|
|
13554
|
+
const createFabFile = async (userId, parameters, { db, storage }) => {
|
|
13555
|
+
const params = secureParameters(parameters, createFabFileSchema);
|
|
13556
|
+
const user = await db.users.findById(userId);
|
|
13557
|
+
if (!user) throw new BadRequestError("User not found");
|
|
13558
|
+
let ext = "";
|
|
13559
|
+
let mimeType = parameters.mimeType;
|
|
13560
|
+
ext = getFileExtension(params.fileName);
|
|
13561
|
+
mimeType = params.mimeType || getMimeTypeByExtension(ext);
|
|
13562
|
+
if (!mimeType) mimeType = SupportedFabFileMimeTypes.TXT_PLAIN;
|
|
13563
|
+
if (!Object.values(SupportedFabFileMimeTypes).some((type) => type === mimeType)) throw new BadRequestError(`File type ${mimeType} is not supported`);
|
|
13564
|
+
let filePath = params.prefix ? `${params.prefix}/` : "";
|
|
13565
|
+
filePath += `${v4()}${ext ? `.${ext}` : ".txt"}`;
|
|
13566
|
+
const maxFileSize = getSettingsValue("MaxFileSize", await getSettingsMap(db), DEFAULT_MAX_FILE_SIZE) * 1024 * 1024;
|
|
13567
|
+
if (params.fileSize >= maxFileSize) throw new BadRequestError("File size exceeds maximum file size");
|
|
13568
|
+
await checkStorageLimitForFile(user, params.fileSize, params.organizationId, db.organizations?.findById);
|
|
13569
|
+
const buildData = {
|
|
13570
|
+
userId,
|
|
13571
|
+
...params,
|
|
13572
|
+
mimeType,
|
|
13573
|
+
filePath,
|
|
13574
|
+
users: [],
|
|
13575
|
+
groups: [],
|
|
13576
|
+
isGlobalRead: false,
|
|
13577
|
+
isGlobalWrite: false,
|
|
13578
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
13579
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
13580
|
+
};
|
|
13581
|
+
if (params.content) {
|
|
13582
|
+
await storage.upload(filePath, params.content, {
|
|
13583
|
+
ContentType: params.contentType,
|
|
13584
|
+
ContentLength: params.fileSize
|
|
13585
|
+
});
|
|
13586
|
+
buildData.fileUrl = await storage.generateSignedUrl(filePath, DEFAULT_EXPIRE_IN_SECONDS, "get");
|
|
13587
|
+
buildData.fileUrlExpireAt = new Date(Date.now() + DEFAULT_EXPIRE_IN_SECONDS * 1e3);
|
|
13588
|
+
} else buildData.presignedUrl = await storage.generateSignedUrl(filePath, 600, "put");
|
|
13589
|
+
return await db.fabFiles.create(buildData);
|
|
13590
|
+
};
|
|
11285
13591
|
async function performDeepResearch(context, params, config = {}) {
|
|
11286
13592
|
const maxDepth = config.maxDepth || 7;
|
|
11287
13593
|
const duration = config.duration || 4.5;
|
|
@@ -11642,18 +13948,22 @@ const deepResearchTool = {
|
|
|
11642
13948
|
async function getDynamicDataLakeAccess(context) {
|
|
11643
13949
|
const userTags = context.user.tags || [];
|
|
11644
13950
|
const entitlementKeys = context.entitlementKeys ?? [];
|
|
13951
|
+
const organizationId = context.user.organizationId ? String(context.user.organizationId) : void 0;
|
|
13952
|
+
const userId = context.user.id ? String(context.user.id) : void 0;
|
|
11645
13953
|
let dynamicDataLakes;
|
|
11646
13954
|
if (context.db.dataLakes) try {
|
|
11647
|
-
dynamicDataLakes = (await context.db.dataLakes.findActiveByUserTagsAndEntitlements(userTags, entitlementKeys)).map(toDataLakeConfig);
|
|
13955
|
+
dynamicDataLakes = (await context.db.dataLakes.findActiveByUserTagsAndEntitlements(userTags, entitlementKeys, organizationId, userId)).map(toDataLakeConfig);
|
|
11648
13956
|
} catch {}
|
|
11649
13957
|
const accessibleLakes = getAccessibleDataLakes(userTags, dynamicDataLakes, entitlementKeys);
|
|
13958
|
+
const dynamicIds = new Set((dynamicDataLakes ?? []).map((d) => d.id));
|
|
11650
13959
|
return {
|
|
11651
13960
|
dataLakeTags: accessibleLakes.map((dl) => dl.datalakeTag),
|
|
11652
|
-
dataLakeTagPrefixes: accessibleLakes.map((dl) => dl.fileTagPrefix)
|
|
13961
|
+
dataLakeTagPrefixes: accessibleLakes.filter((dl) => !dynamicIds.has(dl.id)).map((dl) => dl.fileTagPrefix),
|
|
13962
|
+
scopedTagPrefixes: accessibleLakes.filter((dl) => dynamicIds.has(dl.id)).map((dl) => dl.fileTagPrefix)
|
|
11653
13963
|
};
|
|
11654
13964
|
}
|
|
11655
13965
|
async function semanticDataLakeSearch(params, adapters) {
|
|
11656
|
-
const { userId, userGroups = [], query, tags = [], topK = 10, minScore = 0, embeddingModel, apiKeyTable, dataLakeTags, dataLakeTagPrefixes, maxFiles = 2e3, chunkLoadCap = 1e4, logger } = params;
|
|
13966
|
+
const { userId, userGroups = [], query, tags = [], topK = 10, minScore = 0, embeddingModel, apiKeyTable, dataLakeTags, dataLakeTagPrefixes, scopedTagPrefixes = [], maxFiles = 2e3, chunkLoadCap = 1e4, logger } = params;
|
|
11657
13967
|
const empty = {
|
|
11658
13968
|
results: [],
|
|
11659
13969
|
totalChunksSearched: 0,
|
|
@@ -11687,6 +13997,7 @@ async function semanticDataLakeSearch(params, adapters) {
|
|
|
11687
13997
|
userGroups,
|
|
11688
13998
|
dataLakeTags,
|
|
11689
13999
|
dataLakeTagPrefixes,
|
|
14000
|
+
scopedTagPrefixes,
|
|
11690
14001
|
excludeContent: true
|
|
11691
14002
|
});
|
|
11692
14003
|
const fileIds = fileSearch.data.map((f) => f.id);
|
|
@@ -11792,6 +14103,60 @@ const weatherTool = {
|
|
|
11792
14103
|
}
|
|
11793
14104
|
})
|
|
11794
14105
|
};
|
|
14106
|
+
/**
|
|
14107
|
+
* Persist a tool-generated file (image, Excel, etc.) as a session-scoped FabFile so it
|
|
14108
|
+
* shows up in the Knowledge Base / WorkBench and is downloadable after the run.
|
|
14109
|
+
*
|
|
14110
|
+
* Why this exists: file-generating tools historically only pushed the generated filename
|
|
14111
|
+
* into `quest.images` via `statusUpdate`, which (a) renders fine inline for images but is
|
|
14112
|
+
* a broken `<img>` for non-image files like .xlsx, and (b) was never recorded as a FabFile,
|
|
14113
|
+
* so nothing the user can browse referenced it — the file was effectively orphaned in the
|
|
14114
|
+
* generated-content bucket (see prod session 6a41abae…, where 4 agent-generated images were
|
|
14115
|
+
* lost entirely). Creating a FabFile with `sessionId` set makes the file appear in the
|
|
14116
|
+
* Knowledge Viewer via `useGetFabFilesBySessionId` → `useMessageFiles`.
|
|
14117
|
+
*
|
|
14118
|
+
* Best-effort: a failure here (missing adapters, storage quota, unsupported mime) must never
|
|
14119
|
+
* break the tool itself — the inline `quest.images` render still works. We log and swallow.
|
|
14120
|
+
*
|
|
14121
|
+
* The file is re-uploaded into the FabFile bucket (`context.storage`) because the Knowledge
|
|
14122
|
+
* Viewer reads FabFiles through their signed `fileUrl`, which is issued against that bucket.
|
|
14123
|
+
*/
|
|
14124
|
+
async function persistGeneratedFileAsFabFile(context, file) {
|
|
14125
|
+
const { sessionId, userId, db, storage, logger } = context;
|
|
14126
|
+
if (!sessionId) return;
|
|
14127
|
+
if (!db.fabfiles || !db.users || !db.adminSettings) {
|
|
14128
|
+
logger.warn("[persistGeneratedFileAsFabFile] missing db adapters — skipping FabFile persist");
|
|
14129
|
+
return;
|
|
14130
|
+
}
|
|
14131
|
+
try {
|
|
14132
|
+
await createFabFile(userId, {
|
|
14133
|
+
fileName: file.fileName,
|
|
14134
|
+
mimeType: file.mimeType,
|
|
14135
|
+
fileSize: file.content.length,
|
|
14136
|
+
type: KnowledgeType.FILE,
|
|
14137
|
+
content: file.content,
|
|
14138
|
+
contentType: file.mimeType,
|
|
14139
|
+
sessionId,
|
|
14140
|
+
prefix: "generated",
|
|
14141
|
+
tags: [{
|
|
14142
|
+
name: "generated",
|
|
14143
|
+
strength: 1
|
|
14144
|
+
}]
|
|
14145
|
+
}, {
|
|
14146
|
+
db: {
|
|
14147
|
+
fabFiles: db.fabfiles,
|
|
14148
|
+
adminSettings: db.adminSettings,
|
|
14149
|
+
users: db.users
|
|
14150
|
+
},
|
|
14151
|
+
storage: {
|
|
14152
|
+
upload: (path, content, options) => storage.upload(content, path, options),
|
|
14153
|
+
generateSignedUrl: (path, expireInSeconds, type) => storage.getSignedUrl(path, type ?? "get", { expiresIn: expireInSeconds })
|
|
14154
|
+
}
|
|
14155
|
+
});
|
|
14156
|
+
} catch (err) {
|
|
14157
|
+
logger.error("[persistGeneratedFileAsFabFile] failed to persist generated file (non-fatal)", err);
|
|
14158
|
+
}
|
|
14159
|
+
}
|
|
11795
14160
|
async function downloadImage$1(url) {
|
|
11796
14161
|
if (url.startsWith("data:image/")) {
|
|
11797
14162
|
const base64Data = url.split(",")[1];
|
|
@@ -11825,8 +14190,15 @@ async function processAndStoreImages(images, context) {
|
|
|
11825
14190
|
return Promise.all(images.map(async (image) => {
|
|
11826
14191
|
const buffer = await downloadImage$1(image);
|
|
11827
14192
|
const fileType = await fileTypeFromBuffer(buffer);
|
|
11828
|
-
const
|
|
11829
|
-
|
|
14193
|
+
const ext = fileType?.ext ?? "png";
|
|
14194
|
+
const filename = `${v4()}.${ext}`;
|
|
14195
|
+
const path = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
14196
|
+
await persistGeneratedFileAsFabFile(context, {
|
|
14197
|
+
fileName: `generated-image-${filename.slice(0, 8)}.${ext}`,
|
|
14198
|
+
mimeType: fileType?.mime ?? "image/png",
|
|
14199
|
+
content: buffer
|
|
14200
|
+
});
|
|
14201
|
+
return path;
|
|
11830
14202
|
}));
|
|
11831
14203
|
}
|
|
11832
14204
|
async function updateQuestAndReturnMarkdown$1(storedImageUrls, context) {
|
|
@@ -12538,7 +14910,7 @@ const currentDateTimeTool = {
|
|
|
12538
14910
|
},
|
|
12539
14911
|
toolSchema: {
|
|
12540
14912
|
name: "current_datetime",
|
|
12541
|
-
description: "Get the current date and time
|
|
14913
|
+
description: "Get the current, real-world date and time, fresh at the moment of the call. Call this whenever you need the current time of day (hour/minute) or to timestamp an action as it executes — never guess or rely on memory. Supports multiple output formats, date calculations (days until/since), historical day lookups, and IANA timezone identifiers.",
|
|
12542
14914
|
parameters: {
|
|
12543
14915
|
type: "object",
|
|
12544
14916
|
properties: {
|
|
@@ -14957,7 +17329,7 @@ async function trySemanticKbSearch(context, query, tags, maxResults) {
|
|
|
14957
17329
|
const provider = getProviderFromModel(embeddingModel);
|
|
14958
17330
|
if (provider === "openai" && !apiKeyTable?.openai) return null;
|
|
14959
17331
|
if (provider === "voyageai" && !apiKeyTable?.voyageai) return null;
|
|
14960
|
-
const { dataLakeTags, dataLakeTagPrefixes } = await getDynamicDataLakeAccess(context);
|
|
17332
|
+
const { dataLakeTags, dataLakeTagPrefixes, scopedTagPrefixes } = await getDynamicDataLakeAccess(context);
|
|
14961
17333
|
if (dataLakeTags.length === 0) return null;
|
|
14962
17334
|
const search = await semanticDataLakeSearch({
|
|
14963
17335
|
userId: context.userId,
|
|
@@ -14970,6 +17342,7 @@ async function trySemanticKbSearch(context, query, tags, maxResults) {
|
|
|
14970
17342
|
apiKeyTable,
|
|
14971
17343
|
dataLakeTags,
|
|
14972
17344
|
dataLakeTagPrefixes,
|
|
17345
|
+
scopedTagPrefixes,
|
|
14973
17346
|
logger: context.logger
|
|
14974
17347
|
}, { db: {
|
|
14975
17348
|
fabfiles: context.db.fabfiles,
|
|
@@ -15043,7 +17416,7 @@ const knowledgeBaseSearchTool = {
|
|
|
15043
17416
|
const semantic = await trySemanticKbSearch(context, query, tags, max_results);
|
|
15044
17417
|
if (semantic) return semantic;
|
|
15045
17418
|
try {
|
|
15046
|
-
const { dataLakeTags, dataLakeTagPrefixes } = await getDynamicDataLakeAccess(context);
|
|
17419
|
+
const { dataLakeTags, dataLakeTagPrefixes, scopedTagPrefixes } = await getDynamicDataLakeAccess(context);
|
|
15047
17420
|
const searchResults = await context.db.fabfiles.search(context.userId, query, {
|
|
15048
17421
|
tags: tags || [],
|
|
15049
17422
|
type: file_type,
|
|
@@ -15060,6 +17433,7 @@ const knowledgeBaseSearchTool = {
|
|
|
15060
17433
|
userGroups: context.user.groups || [],
|
|
15061
17434
|
dataLakeTags,
|
|
15062
17435
|
dataLakeTagPrefixes,
|
|
17436
|
+
scopedTagPrefixes,
|
|
15063
17437
|
excludeContent: true
|
|
15064
17438
|
});
|
|
15065
17439
|
const queryTerms = Array.from(new Set(query.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3)));
|
|
@@ -15207,7 +17581,7 @@ const knowledgeBaseRetrieveTool = {
|
|
|
15207
17581
|
if (files.length === 0) return `No document found with ID "${file_id}". The file may not exist or you may not have access to it. Try using search_knowledge_base to find the correct file ID.`;
|
|
15208
17582
|
}
|
|
15209
17583
|
if (files.length === 0 && (tags?.length || query)) {
|
|
15210
|
-
const { dataLakeTags, dataLakeTagPrefixes } = await getDynamicDataLakeAccess(context);
|
|
17584
|
+
const { dataLakeTags, dataLakeTagPrefixes, scopedTagPrefixes } = await getDynamicDataLakeAccess(context);
|
|
15211
17585
|
files = (await context.db.fabfiles.search(context.userId, query || "", {
|
|
15212
17586
|
tags: tags || [],
|
|
15213
17587
|
shared: false
|
|
@@ -15223,6 +17597,7 @@ const knowledgeBaseRetrieveTool = {
|
|
|
15223
17597
|
userGroups: context.user.groups || [],
|
|
15224
17598
|
dataLakeTags,
|
|
15225
17599
|
dataLakeTagPrefixes,
|
|
17600
|
+
scopedTagPrefixes,
|
|
15226
17601
|
excludeContent: true
|
|
15227
17602
|
})).data;
|
|
15228
17603
|
if (files.length === 0) return `No documents found matching ${[query && `query "${query}"`, tags?.length && `tags [${tags.join(", ")}]`].filter(Boolean).join(" and ")}. Try broadening your search with search_knowledge_base.`;
|
|
@@ -15326,6 +17701,13 @@ function formatResult$1(result) {
|
|
|
15326
17701
|
}
|
|
15327
17702
|
return lines.join("\n");
|
|
15328
17703
|
}
|
|
17704
|
+
/**
|
|
17705
|
+
* Returns a JSON `{ __uiSideEffect: true, ... }` envelope (see end of function), NOT
|
|
17706
|
+
* model-facing text. Production exec routes this through `wrapToolsForSentinels`
|
|
17707
|
+
* (sharedToolBuilder), which strips the envelope and surfaces `displayMessage` to the
|
|
17708
|
+
* model. Any future exec path that bypasses `wrapToolsForSentinels` would leak the raw
|
|
17709
|
+
* envelope into the model context — keep it behind that wrapper.
|
|
17710
|
+
*/
|
|
15329
17711
|
async function runQuantumSchedule(params) {
|
|
15330
17712
|
const parseResult = SchedulingProblemSchema.safeParse(params.problem);
|
|
15331
17713
|
if (!parseResult.success) return `Error: Invalid scheduling problem format.\n${parseResult.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n\nExpected format: { name, jobs: [{ id, name, operations: [{ jobId, machineId, duration }] }], machines: [{ id, name }] }`;
|
|
@@ -15371,7 +17753,12 @@ async function runQuantumSchedule(params) {
|
|
|
15371
17753
|
lines.push("### Errors");
|
|
15372
17754
|
errors.forEach((e) => lines.push(`- ${e}`));
|
|
15373
17755
|
}
|
|
15374
|
-
return
|
|
17756
|
+
return JSON.stringify({
|
|
17757
|
+
__uiSideEffect: true,
|
|
17758
|
+
type: "populateProblem",
|
|
17759
|
+
payload: problem,
|
|
17760
|
+
displayMessage: lines.join("\n")
|
|
17761
|
+
});
|
|
15375
17762
|
}
|
|
15376
17763
|
const quantumScheduleTool = {
|
|
15377
17764
|
name: "quantum_schedule",
|
|
@@ -15385,7 +17772,9 @@ Available solvers: ${allSolvers.map((s) => `${s.id} (${s.name})`).join(", ")}.
|
|
|
15385
17772
|
|
|
15386
17773
|
The "greedy" solver runs instantly. Metaheuristic solvers (simulated-annealing, tabu, genetic-algorithm, ant-colony) explore larger solution spaces but take longer. Use multiple solvers to race them against each other.
|
|
15387
17774
|
|
|
15388
|
-
The problem must define jobs (each with ordered operations) and machines. Each operation runs on a specific machine for a specific duration. Operations within a job must run in order
|
|
17775
|
+
The problem must define jobs (each with ordered operations) and machines. Each operation runs on a specific machine for a specific duration. Operations within a job must run in order.
|
|
17776
|
+
|
|
17777
|
+
Calling this loads the problem into the on-screen /opti Scheduling console (brief + Problem tab) so the user sees exactly what was solved and can tweak and re-race it — so the screen always matches the conversation.`,
|
|
15389
17778
|
parameters: {
|
|
15390
17779
|
type: "object",
|
|
15391
17780
|
properties: {
|
|
@@ -15476,37 +17865,93 @@ The problem must define jobs (each with ordered operations) and machines. Each o
|
|
|
15476
17865
|
}
|
|
15477
17866
|
})
|
|
15478
17867
|
};
|
|
17868
|
+
const FAMILY_IDS$1 = [
|
|
17869
|
+
"routing",
|
|
17870
|
+
"packing",
|
|
17871
|
+
"assignment",
|
|
17872
|
+
"network",
|
|
17873
|
+
"partitioning",
|
|
17874
|
+
"selection",
|
|
17875
|
+
"economic",
|
|
17876
|
+
"continuous"
|
|
17877
|
+
];
|
|
17878
|
+
/** Run the model and pull a JSON object out of the response (handles code fences). */
|
|
17879
|
+
async function completeToJson$1(context, systemPrompt, userDescription) {
|
|
17880
|
+
let rawContent = "";
|
|
17881
|
+
await context.llm.complete(context.model ?? "gpt-4", [{
|
|
17882
|
+
role: "system",
|
|
17883
|
+
content: systemPrompt
|
|
17884
|
+
}, {
|
|
17885
|
+
role: "user",
|
|
17886
|
+
content: userDescription
|
|
17887
|
+
}], {
|
|
17888
|
+
temperature: .2,
|
|
17889
|
+
stream: false
|
|
17890
|
+
}, async (texts) => {
|
|
17891
|
+
rawContent = texts.filter((t) => t !== null && t !== void 0).join("");
|
|
17892
|
+
});
|
|
17893
|
+
let jsonStr = rawContent.trim();
|
|
17894
|
+
const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
17895
|
+
if (codeBlockMatch) jsonStr = codeBlockMatch[1].trim();
|
|
17896
|
+
try {
|
|
17897
|
+
return { json: JSON.parse(jsonStr) };
|
|
17898
|
+
} catch {
|
|
17899
|
+
return { error: `Could not parse the model response as JSON. Raw response:\n${rawContent}` };
|
|
17900
|
+
}
|
|
17901
|
+
}
|
|
17902
|
+
/** Build the per-family system prompt from a real sample as the shape template. */
|
|
17903
|
+
function familyFormulationPrompt(familyId) {
|
|
17904
|
+
return `You convert a natural-language description into a "${familyId}" optimization problem as JSON.
|
|
17905
|
+
|
|
17906
|
+
Output ONLY a single JSON object matching this exact shape (a real ${familyId} example):
|
|
17907
|
+
|
|
17908
|
+
${JSON.stringify(FAMILY_SAMPLE_PROBLEMS[familyId][0], null, 2)}
|
|
17909
|
+
|
|
17910
|
+
Rules:
|
|
17911
|
+
- "family" MUST be exactly "${familyId}".
|
|
17912
|
+
- Keep it small and browser-solvable, at a scale similar to the example.
|
|
17913
|
+
- ids are integers starting at 0; names are short human-readable strings.
|
|
17914
|
+
- "description" MUST be a single plain-English sentence summarizing the real-world scenario this models (what the user just described) — it is shown on the on-screen brief card so a human can recognize at a glance that it matches the conversation.
|
|
17915
|
+
- Output ONLY the JSON object — no prose, no markdown fences.`;
|
|
17916
|
+
}
|
|
15479
17917
|
const quantumFormulateTool = {
|
|
15480
17918
|
name: "quantum_formulate",
|
|
15481
17919
|
implementation: (context) => ({
|
|
15482
17920
|
toolFn: async (value) => {
|
|
15483
|
-
const
|
|
15484
|
-
|
|
17921
|
+
const params = value;
|
|
17922
|
+
const userDescription = params.description?.trim();
|
|
17923
|
+
if (!userDescription) return "Error: Please provide a description of the optimization problem to formulate.";
|
|
17924
|
+
if (params.familyId && !FAMILY_IDS$1.includes(params.familyId)) return `Error: unknown familyId "${String(params.familyId)}". Valid families: ${FAMILY_IDS$1.join(", ")}. Omit familyId to formulate a job-shop scheduling problem.`;
|
|
17925
|
+
const familyId = params.familyId || void 0;
|
|
15485
17926
|
try {
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
17927
|
+
if (familyId) {
|
|
17928
|
+
const result = await completeToJson$1(context, familyFormulationPrompt(familyId), userDescription);
|
|
17929
|
+
if ("error" in result) return `Error: ${result.error}\n\nPlease rephrase with more specifics for a ${familyId} problem.`;
|
|
17930
|
+
const validated = FAMILY_PROBLEM_SCHEMAS[familyId].safeParse(result.json);
|
|
17931
|
+
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.`;
|
|
17932
|
+
const problem = validated.data;
|
|
17933
|
+
const summary = [
|
|
17934
|
+
`## Formulated: "${problem.name}"`,
|
|
17935
|
+
problem.description ? `\n${problem.description}` : "",
|
|
17936
|
+
"",
|
|
17937
|
+
`**${familyId} problem** — loaded into your active brief.`,
|
|
17938
|
+
"",
|
|
17939
|
+
`I've opened the ${familyId} console and set this as the active brief (use Undo on the banner to revert). Tweak it on the Problem tab or open Solvers to race it.`
|
|
17940
|
+
].filter((l) => l !== void 0).join("\n");
|
|
17941
|
+
return JSON.stringify({
|
|
17942
|
+
__uiSideEffect: true,
|
|
17943
|
+
type: "populateFamilyProblem",
|
|
17944
|
+
payload: {
|
|
17945
|
+
familyId,
|
|
17946
|
+
problem
|
|
17947
|
+
},
|
|
17948
|
+
displayMessage: summary
|
|
17949
|
+
});
|
|
15507
17950
|
}
|
|
15508
|
-
const
|
|
15509
|
-
if (
|
|
17951
|
+
const result = await completeToJson$1(context, PROBLEM_FORMULATION_PROMPT, userDescription);
|
|
17952
|
+
if ("error" in result) return `Error: ${result.error}\n\nPlease try rephrasing with specific jobs, machines, and processing times.`;
|
|
17953
|
+
const validated = SchedulingProblemSchema.safeParse(result.json);
|
|
17954
|
+
if (!validated.success) return `Error: LLM generated an invalid problem structure.\n${validated.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n\nPlease try rephrasing your description.`;
|
|
15510
17955
|
const problem = validated.data;
|
|
15511
17956
|
const totalOps = problem.jobs.reduce((s, j) => s + j.operations.length, 0);
|
|
15512
17957
|
const humanReadableSummary = [
|
|
@@ -15527,7 +17972,7 @@ const quantumFormulateTool = {
|
|
|
15527
17972
|
"### Machines:",
|
|
15528
17973
|
...problem.machines.map((m) => `- ${m.name} (id: ${m.id})`),
|
|
15529
17974
|
"",
|
|
15530
|
-
"
|
|
17975
|
+
"I've loaded this as your active scheduling brief (use Undo on the banner to revert). Tweak it on the Problem tab or open Solvers to race it."
|
|
15531
17976
|
].filter((l) => l !== void 0).join("\n");
|
|
15532
17977
|
return JSON.stringify({
|
|
15533
17978
|
__uiSideEffect: true,
|
|
@@ -15541,27 +17986,188 @@ const quantumFormulateTool = {
|
|
|
15541
17986
|
},
|
|
15542
17987
|
toolSchema: {
|
|
15543
17988
|
name: "quantum_formulate",
|
|
15544
|
-
description: `Convert a natural
|
|
17989
|
+
description: `Convert a natural-language description of an optimization problem into a structured problem that the /opti solvers can run.
|
|
15545
17990
|
|
|
15546
|
-
|
|
17991
|
+
Pass \`familyId\` to target one of the eight pattern families; omit it for job-shop scheduling:
|
|
17992
|
+
- routing (TSP / vehicle routing), packing (bin packing), assignment (who does what),
|
|
17993
|
+
network (max-cut), partitioning (balanced k-way), selection (knapsack / portfolio),
|
|
17994
|
+
economic (combinatorial auction), continuous (function minimization).
|
|
15547
17995
|
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
- "Schedule 4 software builds across 2 CI runners. Build A needs compile (5min) then test (3min)..."
|
|
15551
|
-
- "A bakery needs to make 5 cakes. Each cake goes through mixing, baking, and decorating on separate stations."`,
|
|
15552
|
-
strict: true,
|
|
17996
|
+
Describe the scenario in plain English — the entities, resources, costs/values, and constraints — and the tool produces a structured problem definition for that family.`,
|
|
17997
|
+
strict: false,
|
|
15553
17998
|
parameters: {
|
|
15554
17999
|
type: "object",
|
|
15555
|
-
properties: {
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
18000
|
+
properties: {
|
|
18001
|
+
description: {
|
|
18002
|
+
type: "string",
|
|
18003
|
+
description: "Natural-language description of the optimization problem to formulate"
|
|
18004
|
+
},
|
|
18005
|
+
familyId: {
|
|
18006
|
+
type: "string",
|
|
18007
|
+
enum: FAMILY_IDS$1,
|
|
18008
|
+
description: "Optional optimization family. Omit for job-shop scheduling; otherwise one of the eight families."
|
|
18009
|
+
}
|
|
18010
|
+
},
|
|
15559
18011
|
required: ["description"],
|
|
15560
18012
|
additionalProperties: false
|
|
15561
18013
|
}
|
|
15562
18014
|
}
|
|
15563
18015
|
})
|
|
15564
18016
|
};
|
|
18017
|
+
const FAMILY_IDS = [
|
|
18018
|
+
"routing",
|
|
18019
|
+
"packing",
|
|
18020
|
+
"assignment",
|
|
18021
|
+
"network",
|
|
18022
|
+
"partitioning",
|
|
18023
|
+
"selection",
|
|
18024
|
+
"economic",
|
|
18025
|
+
"continuous"
|
|
18026
|
+
];
|
|
18027
|
+
/** Run the model and pull a JSON object out of the response (handles code fences). */
|
|
18028
|
+
async function completeToJson(context, systemPrompt, userInstruction) {
|
|
18029
|
+
let rawContent = "";
|
|
18030
|
+
await context.llm.complete(context.model ?? "gpt-4", [{
|
|
18031
|
+
role: "system",
|
|
18032
|
+
content: systemPrompt
|
|
18033
|
+
}, {
|
|
18034
|
+
role: "user",
|
|
18035
|
+
content: userInstruction
|
|
18036
|
+
}], {
|
|
18037
|
+
temperature: .2,
|
|
18038
|
+
stream: false
|
|
18039
|
+
}, async (texts) => {
|
|
18040
|
+
rawContent = texts.filter((t) => t !== null && t !== void 0).join("");
|
|
18041
|
+
});
|
|
18042
|
+
let jsonStr = rawContent.trim();
|
|
18043
|
+
const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
18044
|
+
if (codeBlockMatch) jsonStr = codeBlockMatch[1].trim();
|
|
18045
|
+
try {
|
|
18046
|
+
return { json: JSON.parse(jsonStr) };
|
|
18047
|
+
} catch {
|
|
18048
|
+
return { error: `Could not parse the model response as JSON. Raw response:\n${rawContent}` };
|
|
18049
|
+
}
|
|
18050
|
+
}
|
|
18051
|
+
/**
|
|
18052
|
+
* Edit prompt. The current problem doubles as the shape template — the model must
|
|
18053
|
+
* return the SAME shape with only the requested change applied. The hard rule is
|
|
18054
|
+
* fidelity: anything the instruction doesn't touch comes back byte-for-byte, which
|
|
18055
|
+
* is the whole reason this tool exists instead of re-running `quantum_formulate`.
|
|
18056
|
+
*/
|
|
18057
|
+
function editPrompt(currentProblem, familyId) {
|
|
18058
|
+
return `You are EDITING an existing optimization problem. Below is the CURRENT problem as JSON.
|
|
18059
|
+
|
|
18060
|
+
Apply ONLY the change the user describes. Preserve every other field EXACTLY — identical ids, names, and numeric values for anything the change does not explicitly touch. Do not renumber, rename, reorder, or "improve" untouched entities. Keep it small and browser-solvable.
|
|
18061
|
+
${familyId ? `- "family" MUST stay exactly "${familyId}".` : "- Keep the job-shop scheduling shape (jobs with ordered operations referencing machine ids)."}
|
|
18062
|
+
- "description" stays a single plain-English sentence; update it only if the change makes it inaccurate.
|
|
18063
|
+
|
|
18064
|
+
CURRENT PROBLEM:
|
|
18065
|
+
${JSON.stringify(currentProblem, null, 2)}
|
|
18066
|
+
|
|
18067
|
+
Output ONLY a JSON object of the form:
|
|
18068
|
+
{ "problem": <the COMPLETE updated problem, same shape as above>, "changes": "<one short sentence describing exactly what you changed>" }
|
|
18069
|
+
No prose, no markdown fences.`;
|
|
18070
|
+
}
|
|
18071
|
+
/** Split the model output into the problem and an optional change summary. */
|
|
18072
|
+
function unwrap(json) {
|
|
18073
|
+
if (json && typeof json === "object" && "problem" in json) {
|
|
18074
|
+
const obj = json;
|
|
18075
|
+
return {
|
|
18076
|
+
problem: obj.problem,
|
|
18077
|
+
changes: typeof obj.changes === "string" ? obj.changes : void 0
|
|
18078
|
+
};
|
|
18079
|
+
}
|
|
18080
|
+
return { problem: json };
|
|
18081
|
+
}
|
|
18082
|
+
const quantumEditProblemTool = {
|
|
18083
|
+
name: "quantum_edit_problem",
|
|
18084
|
+
implementation: (context) => ({
|
|
18085
|
+
toolFn: async (value) => {
|
|
18086
|
+
const params = value;
|
|
18087
|
+
const instruction = params.instruction?.trim();
|
|
18088
|
+
if (!instruction) return "Error: Please provide an instruction describing the change to make.";
|
|
18089
|
+
if (params.currentProblem == null || typeof params.currentProblem !== "object") return "Error: Please pass the current problem (the active brief JSON) as `currentProblem` so it can be edited in place.";
|
|
18090
|
+
if (params.familyId && !FAMILY_IDS.includes(params.familyId)) return `Error: unknown familyId "${String(params.familyId)}". Valid families: ${FAMILY_IDS.join(", ")}. Omit familyId to edit a job-shop scheduling problem.`;
|
|
18091
|
+
const familyId = params.familyId || void 0;
|
|
18092
|
+
try {
|
|
18093
|
+
const result = await completeToJson(context, editPrompt(params.currentProblem, familyId), instruction);
|
|
18094
|
+
if ("error" in result) return `Error: ${result.error}\n\nPlease restate the change more specifically.`;
|
|
18095
|
+
const { problem: rawProblem, changes } = unwrap(result.json);
|
|
18096
|
+
if (rawProblem == null || typeof rawProblem !== "object") return "Error: the edit did not produce a problem object. Please restate the change.";
|
|
18097
|
+
if (familyId) {
|
|
18098
|
+
const validated = FAMILY_PROBLEM_SCHEMAS[familyId].safeParse(rawProblem);
|
|
18099
|
+
if (!validated.success) return `Error: the edited ${familyId} problem is invalid.\n${validated.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n\nPlease restate the change.`;
|
|
18100
|
+
const problem = validated.data;
|
|
18101
|
+
const summary = [
|
|
18102
|
+
`## Updated: "${problem.name}"`,
|
|
18103
|
+
changes ? `\n${changes}` : "",
|
|
18104
|
+
"",
|
|
18105
|
+
`**${familyId} problem** — applied to your active brief.`,
|
|
18106
|
+
"",
|
|
18107
|
+
`I've updated the ${familyId} brief in place (use Undo on the banner to revert). Open Solvers to race it.`
|
|
18108
|
+
].filter((l) => l !== void 0).join("\n");
|
|
18109
|
+
return JSON.stringify({
|
|
18110
|
+
__uiSideEffect: true,
|
|
18111
|
+
type: "populateFamilyProblem",
|
|
18112
|
+
payload: {
|
|
18113
|
+
familyId,
|
|
18114
|
+
problem
|
|
18115
|
+
},
|
|
18116
|
+
displayMessage: summary
|
|
18117
|
+
});
|
|
18118
|
+
}
|
|
18119
|
+
const validated = SchedulingProblemSchema.safeParse(rawProblem);
|
|
18120
|
+
if (!validated.success) return `Error: the edited problem structure is invalid.\n${validated.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}\n\nPlease restate the change.`;
|
|
18121
|
+
const problem = validated.data;
|
|
18122
|
+
const totalOps = problem.jobs.reduce((s, j) => s + j.operations.length, 0);
|
|
18123
|
+
const summary = [
|
|
18124
|
+
`## Updated: "${problem.name}"`,
|
|
18125
|
+
changes ? `\n${changes}` : "",
|
|
18126
|
+
"",
|
|
18127
|
+
`**${problem.jobs.length} jobs, ${problem.machines.length} machines, ${totalOps} operations**`,
|
|
18128
|
+
"",
|
|
18129
|
+
"I've updated your active scheduling brief in place (use Undo on the banner to revert). Open Solvers to race it."
|
|
18130
|
+
].filter((l) => l !== void 0).join("\n");
|
|
18131
|
+
return JSON.stringify({
|
|
18132
|
+
__uiSideEffect: true,
|
|
18133
|
+
type: "populateProblem",
|
|
18134
|
+
payload: problem,
|
|
18135
|
+
displayMessage: summary
|
|
18136
|
+
});
|
|
18137
|
+
} catch (err) {
|
|
18138
|
+
return `Error editing problem: ${err instanceof Error ? err.message : String(err)}`;
|
|
18139
|
+
}
|
|
18140
|
+
},
|
|
18141
|
+
toolSchema: {
|
|
18142
|
+
name: "quantum_edit_problem",
|
|
18143
|
+
description: `Edit the optimization problem currently loaded in the /opti active brief. Use this — NOT quantum_formulate — whenever the user asks to change, refine, tweak, add to, or adjust the problem they're already looking at.
|
|
18144
|
+
|
|
18145
|
+
Pass the active brief as \`currentProblem\` (you have it in your context), describe the change in \`instruction\`, and pass \`familyId\` for one of the eight pattern families (omit for job-shop scheduling). The tool applies only the requested change, preserves everything else exactly, and the result is auto-applied to the active brief with one-click Undo. Use quantum_formulate instead only for a brand-new, unrelated problem.`,
|
|
18146
|
+
strict: false,
|
|
18147
|
+
parameters: {
|
|
18148
|
+
type: "object",
|
|
18149
|
+
properties: {
|
|
18150
|
+
instruction: {
|
|
18151
|
+
type: "string",
|
|
18152
|
+
description: "The change to apply to the current problem, in plain English (e.g. \"add two more jobs\")."
|
|
18153
|
+
},
|
|
18154
|
+
currentProblem: {
|
|
18155
|
+
type: "object",
|
|
18156
|
+
description: "The current problem JSON (the active brief) to edit in place.",
|
|
18157
|
+
additionalProperties: true
|
|
18158
|
+
},
|
|
18159
|
+
familyId: {
|
|
18160
|
+
type: "string",
|
|
18161
|
+
enum: FAMILY_IDS,
|
|
18162
|
+
description: "Optional optimization family. Omit for job-shop scheduling; otherwise one of the eight."
|
|
18163
|
+
}
|
|
18164
|
+
},
|
|
18165
|
+
required: ["instruction", "currentProblem"],
|
|
18166
|
+
additionalProperties: false
|
|
18167
|
+
}
|
|
18168
|
+
}
|
|
18169
|
+
})
|
|
18170
|
+
};
|
|
15565
18171
|
const navigateViewTool = {
|
|
15566
18172
|
name: "navigate_view",
|
|
15567
18173
|
implementation: (context) => ({
|
|
@@ -15869,6 +18475,7 @@ Please generate a complete, well-structured notebook that performs this analysis
|
|
|
15869
18475
|
}
|
|
15870
18476
|
})
|
|
15871
18477
|
};
|
|
18478
|
+
const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
15872
18479
|
const LIMITS = {
|
|
15873
18480
|
MAX_SHEETS: 10,
|
|
15874
18481
|
MAX_CELLS_PER_SHEET: 1e4,
|
|
@@ -16187,9 +18794,14 @@ const excelGenerationTool = {
|
|
|
16187
18794
|
const sanitizedName = sanitizeFilename(params.filename);
|
|
16188
18795
|
const filename = `${sanitizedName}-${v4().slice(0, 8)}.xlsx`;
|
|
16189
18796
|
await context.imageGenerateStorage.upload(buffer, filename, {
|
|
16190
|
-
ContentType:
|
|
18797
|
+
ContentType: XLSX_MIME,
|
|
16191
18798
|
ContentDisposition: `attachment; filename="${sanitizedName}.xlsx"`
|
|
16192
18799
|
});
|
|
18800
|
+
await persistGeneratedFileAsFabFile(context, {
|
|
18801
|
+
fileName: `${sanitizedName}.xlsx`,
|
|
18802
|
+
mimeType: XLSX_MIME,
|
|
18803
|
+
content: buffer
|
|
18804
|
+
});
|
|
16193
18805
|
await context.onFinish?.("excel_generation", [filename]);
|
|
16194
18806
|
await context.statusUpdate({ images: [filename] });
|
|
16195
18807
|
return `Successfully generated Excel file: ${sanitizedName}.xlsx`;
|
|
@@ -16574,15 +19186,28 @@ DO NOT USE FOR:
|
|
|
16574
19186
|
}
|
|
16575
19187
|
})
|
|
16576
19188
|
};
|
|
19189
|
+
/** Cap on how many invocable skills are listed back when resolution fails. */
|
|
19190
|
+
const INVOCABLE_HINT_LIMIT = 50;
|
|
16577
19191
|
/**
|
|
16578
19192
|
* LLM-invocable `skill` tool — the autonomous counterpart to user `/skill-name`
|
|
16579
19193
|
* invocations handled by SkillsFeature.
|
|
16580
19194
|
*
|
|
16581
|
-
* The LLM passes `{ name, args }`. The tool resolves the skill
|
|
16582
|
-
* scope
|
|
16583
|
-
*
|
|
19195
|
+
* The LLM passes `{ name, args }`. The tool resolves the skill across every
|
|
19196
|
+
* scope the caller can access (`findAccessibleByNameForUser` — owned + shared +
|
|
19197
|
+
* global-read), expands `$ARGUMENTS` / `$1` / `$2`, and returns the expanded
|
|
19198
|
+
* body as the observation for the next turn. Skills with
|
|
16584
19199
|
* `disableModelInvocation: true` are not callable here.
|
|
16585
19200
|
*
|
|
19201
|
+
* Security: a skill the caller does NOT own (shared / org / global) is treated
|
|
19202
|
+
* as untrusted content — its body is wrapped with explicit delimiters and a
|
|
19203
|
+
* "do not follow conflicting instructions" framing so a malicious author can't
|
|
19204
|
+
* hijack the turn via prompt injection. The skill's `allowedTools` whitelist is
|
|
19205
|
+
* surfaced to the model as a *requested* tool scope (intersected with the
|
|
19206
|
+
* invoker's allow-list via `intersectAllowedTools`). NOTE: today this is prompt
|
|
19207
|
+
* guidance only — there is no `ToolContext` invoker allow-list to enforce it
|
|
19208
|
+
* against, so the wording stays advisory. When that wiring lands, the same
|
|
19209
|
+
* `permittedTools` set can narrow the completion's actual tool list.
|
|
19210
|
+
*
|
|
16586
19211
|
* Catalog discovery: the available skill names + descriptions are listed in
|
|
16587
19212
|
* the system prompt by SkillsFeature, so the LLM knows what's invocable. If
|
|
16588
19213
|
* the LLM passes a name that isn't accessible, the tool returns an error
|
|
@@ -16598,14 +19223,18 @@ const skillTool = {
|
|
|
16598
19223
|
const name = rawName.replace(/^\//, "");
|
|
16599
19224
|
const skillsRepo = context.db.skills;
|
|
16600
19225
|
if (!skillsRepo) return "Error: skills are not configured in this environment.";
|
|
16601
|
-
const skill = await skillsRepo.
|
|
19226
|
+
const skill = await skillsRepo.findAccessibleByNameForUser(context.userId, name);
|
|
16602
19227
|
if (!skill || skill.disableModelInvocation) {
|
|
16603
|
-
const invocable = (await skillsRepo.
|
|
19228
|
+
const invocable = (await skillsRepo.listAccessibleInvocableForUser(context.userId, INVOCABLE_HINT_LIMIT)).map((s) => s.name);
|
|
16604
19229
|
return invocable.length === 0 ? `Error: skill "${name}" is not available, and you have no LLM-invocable skills defined.` : `Error: skill "${name}" is not available. Invocable skills: ${invocable.join(", ")}`;
|
|
16605
19230
|
}
|
|
16606
19231
|
const argArray = typeof params.args === "string" ? parseSkillArguments(params.args) : [];
|
|
16607
19232
|
const expandedBody = substituteArguments$1(skill.body, argArray);
|
|
16608
|
-
return `## Skill: /${skill.name}\n\n${expandedBody}\n\n---\nFollow the instructions above for the rest of this turn.`;
|
|
19233
|
+
if (skill.userId === context.userId) return `## Skill: /${skill.name}\n\n${expandedBody}\n\n---\nFollow the instructions above for the rest of this turn.`;
|
|
19234
|
+
const ownerLabel = skill.organizationId ? "your organization" : skill.isSystem ? "the system" : "another user";
|
|
19235
|
+
const wrapped = wrapUntrustedSkillBody(skill.name, expandedBody, ownerLabel);
|
|
19236
|
+
const permittedTools = intersectAllowedTools(skill.allowedTools, void 0);
|
|
19237
|
+
return `${wrapped}${permittedTools && permittedTools.length > 0 ? `\n\nThis skill is intended to use only these tools: ${permittedTools.join(", ")}. Prefer to stay within that set.` : ""}\n\n---\nFollow the intent above for the rest of this turn, subject to the constraints stated.`;
|
|
16609
19238
|
},
|
|
16610
19239
|
toolSchema: {
|
|
16611
19240
|
name: "skill",
|
|
@@ -17469,16 +20098,18 @@ const b4mTools = {
|
|
|
17469
20098
|
retrieve_knowledge_content: knowledgeBaseRetrieveTool,
|
|
17470
20099
|
quantum_schedule: quantumScheduleTool,
|
|
17471
20100
|
quantum_formulate: quantumFormulateTool,
|
|
20101
|
+
quantum_edit_problem: quantumEditProblemTool,
|
|
17472
20102
|
navigate_view: navigateViewTool,
|
|
17473
20103
|
generate_jupyter_notebook: jupyterNotebookTool,
|
|
17474
20104
|
excel_generation: excelGenerationTool,
|
|
17475
20105
|
fmp_financial_data: fmpTool,
|
|
17476
20106
|
skill: skillTool
|
|
17477
20107
|
};
|
|
17478
|
-
const generateTools = (userId, user, logger, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model, imageProcessorLambdaName, tools = b4mTools, allowedDirectories, entitlementKeys = []) => {
|
|
20108
|
+
const generateTools = (userId, user, logger, { db }, storage, imageGenerateStorage, statusUpdate, onStart, onFinish, llm, config, model, imageProcessorLambdaName, tools = b4mTools, allowedDirectories, entitlementKeys = [], sessionId) => {
|
|
17479
20109
|
const context = {
|
|
17480
20110
|
userId,
|
|
17481
20111
|
user,
|
|
20112
|
+
sessionId,
|
|
17482
20113
|
logger,
|
|
17483
20114
|
db,
|
|
17484
20115
|
storage,
|
|
@@ -18499,7 +21130,11 @@ var ReActAgent = class extends EventEmitter {
|
|
|
18499
21130
|
* This can be overridden in the context or customized here
|
|
18500
21131
|
*/
|
|
18501
21132
|
getSystemPrompt() {
|
|
18502
|
-
|
|
21133
|
+
const base = this.context.systemPrompt ? this.context.systemPrompt : this.buildDefaultSystemPrompt();
|
|
21134
|
+
const persona = this.context.personaPrompt?.trim();
|
|
21135
|
+
return persona ? `${persona}\n\n${base}` : base;
|
|
21136
|
+
}
|
|
21137
|
+
buildDefaultSystemPrompt() {
|
|
18503
21138
|
return `You are an autonomous AI agent with access to tools. Your job is to solve problems by taking action, not by asking for clarification.
|
|
18504
21139
|
|
|
18505
21140
|
CORE PRINCIPLES:
|
|
@@ -19122,7 +21757,7 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
|
|
|
19122
21757
|
})}</function>`;
|
|
19123
21758
|
}
|
|
19124
21759
|
}
|
|
19125
|
-
return `Error: Tool ${toolUse.name} not
|
|
21760
|
+
return `Error: Tool "${toolUse.name}" is not available in this context and cannot be called. Do NOT attempt to call it again. Proceed without it — call other available tools if your plan still needs them, or compose your final answer from the information already gathered if no more tools are needed.`;
|
|
19126
21761
|
}
|
|
19127
21762
|
try {
|
|
19128
21763
|
const params = this.parseToolArguments(toolUse.arguments);
|
|
@@ -19947,6 +22582,159 @@ parentPort.on('message', async (msg) => {
|
|
|
19947
22582
|
}
|
|
19948
22583
|
});
|
|
19949
22584
|
`;
|
|
22585
|
+
String.raw`
|
|
22586
|
+
const __cap = _captureLine;
|
|
22587
|
+
const __callTool = _callTool;
|
|
22588
|
+
delete globalThis._captureLine;
|
|
22589
|
+
delete globalThis._callTool;
|
|
22590
|
+
|
|
22591
|
+
const HARD_PER_LINE_BYTES = ${5e4};
|
|
22592
|
+
|
|
22593
|
+
function __jsonReplacer(_k, v) {
|
|
22594
|
+
if (v instanceof Error) return { name: v.name, message: v.message };
|
|
22595
|
+
if (typeof v === 'bigint') return v.toString() + 'n';
|
|
22596
|
+
return v;
|
|
22597
|
+
}
|
|
22598
|
+
function __formatLine(args) {
|
|
22599
|
+
const line = args.map(a => {
|
|
22600
|
+
if (typeof a === 'string') return a;
|
|
22601
|
+
if (a === undefined) return 'undefined';
|
|
22602
|
+
if (a === null) return 'null';
|
|
22603
|
+
try { return JSON.stringify(a, __jsonReplacer, 2); } catch { return String(a); }
|
|
22604
|
+
}).join(' ');
|
|
22605
|
+
return line.length > HARD_PER_LINE_BYTES
|
|
22606
|
+
? line.slice(0, HARD_PER_LINE_BYTES) + ' [...line truncated]'
|
|
22607
|
+
: line;
|
|
22608
|
+
}
|
|
22609
|
+
globalThis.console = {
|
|
22610
|
+
log: (...a) => __cap.applySync(undefined, [__formatLine(a)], { arguments: { copy: true } }),
|
|
22611
|
+
warn: (...a) => __cap.applySync(undefined, [__formatLine(a)], { arguments: { copy: true } }),
|
|
22612
|
+
error: (...a) => __cap.applySync(undefined, [__formatLine(a)], { arguments: { copy: true } }),
|
|
22613
|
+
info: (...a) => __cap.applySync(undefined, [__formatLine(a)], { arguments: { copy: true } }),
|
|
22614
|
+
};
|
|
22615
|
+
|
|
22616
|
+
// A bare isolate has no structuredClone (it's a host/web API, not a V8
|
|
22617
|
+
// intrinsic). The in-process + worker backends expose the *host's* real
|
|
22618
|
+
// structuredClone — which we can't reach across the isolate boundary — so
|
|
22619
|
+
// polyfill the structured-clone algorithm in-isolate for the common cloneable
|
|
22620
|
+
// types (Date, RegExp, Map, Set, ArrayBuffer / typed arrays / DataView,
|
|
22621
|
+
// arrays, plain objects) WITH circular-reference support. This is real
|
|
22622
|
+
// parity for those types; a JSON round-trip would silently drop
|
|
22623
|
+
// Date -> string, Map/Set -> {}, RegExp -> {}, TypedArray -> index map.
|
|
22624
|
+
// Genuinely uncloneable inputs (functions, etc.) throw, as the real API does.
|
|
22625
|
+
if (typeof globalThis.structuredClone !== 'function') {
|
|
22626
|
+
globalThis.structuredClone = function structuredClone(input) {
|
|
22627
|
+
const seen = new WeakMap();
|
|
22628
|
+
function clone(v) {
|
|
22629
|
+
if (v === null || typeof v !== 'object') {
|
|
22630
|
+
if (typeof v === 'function') throw new Error('structuredClone: a function could not be cloned');
|
|
22631
|
+
return v;
|
|
22632
|
+
}
|
|
22633
|
+
if (seen.has(v)) return seen.get(v);
|
|
22634
|
+
// Every cloneable type records its clone in the seen-map BEFORE returning,
|
|
22635
|
+
// so the same object appearing at multiple paths yields one shared clone
|
|
22636
|
+
// (reference identity), matching the real structured-clone algorithm —
|
|
22637
|
+
// not just the recursive containers.
|
|
22638
|
+
if (v instanceof Date) { const out = new Date(v.getTime()); seen.set(v, out); return out; }
|
|
22639
|
+
if (v instanceof RegExp) { const out = new RegExp(v.source, v.flags); seen.set(v, out); return out; }
|
|
22640
|
+
if (v instanceof ArrayBuffer) { const out = v.slice(0); seen.set(v, out); return out; }
|
|
22641
|
+
if (typeof DataView !== 'undefined' && v instanceof DataView) {
|
|
22642
|
+
const out = new DataView(clone(v.buffer), v.byteOffset, v.byteLength); seen.set(v, out); return out;
|
|
22643
|
+
}
|
|
22644
|
+
if (ArrayBuffer.isView(v)) { const out = new v.constructor(v); seen.set(v, out); return out; } // typed array — fresh buffer
|
|
22645
|
+
if (v instanceof Map) {
|
|
22646
|
+
const out = new Map(); seen.set(v, out);
|
|
22647
|
+
for (const [k, val] of v) out.set(clone(k), clone(val));
|
|
22648
|
+
return out;
|
|
22649
|
+
}
|
|
22650
|
+
if (v instanceof Set) {
|
|
22651
|
+
const out = new Set(); seen.set(v, out);
|
|
22652
|
+
for (const val of v) out.add(clone(val));
|
|
22653
|
+
return out;
|
|
22654
|
+
}
|
|
22655
|
+
if (Array.isArray(v)) {
|
|
22656
|
+
const out = []; seen.set(v, out);
|
|
22657
|
+
for (let i = 0; i < v.length; i++) out[i] = clone(v[i]);
|
|
22658
|
+
return out;
|
|
22659
|
+
}
|
|
22660
|
+
const out = {}; seen.set(v, out);
|
|
22661
|
+
for (const k of Object.keys(v)) out[k] = clone(v[k]);
|
|
22662
|
+
return out;
|
|
22663
|
+
}
|
|
22664
|
+
return clone(input);
|
|
22665
|
+
};
|
|
22666
|
+
}
|
|
22667
|
+
|
|
22668
|
+
// Neuter string-based code generation. Same posture as the worker /
|
|
22669
|
+
// in-process backends' codeGeneration:{strings:false} (Ken's P2 #2): the
|
|
22670
|
+
// LLM has no legitimate reason to generate second-order code that's
|
|
22671
|
+
// invisible in the logged 'code' parameter. isolated-vm exposes no V8-level
|
|
22672
|
+
// codegen toggle, so we block every reachable path to the intrinsic
|
|
22673
|
+
// Function constructors — not just the global bindings. The well-known
|
|
22674
|
+
// vm-escape vector \`(function(){}).constructor("…")()\` reaches the
|
|
22675
|
+
// intrinsic via the prototype chain, around \`globalThis.Function\`, so we
|
|
22676
|
+
// capture each function-type intrinsic BEFORE overriding the globals and
|
|
22677
|
+
// replace its \`prototype.constructor\` too. (Generated code would stay
|
|
22678
|
+
// trapped in the isolate regardless — this is auditability + worker parity,
|
|
22679
|
+
// not the core isolation guarantee, which the isolate itself provides.)
|
|
22680
|
+
const __RealFunction = (function () {}).constructor;
|
|
22681
|
+
const __AsyncFunction = (async function () {}).constructor;
|
|
22682
|
+
const __GeneratorFunction = (function* () {}).constructor;
|
|
22683
|
+
const __AsyncGeneratorFunction = (async function* () {}).constructor;
|
|
22684
|
+
const __blockCodegen = function () {
|
|
22685
|
+
throw new Error('code generation from strings (eval / Function) is disabled in the REPL sandbox');
|
|
22686
|
+
};
|
|
22687
|
+
for (const __Ctor of [__RealFunction, __AsyncFunction, __GeneratorFunction, __AsyncGeneratorFunction]) {
|
|
22688
|
+
try {
|
|
22689
|
+
Object.defineProperty(__Ctor.prototype, 'constructor', {
|
|
22690
|
+
value: __blockCodegen,
|
|
22691
|
+
writable: true,
|
|
22692
|
+
configurable: true,
|
|
22693
|
+
enumerable: false,
|
|
22694
|
+
});
|
|
22695
|
+
} catch (_e) {
|
|
22696
|
+
// best-effort; the global overrides below still cover the common paths
|
|
22697
|
+
}
|
|
22698
|
+
}
|
|
22699
|
+
globalThis.eval = __blockCodegen;
|
|
22700
|
+
globalThis.Function = __blockCodegen;
|
|
22701
|
+
|
|
22702
|
+
// Tool-stub registry. Each registered tool becomes a top-level async
|
|
22703
|
+
// function that round-trips through the host dispatcher and re-throws on
|
|
22704
|
+
// the { ok:false } envelope.
|
|
22705
|
+
globalThis.__registerTools = function (names) {
|
|
22706
|
+
for (const name of names) {
|
|
22707
|
+
globalThis[name] = async (...args) => {
|
|
22708
|
+
const envJson = await __callTool.apply(
|
|
22709
|
+
undefined,
|
|
22710
|
+
[name, JSON.stringify(args)],
|
|
22711
|
+
{ arguments: { copy: true }, result: { promise: true, copy: true } }
|
|
22712
|
+
);
|
|
22713
|
+
const env = JSON.parse(envJson);
|
|
22714
|
+
if (!env.ok) throw new Error(env.error);
|
|
22715
|
+
return env.value;
|
|
22716
|
+
};
|
|
22717
|
+
}
|
|
22718
|
+
};
|
|
22719
|
+
`;
|
|
22720
|
+
z.object({
|
|
22721
|
+
reflection: z.string().min(1),
|
|
22722
|
+
summary: z.string().min(1),
|
|
22723
|
+
nextIntendedAction: z.string().min(1),
|
|
22724
|
+
scopeLocks: z.array(z.string()).default([]),
|
|
22725
|
+
drivesAfter: DriveVectorSchema
|
|
22726
|
+
});
|
|
22727
|
+
z.object({
|
|
22728
|
+
semanticMemory: z.array(z.object({
|
|
22729
|
+
id: z.string().min(1),
|
|
22730
|
+
fact: z.string().min(1),
|
|
22731
|
+
evidenceTier: EvidenceTierSchema,
|
|
22732
|
+
confidence: z.number().min(0).max(1).default(.5),
|
|
22733
|
+
sourceEpisodeIds: z.array(z.string()).default([]),
|
|
22734
|
+
lastAffirmedAt: z.string()
|
|
22735
|
+
})),
|
|
22736
|
+
openQuestions: z.array(z.string()).default([])
|
|
22737
|
+
});
|
|
19950
22738
|
z.object({
|
|
19951
22739
|
count: z.number().optional(),
|
|
19952
22740
|
unlimitedUse: z.boolean().optional(),
|
|
@@ -20462,6 +23250,18 @@ z.object({
|
|
|
20462
23250
|
id: z.string().min(1) });
|
|
20463
23251
|
z.object({ organizationId: z.string() });
|
|
20464
23252
|
z.object({ id: z.string() });
|
|
23253
|
+
const createPromptSchema = z.object({
|
|
23254
|
+
type: z.string(),
|
|
23255
|
+
name: z.string(),
|
|
23256
|
+
promptText: z.string(),
|
|
23257
|
+
tags: z.array(z.string())
|
|
23258
|
+
});
|
|
23259
|
+
z.object({ id: z.string() });
|
|
23260
|
+
z.object({ id: z.string() });
|
|
23261
|
+
z.object({ name: z.string() });
|
|
23262
|
+
z.object({ tags: z.array(z.string()) });
|
|
23263
|
+
z.object({ type: z.string() });
|
|
23264
|
+
createPromptSchema.partial().extend({ id: z.string() });
|
|
20465
23265
|
z.object({ id: z.string() });
|
|
20466
23266
|
z.object({ ids: z.array(z.string()).optional() });
|
|
20467
23267
|
z.object({
|
|
@@ -20474,7 +23274,7 @@ z.object({
|
|
|
20474
23274
|
systemPriority: z.number().min(0).max(999).optional(),
|
|
20475
23275
|
sessionId: z.string().optional(),
|
|
20476
23276
|
notes: z.string().optional(),
|
|
20477
|
-
primaryTag: z.string().optional(),
|
|
23277
|
+
primaryTag: z.string().nullable().optional(),
|
|
20478
23278
|
tags: z.array(z.object({
|
|
20479
23279
|
name: z.string(),
|
|
20480
23280
|
strength: z.number()
|
|
@@ -20490,33 +23290,6 @@ z.object({
|
|
|
20490
23290
|
fabFileId: z.string(),
|
|
20491
23291
|
chunkId: z.string()
|
|
20492
23292
|
});
|
|
20493
|
-
z.object({
|
|
20494
|
-
fileName: z.string(),
|
|
20495
|
-
mimeType: z.string(),
|
|
20496
|
-
fileSize: z.number(),
|
|
20497
|
-
type: z.enum(KnowledgeType),
|
|
20498
|
-
content: z.union([z.string(), z.instanceof(Buffer)]).optional(),
|
|
20499
|
-
organizationId: z.string().optional(),
|
|
20500
|
-
/**
|
|
20501
|
-
* Content type of the file
|
|
20502
|
-
* @example 'text/markdown'
|
|
20503
|
-
* @example 'application/pdf'
|
|
20504
|
-
* @example 'application/octet-stream' for binary files
|
|
20505
|
-
*/
|
|
20506
|
-
contentType: z.string().optional(),
|
|
20507
|
-
public: z.boolean().optional(),
|
|
20508
|
-
prefix: z.string().optional(),
|
|
20509
|
-
system: z.boolean().optional(),
|
|
20510
|
-
tags: z.array(z.object({
|
|
20511
|
-
name: z.string(),
|
|
20512
|
-
strength: z.number()
|
|
20513
|
-
})).optional(),
|
|
20514
|
-
systemPriority: z.number().optional(),
|
|
20515
|
-
sessionId: z.string().optional(),
|
|
20516
|
-
contentHash: z.string().optional(),
|
|
20517
|
-
batchId: z.string().optional(),
|
|
20518
|
-
relativePath: z.string().optional()
|
|
20519
|
-
});
|
|
20520
23293
|
z.object({ sessionId: z.string() });
|
|
20521
23294
|
z.object({ questId: z.string() });
|
|
20522
23295
|
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,})).+/) });
|
|
@@ -20559,6 +23332,7 @@ z.object({
|
|
|
20559
23332
|
userGroups: z.array(z.string()).optional(),
|
|
20560
23333
|
dataLakeTags: z.array(z.string()).optional(),
|
|
20561
23334
|
dataLakeTagPrefixes: z.array(z.string()).optional(),
|
|
23335
|
+
scopedTagPrefixes: z.array(z.string()).optional(),
|
|
20562
23336
|
excludeContent: z.coerce.boolean().optional()
|
|
20563
23337
|
}).optional()
|
|
20564
23338
|
});
|
|
@@ -21910,6 +24684,15 @@ z.object({
|
|
|
21910
24684
|
isPersonal: z.boolean(),
|
|
21911
24685
|
mementos: z.array(SingleMementoEvalSchema).optional()
|
|
21912
24686
|
});
|
|
24687
|
+
new Set([{
|
|
24688
|
+
key: "internal",
|
|
24689
|
+
audienceType: "internal",
|
|
24690
|
+
label: "Internal"
|
|
24691
|
+
}, {
|
|
24692
|
+
key: "customer",
|
|
24693
|
+
audienceType: "customer",
|
|
24694
|
+
label: "Customer"
|
|
24695
|
+
}].map((v) => v.key));
|
|
21913
24696
|
crypto_exports.safeCompareTokens;
|
|
21914
24697
|
//#endregion
|
|
21915
24698
|
//#region ../../b4m-core/services/dist/llm/tools/cliTools.mjs
|
|
@@ -25008,16 +27791,21 @@ function extractUsageInfo(parsed) {
|
|
|
25008
27791
|
return {
|
|
25009
27792
|
inputTokens: parsed.usage?.inputTokens,
|
|
25010
27793
|
outputTokens: parsed.usage?.outputTokens,
|
|
27794
|
+
cacheReadInputTokens: parsed.usage?.cacheReadInputTokens,
|
|
27795
|
+
cacheCreationInputTokens: parsed.usage?.cacheCreationInputTokens,
|
|
25011
27796
|
creditsUsed: parsed.credits?.used,
|
|
25012
27797
|
usdCost: parsed.credits?.usdCost
|
|
25013
27798
|
};
|
|
25014
27799
|
}
|
|
25015
27800
|
/**
|
|
25016
|
-
*
|
|
25017
|
-
*
|
|
27801
|
+
* Reduces a stream of {@link StreamEvent}s (text, tool calls, thinking blocks,
|
|
27802
|
+
* usage) into a single accumulated assistant turn, then fires the completion
|
|
27803
|
+
* callback once at the end.
|
|
25018
27804
|
*
|
|
25019
|
-
*
|
|
25020
|
-
*
|
|
27805
|
+
* Both backends — `ServerLlmBackend` (SSE) and `WebSocketLlmBackend` (WebSocket
|
|
27806
|
+
* frames) — decode their wire payloads into the shared event union and feed
|
|
27807
|
+
* each event to {@link apply}, so accumulation logic lives in exactly one
|
|
27808
|
+
* place and is transport-agnostic.
|
|
25021
27809
|
*/
|
|
25022
27810
|
var StreamAccumulator = class {
|
|
25023
27811
|
constructor() {
|
|
@@ -25026,21 +27814,25 @@ var StreamAccumulator = class {
|
|
|
25026
27814
|
this.thinkingBlocks = [];
|
|
25027
27815
|
this.lastUsageInfo = {};
|
|
25028
27816
|
}
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
|
|
25032
|
-
|
|
25033
|
-
|
|
25034
|
-
|
|
25035
|
-
|
|
25036
|
-
|
|
25037
|
-
|
|
25038
|
-
|
|
25039
|
-
|
|
25040
|
-
|
|
25041
|
-
|
|
25042
|
-
|
|
25043
|
-
|
|
27817
|
+
/**
|
|
27818
|
+
* Fold one streaming event into the accumulated turn. `error` events carry no
|
|
27819
|
+
* accumulable content — backends surface them by rejecting — so they are a
|
|
27820
|
+
* no-op here.
|
|
27821
|
+
*/
|
|
27822
|
+
apply(event) {
|
|
27823
|
+
switch (event.type) {
|
|
27824
|
+
case "content":
|
|
27825
|
+
this.accumulatedText += event.text ?? "";
|
|
27826
|
+
if (event.usage || event.credits) this.lastUsageInfo = extractUsageInfo(event);
|
|
27827
|
+
break;
|
|
27828
|
+
case "tool_use":
|
|
27829
|
+
if (event.text) this.accumulatedText += event.text;
|
|
27830
|
+
if (event.tools && event.tools.length > 0) this.toolsUsed = event.tools;
|
|
27831
|
+
if (event.thinking && event.thinking.length > 0) this.thinkingBlocks = event.thinking;
|
|
27832
|
+
if (event.usage || event.credits) this.lastUsageInfo = extractUsageInfo(event);
|
|
27833
|
+
break;
|
|
27834
|
+
case "error": break;
|
|
27835
|
+
}
|
|
25044
27836
|
}
|
|
25045
27837
|
/** True when neither text nor tools have been accumulated (stream produced nothing useful). */
|
|
25046
27838
|
isEmpty() {
|
|
@@ -25073,6 +27865,93 @@ var StreamAccumulator = class {
|
|
|
25073
27865
|
}
|
|
25074
27866
|
};
|
|
25075
27867
|
//#endregion
|
|
27868
|
+
//#region src/llm/streamEvents.ts
|
|
27869
|
+
/**
|
|
27870
|
+
* Unified streaming event protocol for CLI LLM backends.
|
|
27871
|
+
*
|
|
27872
|
+
* Both transports — `ServerLlmBackend` (SSE) and `WebSocketLlmBackend`
|
|
27873
|
+
* (HTTP POST + WebSocket frames) — decode their wire payloads into this single
|
|
27874
|
+
* typed union, then feed it to `StreamAccumulator`. This collapses the two
|
|
27875
|
+
* previously-duplicated, loosely-typed chunk shapes (`SSEEvent` in
|
|
27876
|
+
* ServerLlmBackend, the inline `chunk` type in WebSocketLlmBackend) into one
|
|
27877
|
+
* source of truth, and replaces the unchecked `as SSEEvent` cast on
|
|
27878
|
+
* server-provided data with a runtime-validated parse at the boundary.
|
|
27879
|
+
*
|
|
27880
|
+
* The vocabulary mirrors what the backends emit today (`content` / `tool_use` /
|
|
27881
|
+
* `error`) so the refactor changes no observable behavior. Finer-grained delta
|
|
27882
|
+
* events (text_delta / toolcall_delta / …) are intentionally out of scope — the
|
|
27883
|
+
* CLI accumulates a full turn before firing its callback, so per-delta events
|
|
27884
|
+
* would be a behavior change, not a refactor.
|
|
27885
|
+
*/
|
|
27886
|
+
/**
|
|
27887
|
+
* Token usage counts carried alongside content/tool_use events. Shape mirrors
|
|
27888
|
+
* `SSEContentEvent.usage` in `@bike4mind/common` (`sseEvents.ts`) — the
|
|
27889
|
+
* authoritative wire contract — including the Anthropic cache-token deltas
|
|
27890
|
+
* (#8155) so they survive the boundary parse instead of being stripped.
|
|
27891
|
+
*/
|
|
27892
|
+
const usageSchema = z.object({
|
|
27893
|
+
inputTokens: z.number().optional(),
|
|
27894
|
+
outputTokens: z.number().optional(),
|
|
27895
|
+
cacheReadInputTokens: z.number().optional(),
|
|
27896
|
+
cacheCreationInputTokens: z.number().optional()
|
|
27897
|
+
}).partial();
|
|
27898
|
+
/** Credit/cost accounting carried alongside content/tool_use events (SSE only). */
|
|
27899
|
+
const creditsSchema = z.object({
|
|
27900
|
+
used: z.number().optional(),
|
|
27901
|
+
usdCost: z.number().optional()
|
|
27902
|
+
}).partial();
|
|
27903
|
+
/**
|
|
27904
|
+
* A single tool call requested by the model. Shape mirrors the wire contract in
|
|
27905
|
+
* `@bike4mind/common` (`SSEContentEvent.tools` / `CompletionInfo.toolsUsed`):
|
|
27906
|
+
* the server sends `{ name, arguments?: string, id? }` — `arguments` is a raw
|
|
27907
|
+
* JSON string, NOT a parsed object. (The previous `input: Record<…>` shape was
|
|
27908
|
+
* a typed lie the old unchecked `as SSEEvent` cast hid; downstream consumers
|
|
27909
|
+
* read `tool.arguments`, e.g. `toolParallelizer.ts`.)
|
|
27910
|
+
*/
|
|
27911
|
+
const toolUseSchema = z.object({
|
|
27912
|
+
name: z.string(),
|
|
27913
|
+
arguments: z.string().optional(),
|
|
27914
|
+
id: z.string().optional()
|
|
27915
|
+
});
|
|
27916
|
+
/**
|
|
27917
|
+
* Discriminated union of streaming events. `thinking` blocks are opaque
|
|
27918
|
+
* provider-shaped objects (Anthropic extended thinking) replayed verbatim into
|
|
27919
|
+
* the next request, so they stay `unknown[]` rather than being modeled.
|
|
27920
|
+
*/
|
|
27921
|
+
const streamEventSchema = z.discriminatedUnion("type", [
|
|
27922
|
+
z.object({
|
|
27923
|
+
type: z.literal("content"),
|
|
27924
|
+
text: z.string().optional(),
|
|
27925
|
+
usage: usageSchema.optional(),
|
|
27926
|
+
credits: creditsSchema.optional()
|
|
27927
|
+
}),
|
|
27928
|
+
z.object({
|
|
27929
|
+
type: z.literal("tool_use"),
|
|
27930
|
+
text: z.string().optional(),
|
|
27931
|
+
tools: z.array(toolUseSchema).optional(),
|
|
27932
|
+
thinking: z.array(z.unknown()).optional(),
|
|
27933
|
+
usage: usageSchema.optional(),
|
|
27934
|
+
credits: creditsSchema.optional()
|
|
27935
|
+
}),
|
|
27936
|
+
z.object({
|
|
27937
|
+
type: z.literal("error"),
|
|
27938
|
+
message: z.string().optional()
|
|
27939
|
+
})
|
|
27940
|
+
]);
|
|
27941
|
+
/**
|
|
27942
|
+
* Validate an already-JSON-decoded wire payload against the event union.
|
|
27943
|
+
*
|
|
27944
|
+
* Returns the typed event on success, or `null` when the payload does not match
|
|
27945
|
+
* a known event shape. A `null` result is treated as "skip this event" by the
|
|
27946
|
+
* backends — preserving the prior behavior where an unrecognized `type` simply
|
|
27947
|
+
* fell through unhandled. (Malformed JSON is still caught upstream at the
|
|
27948
|
+
* `JSON.parse` boundary.)
|
|
27949
|
+
*/
|
|
27950
|
+
function parseStreamEvent(data) {
|
|
27951
|
+
const result = streamEventSchema.safeParse(data);
|
|
27952
|
+
return result.success ? result.data : null;
|
|
27953
|
+
}
|
|
27954
|
+
//#endregion
|
|
25076
27955
|
//#region src/llm/ServerLlmBackend.ts
|
|
25077
27956
|
/**
|
|
25078
27957
|
* Connection-level failures that should be retried rather than surfaced to the
|
|
@@ -25247,30 +28126,29 @@ var ServerLlmBackend = class ServerLlmBackend {
|
|
|
25247
28126
|
return;
|
|
25248
28127
|
}
|
|
25249
28128
|
try {
|
|
25250
|
-
const
|
|
25251
|
-
if (
|
|
28129
|
+
const event = parseStreamEvent(JSON.parse(data));
|
|
28130
|
+
if (!event) return;
|
|
28131
|
+
if (event.type === "error") {
|
|
25252
28132
|
receivedError = true;
|
|
25253
|
-
streamLogger.onCriticalEvent(eventCount, "ERROR",
|
|
25254
|
-
reject(new Error(
|
|
28133
|
+
streamLogger.onCriticalEvent(eventCount, "ERROR", event.message || "Server error");
|
|
28134
|
+
reject(new Error(event.message || "Server error"));
|
|
25255
28135
|
return;
|
|
25256
28136
|
}
|
|
25257
|
-
if (
|
|
25258
|
-
|
|
25259
|
-
|
|
25260
|
-
|
|
25261
|
-
|
|
25262
|
-
|
|
25263
|
-
if (parsed.tools && parsed.tools.length > 0) for (const tool of parsed.tools) {
|
|
28137
|
+
if (event.type === "content") {
|
|
28138
|
+
accumulator.apply(event);
|
|
28139
|
+
streamLogger.onContent(eventCount, event.text || "", accumulator.rawText);
|
|
28140
|
+
} else if (event.type === "tool_use") {
|
|
28141
|
+
streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${event.tools?.length}`);
|
|
28142
|
+
if (event.tools && event.tools.length > 0) for (const tool of event.tools) {
|
|
25264
28143
|
logger.debug(`TOOL REQUEST: ${tool.name}`);
|
|
25265
28144
|
try {
|
|
25266
|
-
|
|
25267
|
-
logger.debug(` Params: ${paramsStr}`);
|
|
28145
|
+
logger.debug(` Params: ${tool.arguments ?? "{}"}`);
|
|
25268
28146
|
} catch {
|
|
25269
28147
|
logger.debug(` Params: [Unable to stringify]`);
|
|
25270
28148
|
}
|
|
25271
28149
|
}
|
|
25272
|
-
accumulator.
|
|
25273
|
-
if (
|
|
28150
|
+
accumulator.apply(event);
|
|
28151
|
+
if (event.thinking && event.thinking.length > 0) streamLogger.onCriticalEvent(eventCount, "THINKING", `${event.thinking.length} thinking blocks`);
|
|
25274
28152
|
}
|
|
25275
28153
|
} catch (parseError) {
|
|
25276
28154
|
streamLogger.streamError(parseError);
|
|
@@ -25451,20 +28329,22 @@ var FallbackLlmBackend = class {
|
|
|
25451
28329
|
async complete(model, messages, options, callback) {
|
|
25452
28330
|
if (options.abortSignal?.aborted) return this.inner.complete(model, messages, options, callback);
|
|
25453
28331
|
const modelsToTry = [model, ...this.fallbackModels.filter((m) => m !== model)];
|
|
25454
|
-
let callbackInvoked = false;
|
|
25455
|
-
const guardedCallback = async (text, completionInfo) => {
|
|
25456
|
-
callbackInvoked = true;
|
|
25457
|
-
return callback(text, completionInfo);
|
|
25458
|
-
};
|
|
25459
28332
|
let lastError;
|
|
25460
28333
|
for (let i = 0; i < modelsToTry.length; i++) {
|
|
25461
28334
|
const modelToTry = modelsToTry[i];
|
|
28335
|
+
const buffered = [];
|
|
28336
|
+
const bufferingCallback = async (text, completionInfo) => {
|
|
28337
|
+
buffered.push({
|
|
28338
|
+
text,
|
|
28339
|
+
completionInfo
|
|
28340
|
+
});
|
|
28341
|
+
};
|
|
25462
28342
|
try {
|
|
25463
|
-
await this.inner.complete(modelToTry, messages, options,
|
|
28343
|
+
await this.inner.complete(modelToTry, messages, options, bufferingCallback);
|
|
28344
|
+
for (const delivery of buffered) await callback(delivery.text, delivery.completionInfo);
|
|
25464
28345
|
return;
|
|
25465
28346
|
} catch (error) {
|
|
25466
28347
|
if (options.abortSignal?.aborted) throw error;
|
|
25467
|
-
if (callbackInvoked) throw error;
|
|
25468
28348
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
25469
28349
|
const nextModel = modelsToTry[i + 1];
|
|
25470
28350
|
if (nextModel) {
|
|
@@ -27371,12 +30251,14 @@ var WebSocketLlmBackend = class {
|
|
|
27371
30251
|
eventCount++;
|
|
27372
30252
|
const chunk = message.chunk;
|
|
27373
30253
|
streamLogger.onEvent(eventCount, JSON.stringify(chunk));
|
|
27374
|
-
|
|
27375
|
-
|
|
27376
|
-
|
|
27377
|
-
|
|
27378
|
-
streamLogger.
|
|
27379
|
-
|
|
30254
|
+
const event = parseStreamEvent(chunk);
|
|
30255
|
+
if (!event) return;
|
|
30256
|
+
if (event.type === "content") {
|
|
30257
|
+
accumulator.apply(event);
|
|
30258
|
+
streamLogger.onContent(eventCount, event.text || "", accumulator.rawText);
|
|
30259
|
+
} else if (event.type === "tool_use") {
|
|
30260
|
+
streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${event.tools?.length}`);
|
|
30261
|
+
accumulator.apply(event);
|
|
27380
30262
|
}
|
|
27381
30263
|
} else if (action === "cli_completion_done") {
|
|
27382
30264
|
streamLogger.streamComplete(accumulator.rawText);
|