@bike4mind/cli 0.2.31-feat-wolfram-alpha-tool.19541 → 0.2.31-feat-python-playground.19625

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/index.js CHANGED
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunk-GQGOWACU.js";
3
- import "./chunk-MHXRROUI.js";
4
- import "./chunk-WO3ITPAK.js";
3
+ import "./chunk-ZHXVJFCI.js";
4
+ import "./chunk-RTD3GUAP.js";
5
5
  import "./chunk-BPFEGDC7.js";
6
6
  import "./chunk-BDQBOLYG.js";
7
7
  import {
8
8
  getEffectiveApiKey,
9
9
  getOpenWeatherKey,
10
- getSerperKey,
11
- getWolframAlphaKey
12
- } from "./chunk-RCVCDNU6.js";
10
+ getSerperKey
11
+ } from "./chunk-LAZAO77H.js";
13
12
  import {
14
13
  ConfigStore,
15
14
  logger
@@ -17,7 +16,7 @@ import {
17
16
  import {
18
17
  checkForUpdate,
19
18
  package_default
20
- } from "./chunk-DRPTZ5JQ.js";
19
+ } from "./chunk-S3S2FV2N.js";
21
20
  import {
22
21
  selectActiveBackgroundAgents,
23
22
  useCliStore
@@ -33,7 +32,7 @@ import {
33
32
  OpenAIBackend,
34
33
  OpenAIImageService,
35
34
  XAIImageService
36
- } from "./chunk-WZURXWHI.js";
35
+ } from "./chunk-2PXPXXDN.js";
37
36
  import {
38
37
  AiEvents,
39
38
  ApiKeyEvents,
@@ -91,7 +90,7 @@ import {
91
90
  getMcpProviderMetadata,
92
91
  getViewById,
93
92
  resolveNavigationIntents
94
- } from "./chunk-KTHRGR3M.js";
93
+ } from "./chunk-EEGKRFVP.js";
95
94
  import {
96
95
  Logger
97
96
  } from "./chunk-PFBYGCOW.js";
@@ -734,8 +733,25 @@ var COMMANDS = [
734
733
  },
735
734
  {
736
735
  name: "rewind",
737
- description: "Rewind conversation to a previous point",
738
- aliases: ["undo"]
736
+ description: "Rewind conversation to a previous point"
737
+ },
738
+ {
739
+ name: "undo",
740
+ description: "Undo the last file change"
741
+ },
742
+ {
743
+ name: "checkpoints",
744
+ description: "List available file restore points"
745
+ },
746
+ {
747
+ name: "restore",
748
+ description: "Restore files to a specific checkpoint",
749
+ args: "<number>"
750
+ },
751
+ {
752
+ name: "diff",
753
+ description: "Show diff between current state and a checkpoint",
754
+ args: "[number]"
739
755
  },
740
756
  {
741
757
  name: "project-config",
@@ -1352,11 +1368,11 @@ var JobItem = React8.memo(function JobItem2({
1352
1368
  job,
1353
1369
  indented = false
1354
1370
  }) {
1355
- const elapsed = Math.round((Date.now() - job.startTime) / 1e3);
1371
+ const elapsed2 = Math.round((Date.now() - job.startTime) / 1e3);
1356
1372
  const maxTaskLength = indented ? 50 : 60;
1357
1373
  const taskPreview = job.task.length > maxTaskLength ? job.task.slice(0, maxTaskLength - 3) + "..." : job.task;
1358
1374
  const isQueued = job.status === "queued";
1359
- return /* @__PURE__ */ React8.createElement(Box7, null, indented && /* @__PURE__ */ React8.createElement(Text7, null, " "), isQueued ? /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "\u23F3") : /* @__PURE__ */ React8.createElement(Text7, { color: "blue" }, /* @__PURE__ */ React8.createElement(Spinner2, { type: "dots" })), /* @__PURE__ */ React8.createElement(Text7, { color: isQueued ? "yellow" : "blue" }, " ", job.agentName), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " ", "[", job.id, "] ", taskPreview, " ", isQueued ? "(queued)" : `(${elapsed}s)`));
1375
+ return /* @__PURE__ */ React8.createElement(Box7, null, indented && /* @__PURE__ */ React8.createElement(Text7, null, " "), isQueued ? /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "\u23F3") : /* @__PURE__ */ React8.createElement(Text7, { color: "blue" }, /* @__PURE__ */ React8.createElement(Spinner2, { type: "dots" })), /* @__PURE__ */ React8.createElement(Text7, { color: isQueued ? "yellow" : "blue" }, " ", job.agentName), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " ", "[", job.id, "] ", taskPreview, " ", isQueued ? "(queued)" : `(${elapsed2}s)`));
1360
1376
  });
1361
1377
  function formatStatusCounts(jobs) {
1362
1378
  let running = 0;
@@ -2839,9 +2855,346 @@ var CommandHistoryStore = class {
2839
2855
  }
2840
2856
  };
2841
2857
 
2842
- // src/storage/CustomCommandStore.ts
2843
- import fs5 from "fs/promises";
2858
+ // src/storage/CheckpointStore.ts
2859
+ import { promises as fs5 } from "fs";
2860
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync, unlinkSync } from "fs";
2861
+ import { execFileSync } from "child_process";
2844
2862
  import path6 from "path";
2863
+ var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
2864
+ var DEFAULT_KEEP_COUNT = 50;
2865
+ var ABSENT_MARKER = ".b4m-absent";
2866
+ var CheckpointStore = class {
2867
+ constructor(projectDir) {
2868
+ this.metadata = null;
2869
+ this.sessionId = null;
2870
+ this.initialized = false;
2871
+ this.projectDir = projectDir;
2872
+ this.shadowRepoDir = path6.join(projectDir, ".b4m", "shadow-repo");
2873
+ this.metadataPath = path6.join(projectDir, ".b4m", "checkpoints.json");
2874
+ }
2875
+ /**
2876
+ * Initialize the shadow git repository and load metadata
2877
+ */
2878
+ async init(sessionId) {
2879
+ this.sessionId = sessionId;
2880
+ await fs5.mkdir(path6.join(this.projectDir, ".b4m"), { recursive: true });
2881
+ if (!existsSync4(path6.join(this.shadowRepoDir, ".git"))) {
2882
+ await fs5.mkdir(this.shadowRepoDir, { recursive: true });
2883
+ this.git("init");
2884
+ this.git("config", "user.email", "checkpoint@b4m.local");
2885
+ this.git("config", "user.name", "B4M Checkpoint");
2886
+ this.git("commit", "--allow-empty", "-m", "checkpoint-init");
2887
+ }
2888
+ await this.loadMetadata();
2889
+ await this.ensureGitignore();
2890
+ await this.pruneCheckpoints(DEFAULT_KEEP_COUNT);
2891
+ this.initialized = true;
2892
+ }
2893
+ /**
2894
+ * Update session ID (e.g., on /clear or /resume)
2895
+ */
2896
+ setSessionId(sessionId) {
2897
+ this.sessionId = sessionId;
2898
+ }
2899
+ /**
2900
+ * Create a checkpoint by snapshotting the current state of target files
2901
+ * before they are modified by a tool.
2902
+ *
2903
+ * @param toolName - The tool about to modify files
2904
+ * @param filePaths - Relative paths of files about to be modified
2905
+ * @param name - Optional human-readable checkpoint name
2906
+ */
2907
+ async createCheckpoint(toolName, filePaths, name) {
2908
+ if (!this.initialized || !this.sessionId) {
2909
+ return null;
2910
+ }
2911
+ const checkpointName = name || `before-${toolName}-${path6.basename(filePaths[0] || "unknown")}`;
2912
+ try {
2913
+ let hasChanges = false;
2914
+ for (const filePath of filePaths) {
2915
+ const absolutePath = this.validatePathWithinProject(filePath);
2916
+ const shadowPath = path6.join(this.shadowRepoDir, filePath);
2917
+ const shadowDir = path6.dirname(shadowPath);
2918
+ const absentMarkerPath = path6.join(shadowDir, `${path6.basename(filePath)}${ABSENT_MARKER}`);
2919
+ await fs5.mkdir(shadowDir, { recursive: true });
2920
+ if (existsSync4(absolutePath)) {
2921
+ const stats = await fs5.lstat(absolutePath);
2922
+ if (stats.isSymbolicLink()) {
2923
+ continue;
2924
+ }
2925
+ if (stats.size > MAX_FILE_SIZE2) {
2926
+ continue;
2927
+ }
2928
+ if (existsSync4(absentMarkerPath)) {
2929
+ await fs5.unlink(absentMarkerPath);
2930
+ }
2931
+ await fs5.copyFile(absolutePath, shadowPath);
2932
+ hasChanges = true;
2933
+ } else {
2934
+ if (existsSync4(shadowPath)) {
2935
+ await fs5.unlink(shadowPath);
2936
+ }
2937
+ await fs5.writeFile(absentMarkerPath, "", "utf-8");
2938
+ hasChanges = true;
2939
+ }
2940
+ }
2941
+ if (!hasChanges) {
2942
+ return null;
2943
+ }
2944
+ this.git("add", "-A");
2945
+ try {
2946
+ this.git("diff", "--cached", "--quiet");
2947
+ return null;
2948
+ } catch {
2949
+ }
2950
+ this.git("commit", "-m", checkpointName);
2951
+ const sha = this.git("rev-parse", "--short", "HEAD").trim();
2952
+ const checkpoint = {
2953
+ id: sha,
2954
+ name: checkpointName,
2955
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2956
+ toolName,
2957
+ filePaths: [...filePaths],
2958
+ sessionId: this.sessionId
2959
+ };
2960
+ if (!this.metadata) {
2961
+ this.metadata = { checkpoints: [], createdAt: (/* @__PURE__ */ new Date()).toISOString() };
2962
+ }
2963
+ this.metadata.checkpoints.push(checkpoint);
2964
+ await this.saveMetadata();
2965
+ return checkpoint;
2966
+ } catch {
2967
+ return null;
2968
+ }
2969
+ }
2970
+ /**
2971
+ * List checkpoints for the current session (most recent first)
2972
+ */
2973
+ listCheckpoints() {
2974
+ if (!this.metadata || !this.sessionId) {
2975
+ return [];
2976
+ }
2977
+ return this.metadata.checkpoints.filter((cp) => cp.sessionId === this.sessionId).reverse();
2978
+ }
2979
+ /**
2980
+ * Get a specific checkpoint by 1-based index (1 = most recent)
2981
+ */
2982
+ getCheckpoint(index) {
2983
+ const checkpoints = this.listCheckpoints();
2984
+ if (index < 1 || index > checkpoints.length) {
2985
+ return null;
2986
+ }
2987
+ return checkpoints[index - 1];
2988
+ }
2989
+ /**
2990
+ * Restore files to the state captured in a specific checkpoint
2991
+ *
2992
+ * @param index - 1-based index (1 = most recent)
2993
+ * @returns The checkpoint that was restored to
2994
+ */
2995
+ async restoreCheckpoint(index) {
2996
+ const checkpoint = this.getCheckpoint(index);
2997
+ if (!checkpoint) {
2998
+ throw new Error(`Checkpoint #${index} not found. Use /checkpoints to see available restore points.`);
2999
+ }
3000
+ for (const filePath of checkpoint.filePaths) {
3001
+ const absolutePath = this.validatePathWithinProject(filePath);
3002
+ try {
3003
+ const content = this.git("show", `${checkpoint.id}:${filePath}`);
3004
+ await fs5.mkdir(path6.dirname(absolutePath), { recursive: true });
3005
+ await fs5.writeFile(absolutePath, content, "utf-8");
3006
+ } catch {
3007
+ try {
3008
+ this.git("show", `${checkpoint.id}:${path6.dirname(filePath)}/${path6.basename(filePath)}${ABSENT_MARKER}`);
3009
+ if (existsSync4(absolutePath)) {
3010
+ await fs5.unlink(absolutePath);
3011
+ }
3012
+ } catch {
3013
+ }
3014
+ }
3015
+ }
3016
+ return checkpoint;
3017
+ }
3018
+ /**
3019
+ * Undo the last file change (restore to most recent checkpoint)
3020
+ */
3021
+ async undoLast() {
3022
+ return this.restoreCheckpoint(1);
3023
+ }
3024
+ /**
3025
+ * Get diff between current file state and a checkpoint
3026
+ *
3027
+ * @param index - 1-based index (1 = most recent, default)
3028
+ * @returns Unified diff string
3029
+ */
3030
+ getCheckpointDiff(index = 1) {
3031
+ const checkpoint = this.getCheckpoint(index);
3032
+ if (!checkpoint) {
3033
+ throw new Error(`Checkpoint #${index} not found. Use /checkpoints to see available restore points.`);
3034
+ }
3035
+ const diffParts = [];
3036
+ for (const filePath of checkpoint.filePaths) {
3037
+ const absolutePath = this.validatePathWithinProject(filePath);
3038
+ const tmpCheckpoint = path6.join(this.shadowRepoDir, ".diff-a");
3039
+ const tmpCurrent = path6.join(this.shadowRepoDir, ".diff-b");
3040
+ try {
3041
+ let checkpointContent;
3042
+ try {
3043
+ checkpointContent = this.git("show", `${checkpoint.id}:${filePath}`);
3044
+ } catch {
3045
+ checkpointContent = "";
3046
+ }
3047
+ let currentContent = "";
3048
+ if (existsSync4(absolutePath)) {
3049
+ currentContent = readFileSync4(absolutePath, "utf-8");
3050
+ }
3051
+ if (checkpointContent === currentContent) {
3052
+ continue;
3053
+ }
3054
+ writeFileSync(tmpCheckpoint, checkpointContent, "utf-8");
3055
+ writeFileSync(tmpCurrent, currentContent, "utf-8");
3056
+ try {
3057
+ this.git(
3058
+ "diff",
3059
+ "--no-index",
3060
+ "--color",
3061
+ `--src-prefix=checkpoint:`,
3062
+ `--dst-prefix=current:`,
3063
+ tmpCheckpoint,
3064
+ tmpCurrent
3065
+ );
3066
+ } catch (diffError) {
3067
+ if (diffError && typeof diffError === "object" && "stdout" in diffError) {
3068
+ const output = diffError.stdout?.toString() || "";
3069
+ if (output) {
3070
+ diffParts.push(`--- ${filePath} (checkpoint #${index})
3071
+ +++ ${filePath} (current)
3072
+ ${output}`);
3073
+ }
3074
+ }
3075
+ }
3076
+ } catch {
3077
+ } finally {
3078
+ try {
3079
+ unlinkSync(tmpCheckpoint);
3080
+ } catch {
3081
+ }
3082
+ try {
3083
+ unlinkSync(tmpCurrent);
3084
+ } catch {
3085
+ }
3086
+ }
3087
+ }
3088
+ return diffParts.join("\n");
3089
+ }
3090
+ /**
3091
+ * Prune old checkpoints beyond the keep count
3092
+ */
3093
+ async pruneCheckpoints(keepCount = DEFAULT_KEEP_COUNT) {
3094
+ if (!this.metadata) return;
3095
+ const total = this.metadata.checkpoints.length;
3096
+ if (total <= keepCount) return;
3097
+ this.metadata.checkpoints = this.metadata.checkpoints.slice(-keepCount);
3098
+ await this.saveMetadata();
3099
+ try {
3100
+ this.git("gc", "--auto", "--quiet");
3101
+ } catch {
3102
+ }
3103
+ }
3104
+ /**
3105
+ * Clean up the shadow repository entirely
3106
+ */
3107
+ async cleanup() {
3108
+ try {
3109
+ await fs5.rm(this.shadowRepoDir, { recursive: true, force: true });
3110
+ if (existsSync4(this.metadataPath)) {
3111
+ await fs5.unlink(this.metadataPath);
3112
+ }
3113
+ this.metadata = null;
3114
+ this.initialized = false;
3115
+ } catch {
3116
+ }
3117
+ }
3118
+ // --- Private helpers ---
3119
+ /**
3120
+ * Validate that a file path resolves within the project directory.
3121
+ * Prevents path traversal attacks (e.g., ../../etc/passwd).
3122
+ */
3123
+ validatePathWithinProject(filePath) {
3124
+ const absolutePath = path6.resolve(this.projectDir, filePath);
3125
+ const normalizedProject = path6.resolve(this.projectDir) + path6.sep;
3126
+ if (!absolutePath.startsWith(normalizedProject) && absolutePath !== path6.resolve(this.projectDir)) {
3127
+ throw new Error(`Path traversal detected: ${filePath}`);
3128
+ }
3129
+ return absolutePath;
3130
+ }
3131
+ /**
3132
+ * Execute a git command in the shadow repo
3133
+ */
3134
+ git(...args) {
3135
+ return execFileSync("git", args, {
3136
+ cwd: this.shadowRepoDir,
3137
+ encoding: "utf-8",
3138
+ stdio: ["pipe", "pipe", "pipe"],
3139
+ timeout: 1e4
3140
+ });
3141
+ }
3142
+ /**
3143
+ * Load checkpoint metadata from disk
3144
+ */
3145
+ async loadMetadata() {
3146
+ try {
3147
+ if (existsSync4(this.metadataPath)) {
3148
+ const data = await fs5.readFile(this.metadataPath, "utf-8");
3149
+ this.metadata = JSON.parse(data);
3150
+ } else {
3151
+ this.metadata = {
3152
+ checkpoints: [],
3153
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3154
+ };
3155
+ }
3156
+ } catch {
3157
+ this.metadata = {
3158
+ checkpoints: [],
3159
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3160
+ };
3161
+ }
3162
+ }
3163
+ /**
3164
+ * Save checkpoint metadata to disk
3165
+ */
3166
+ async saveMetadata() {
3167
+ if (!this.metadata) return;
3168
+ await fs5.writeFile(this.metadataPath, JSON.stringify(this.metadata, null, 2), "utf-8");
3169
+ }
3170
+ /**
3171
+ * Ensure .b4m/ is in .gitignore
3172
+ */
3173
+ async ensureGitignore() {
3174
+ const gitignorePath = path6.join(this.projectDir, ".gitignore");
3175
+ const entryToAdd = ".b4m/";
3176
+ try {
3177
+ let content = "";
3178
+ try {
3179
+ content = await fs5.readFile(gitignorePath, "utf-8");
3180
+ } catch {
3181
+ }
3182
+ if (content.includes(entryToAdd) || content.includes(".b4m")) {
3183
+ return;
3184
+ }
3185
+ const newContent = content.trim() + (content ? "\n" : "") + `
3186
+ # B4M checkpoint data
3187
+ ${entryToAdd}
3188
+ `;
3189
+ await fs5.writeFile(gitignorePath, newContent, "utf-8");
3190
+ } catch {
3191
+ }
3192
+ }
3193
+ };
3194
+
3195
+ // src/storage/CustomCommandStore.ts
3196
+ import fs6 from "fs/promises";
3197
+ import path7 from "path";
2845
3198
  import os from "os";
2846
3199
 
2847
3200
  // src/utils/commandParser.ts
@@ -3005,14 +3358,14 @@ var CustomCommandStore = class {
3005
3358
  const home = os.homedir();
3006
3359
  const root = projectRoot || process.cwd();
3007
3360
  this.globalCommandsDirs = [
3008
- path6.join(home, ".bike4mind", "commands"),
3009
- path6.join(home, ".claude", "commands"),
3010
- path6.join(home, ".claude", "skills")
3361
+ path7.join(home, ".bike4mind", "commands"),
3362
+ path7.join(home, ".claude", "commands"),
3363
+ path7.join(home, ".claude", "skills")
3011
3364
  ];
3012
3365
  this.projectCommandsDirs = [
3013
- path6.join(root, ".bike4mind", "commands"),
3014
- path6.join(root, ".claude", "commands"),
3015
- path6.join(root, ".claude", "skills")
3366
+ path7.join(root, ".bike4mind", "commands"),
3367
+ path7.join(root, ".claude", "commands"),
3368
+ path7.join(root, ".claude", "skills")
3016
3369
  ];
3017
3370
  }
3018
3371
  /**
@@ -3037,7 +3390,7 @@ var CustomCommandStore = class {
3037
3390
  */
3038
3391
  async loadCommandsFromDirectory(directory, source) {
3039
3392
  try {
3040
- const stats = await fs5.stat(directory);
3393
+ const stats = await fs6.stat(directory);
3041
3394
  if (!stats.isDirectory()) {
3042
3395
  return;
3043
3396
  }
@@ -3070,9 +3423,9 @@ var CustomCommandStore = class {
3070
3423
  async findCommandFiles(directory) {
3071
3424
  const files = [];
3072
3425
  try {
3073
- const entries = await fs5.readdir(directory, { withFileTypes: true });
3426
+ const entries = await fs6.readdir(directory, { withFileTypes: true });
3074
3427
  for (const entry of entries) {
3075
- const fullPath = path6.join(directory, entry.name);
3428
+ const fullPath = path7.join(directory, entry.name);
3076
3429
  if (entry.isDirectory()) {
3077
3430
  const subFiles = await this.findCommandFiles(fullPath);
3078
3431
  files.push(...subFiles);
@@ -3092,14 +3445,14 @@ var CustomCommandStore = class {
3092
3445
  * @param source - Source identifier ('global' or 'project')
3093
3446
  */
3094
3447
  async loadCommandFile(filePath, source) {
3095
- const filename = path6.basename(filePath);
3448
+ const filename = path7.basename(filePath);
3096
3449
  const isSkillFile = filename.toLowerCase() === "skill.md";
3097
3450
  const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
3098
3451
  if (!commandName) {
3099
3452
  console.warn(`Invalid command filename: ${filename} (must end with .md and have valid name)`);
3100
3453
  return;
3101
3454
  }
3102
- const fileContent = await fs5.readFile(filePath, "utf-8");
3455
+ const fileContent = await fs6.readFile(filePath, "utf-8");
3103
3456
  const command = parseCommandFile(fileContent, filePath, commandName, source);
3104
3457
  this.commands.set(commandName, command);
3105
3458
  }
@@ -3111,7 +3464,7 @@ var CustomCommandStore = class {
3111
3464
  * @returns Skill name or null if invalid
3112
3465
  */
3113
3466
  extractSkillName(filePath) {
3114
- const parentDir = path6.basename(path6.dirname(filePath));
3467
+ const parentDir = path7.basename(path7.dirname(filePath));
3115
3468
  return parentDir && parentDir !== "skills" ? parentDir : null;
3116
3469
  }
3117
3470
  /**
@@ -3173,15 +3526,15 @@ var CustomCommandStore = class {
3173
3526
  */
3174
3527
  async createCommandFile(name, isGlobal = false) {
3175
3528
  const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
3176
- const filePath = path6.join(targetDir, `${name}.md`);
3177
- const fileExists = await fs5.access(filePath).then(
3529
+ const filePath = path7.join(targetDir, `${name}.md`);
3530
+ const fileExists = await fs6.access(filePath).then(
3178
3531
  () => true,
3179
3532
  () => false
3180
3533
  );
3181
3534
  if (fileExists) {
3182
3535
  throw new Error(`Command file already exists: ${filePath}`);
3183
3536
  }
3184
- await fs5.mkdir(targetDir, { recursive: true });
3537
+ await fs6.mkdir(targetDir, { recursive: true });
3185
3538
  const template = `---
3186
3539
  description: ${name} command
3187
3540
  argument-hint: [args]
@@ -3196,7 +3549,7 @@ You can use:
3196
3549
  - $1, $2, etc. for positional arguments
3197
3550
  - @filename for file references
3198
3551
  `;
3199
- await fs5.writeFile(filePath, template, "utf-8");
3552
+ await fs6.writeFile(filePath, template, "utf-8");
3200
3553
  return filePath;
3201
3554
  }
3202
3555
  };
@@ -4428,7 +4781,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai";
4428
4781
  import OpenAI2 from "openai";
4429
4782
 
4430
4783
  // ../../b4m-core/packages/services/dist/src/importHistoryService/index.js
4431
- import fs6, { unlinkSync } from "fs";
4784
+ import fs7, { unlinkSync as unlinkSync2 } from "fs";
4432
4785
  import yauzl from "yauzl";
4433
4786
  import axios3 from "axios";
4434
4787
 
@@ -6119,8 +6472,8 @@ async function processAndStoreImages(images, context) {
6119
6472
  const buffer = await downloadImage(image);
6120
6473
  const fileType = await fileTypeFromBuffer2(buffer);
6121
6474
  const filename = `${uuidv45()}.${fileType?.ext}`;
6122
- const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
6123
- return path19;
6475
+ const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
6476
+ return path21;
6124
6477
  }));
6125
6478
  }
6126
6479
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -6485,116 +6838,6 @@ var webFetchTool = {
6485
6838
  })
6486
6839
  };
6487
6840
 
6488
- // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/wolfram_alpha/index.js
6489
- var MAX_QUERY_LENGTH = 500;
6490
- var MAX_RESPONSE_SIZE = 5e4;
6491
- function validateQueryParams(params) {
6492
- if (!params.query || typeof params.query !== "string") {
6493
- return "Invalid query parameter: query must be a non-empty string.";
6494
- }
6495
- const trimmedQuery = params.query.trim();
6496
- if (trimmedQuery.length === 0) {
6497
- return "Query cannot be empty.";
6498
- }
6499
- if (params.query.length > MAX_QUERY_LENGTH) {
6500
- return `Query is too long. Maximum ${MAX_QUERY_LENGTH} characters allowed.`;
6501
- }
6502
- return null;
6503
- }
6504
- async function wolframAlphaQuery(adapters, params, logger2) {
6505
- const validationError = validateQueryParams(params);
6506
- if (validationError) {
6507
- logger2?.error("Wolfram Alpha: Validation failed", { error: validationError });
6508
- return validationError;
6509
- }
6510
- const appId = await getWolframAlphaKey(adapters);
6511
- if (!appId) {
6512
- logger2?.error("Wolfram Alpha: No API key configured");
6513
- return "Wolfram Alpha is not configured. Please contact your administrator to set up the WolframAlphaKey in admin settings.";
6514
- }
6515
- const url = new URL("https://www.wolframalpha.com/api/v1/llm-api");
6516
- url.searchParams.set("input", params.query.trim());
6517
- url.searchParams.set("appid", appId);
6518
- if (params.maxchars) {
6519
- url.searchParams.set("maxchars", params.maxchars.toString());
6520
- }
6521
- const controller = new AbortController();
6522
- const timeoutId = setTimeout(() => controller.abort(), 3e4);
6523
- try {
6524
- logger2?.log("\u{1F522} Wolfram Alpha: Querying:", params.query);
6525
- const response = await fetch(url.toString(), {
6526
- method: "GET",
6527
- signal: controller.signal
6528
- });
6529
- clearTimeout(timeoutId);
6530
- logger2?.log("\u{1F4E1} Wolfram Alpha: Response status:", response.status);
6531
- if (!response.ok) {
6532
- const errorText = await response.text();
6533
- logger2?.error("Wolfram Alpha: API error", {
6534
- status: response.status,
6535
- statusText: response.statusText,
6536
- errorText
6537
- });
6538
- if (response.status === 501) {
6539
- return `Wolfram Alpha could not interpret this query. Try rephrasing with simpler keywords or mathematical notation.`;
6540
- }
6541
- if (response.status === 403) {
6542
- return `Wolfram Alpha API key is invalid or missing. Please contact your administrator.`;
6543
- }
6544
- return `Wolfram Alpha error: ${response.statusText}`;
6545
- }
6546
- const result = await response.text();
6547
- const maxSize = Math.min(params.maxchars || MAX_RESPONSE_SIZE, MAX_RESPONSE_SIZE);
6548
- const truncatedResult = result.slice(0, maxSize);
6549
- logger2?.log("\u2705 Wolfram Alpha: Query successful, response length:", truncatedResult.length);
6550
- return truncatedResult || "No results from Wolfram Alpha.";
6551
- } catch (error) {
6552
- clearTimeout(timeoutId);
6553
- if (error instanceof Error && error.name === "AbortError") {
6554
- logger2?.error("Wolfram Alpha: Request timed out");
6555
- return "Wolfram Alpha request timed out. Please try a simpler query.";
6556
- }
6557
- logger2?.error("Wolfram Alpha: Fetch error", error);
6558
- return "Failed to reach Wolfram Alpha. Please try again.";
6559
- }
6560
- }
6561
- var wolframAlphaTool = {
6562
- name: "wolfram_alpha",
6563
- implementation: (context) => ({
6564
- toolFn: async (value) => {
6565
- const params = value;
6566
- const result = await wolframAlphaQuery({ db: context.db }, params, context.logger);
6567
- return result;
6568
- },
6569
- toolSchema: {
6570
- name: "wolfram_alpha",
6571
- description: `Query Wolfram Alpha for computational knowledge. Use this tool for:
6572
- - Mathematical calculations (algebra, calculus, statistics, symbolic math)
6573
- - Unit conversions and physical constants
6574
- - Scientific queries (physics, chemistry, astronomy, biology)
6575
- - Data lookups (populations, distances, historical data, financial data)
6576
- - Equation solving and plotting
6577
- - Date/time calculations and conversions
6578
-
6579
- Do NOT use for: general knowledge questions, programming help, or simple arithmetic you can compute yourself.`,
6580
- parameters: {
6581
- type: "object",
6582
- properties: {
6583
- query: {
6584
- type: "string",
6585
- description: "Natural language query or mathematical expression. Use simplified keywords. For exponents, use notation like 6*10^14 (not 6e14)."
6586
- },
6587
- maxchars: {
6588
- type: "number",
6589
- description: "Maximum characters in response (default: 6800)"
6590
- }
6591
- },
6592
- required: ["query"]
6593
- }
6594
- }
6595
- })
6596
- };
6597
-
6598
6841
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/math/index.js
6599
6842
  import { create as create5, all } from "mathjs";
6600
6843
  var math = create5(all, {
@@ -7556,8 +7799,8 @@ async function processAndStoreImage(imageUrl, context) {
7556
7799
  const buffer = await downloadImage2(imageUrl);
7557
7800
  const fileType = await fileTypeFromBuffer3(buffer);
7558
7801
  const filename = `${uuidv46()}.${fileType?.ext}`;
7559
- const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
7560
- return path19;
7802
+ const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
7803
+ return path21;
7561
7804
  }
7562
7805
  async function generateFullMask(imageBuffer) {
7563
7806
  const sharp = (await import("sharp")).default;
@@ -9498,33 +9741,33 @@ var planetVisibilityTool = {
9498
9741
  };
9499
9742
 
9500
9743
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
9501
- import { promises as fs7 } from "fs";
9502
- import { existsSync as existsSync4, statSync as statSync4 } from "fs";
9503
- import path7 from "path";
9504
- var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
9744
+ import { promises as fs8 } from "fs";
9745
+ import { existsSync as existsSync5, statSync as statSync4 } from "fs";
9746
+ import path8 from "path";
9747
+ var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
9505
9748
  async function readFileContent(params) {
9506
9749
  const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
9507
- const normalizedPath = path7.normalize(filePath);
9508
- const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
9509
- const cwd = path7.resolve(process.cwd());
9750
+ const normalizedPath = path8.normalize(filePath);
9751
+ const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
9752
+ const cwd = path8.resolve(process.cwd());
9510
9753
  if (!resolvedPath.startsWith(cwd)) {
9511
9754
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
9512
9755
  }
9513
- if (!existsSync4(resolvedPath)) {
9756
+ if (!existsSync5(resolvedPath)) {
9514
9757
  throw new Error(`File not found: ${filePath}`);
9515
9758
  }
9516
9759
  const stats = statSync4(resolvedPath);
9517
9760
  if (stats.isDirectory()) {
9518
9761
  throw new Error(`Path is a directory, not a file: ${filePath}`);
9519
9762
  }
9520
- if (stats.size > MAX_FILE_SIZE2) {
9521
- throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE2 / 1024 / 1024}MB)`);
9763
+ if (stats.size > MAX_FILE_SIZE3) {
9764
+ throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE3 / 1024 / 1024}MB)`);
9522
9765
  }
9523
9766
  const isBinary = await checkIfBinary(resolvedPath);
9524
9767
  if (isBinary && encoding === "utf-8") {
9525
9768
  throw new Error(`File appears to be binary. Use encoding 'base64' to read binary files, or specify a different encoding.`);
9526
9769
  }
9527
- const content = await fs7.readFile(resolvedPath, encoding);
9770
+ const content = await fs8.readFile(resolvedPath, encoding);
9528
9771
  if (typeof content === "string") {
9529
9772
  const lines = content.split("\n");
9530
9773
  const totalLines = lines.length;
@@ -9562,7 +9805,7 @@ ${content}`;
9562
9805
  }
9563
9806
  async function checkIfBinary(filePath) {
9564
9807
  const buffer = Buffer.alloc(8192);
9565
- const fd = await fs7.open(filePath, "r");
9808
+ const fd = await fs8.open(filePath, "r");
9566
9809
  try {
9567
9810
  const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
9568
9811
  const chunk = buffer.slice(0, bytesRead);
@@ -9579,7 +9822,7 @@ var fileReadTool = {
9579
9822
  context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
9580
9823
  try {
9581
9824
  const content = await readFileContent(params);
9582
- const stats = statSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
9825
+ const stats = statSync4(path8.resolve(process.cwd(), path8.normalize(params.path)));
9583
9826
  context.logger.info("\u2705 FileRead: Success", {
9584
9827
  path: params.path,
9585
9828
  size: stats.size,
@@ -9623,25 +9866,25 @@ var fileReadTool = {
9623
9866
  };
9624
9867
 
9625
9868
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
9626
- import { promises as fs8 } from "fs";
9627
- import { existsSync as existsSync5 } from "fs";
9628
- import path8 from "path";
9869
+ import { promises as fs9 } from "fs";
9870
+ import { existsSync as existsSync6 } from "fs";
9871
+ import path9 from "path";
9629
9872
  async function createFile(params) {
9630
9873
  const { path: filePath, content, createDirectories = true } = params;
9631
- const normalizedPath = path8.normalize(filePath);
9632
- const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
9633
- const cwd = path8.resolve(process.cwd());
9874
+ const normalizedPath = path9.normalize(filePath);
9875
+ const resolvedPath = path9.resolve(process.cwd(), normalizedPath);
9876
+ const cwd = path9.resolve(process.cwd());
9634
9877
  if (!resolvedPath.startsWith(cwd)) {
9635
9878
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
9636
9879
  }
9637
- const fileExists = existsSync5(resolvedPath);
9880
+ const fileExists = existsSync6(resolvedPath);
9638
9881
  const action = fileExists ? "overwritten" : "created";
9639
9882
  if (createDirectories) {
9640
- const dir = path8.dirname(resolvedPath);
9641
- await fs8.mkdir(dir, { recursive: true });
9883
+ const dir = path9.dirname(resolvedPath);
9884
+ await fs9.mkdir(dir, { recursive: true });
9642
9885
  }
9643
- await fs8.writeFile(resolvedPath, content, "utf-8");
9644
- const stats = await fs8.stat(resolvedPath);
9886
+ await fs9.writeFile(resolvedPath, content, "utf-8");
9887
+ const stats = await fs9.stat(resolvedPath);
9645
9888
  const lines = content.split("\n").length;
9646
9889
  return `File ${action} successfully: ${filePath}
9647
9890
  Size: ${stats.size} bytes
@@ -9652,7 +9895,7 @@ var createFileTool = {
9652
9895
  implementation: (context) => ({
9653
9896
  toolFn: async (value) => {
9654
9897
  const params = value;
9655
- const fileExists = existsSync5(path8.resolve(process.cwd(), path8.normalize(params.path)));
9898
+ const fileExists = existsSync6(path9.resolve(process.cwd(), path9.normalize(params.path)));
9656
9899
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
9657
9900
  path: params.path,
9658
9901
  size: params.content.length
@@ -9694,7 +9937,7 @@ var createFileTool = {
9694
9937
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
9695
9938
  import { glob } from "glob";
9696
9939
  import { stat } from "fs/promises";
9697
- import path9 from "path";
9940
+ import path10 from "path";
9698
9941
  var DEFAULT_IGNORE_PATTERNS = [
9699
9942
  "**/node_modules/**",
9700
9943
  "**/.git/**",
@@ -9710,7 +9953,7 @@ var DEFAULT_IGNORE_PATTERNS = [
9710
9953
  async function findFiles(params) {
9711
9954
  const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
9712
9955
  const baseCwd = process.cwd();
9713
- const targetDir = dir_path ? path9.resolve(baseCwd, path9.normalize(dir_path)) : baseCwd;
9956
+ const targetDir = dir_path ? path10.resolve(baseCwd, path10.normalize(dir_path)) : baseCwd;
9714
9957
  if (!targetDir.startsWith(baseCwd)) {
9715
9958
  throw new Error(`Access denied: Cannot search outside of current working directory`);
9716
9959
  }
@@ -9750,7 +9993,7 @@ async function findFiles(params) {
9750
9993
  const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
9751
9994
  const dirInfo = dir_path ? `
9752
9995
  Directory: ${dir_path}` : "";
9753
- const filesList = results.map((file) => path9.relative(baseCwd, file.path)).join("\n");
9996
+ const filesList = results.map((file) => path10.relative(baseCwd, file.path)).join("\n");
9754
9997
  return `${summary}${dirInfo}
9755
9998
 
9756
9999
  ${filesList}`;
@@ -9804,27 +10047,48 @@ var globFilesTool = {
9804
10047
 
9805
10048
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
9806
10049
  import { stat as stat2 } from "fs/promises";
9807
- import path10 from "path";
9808
- import { execFile } from "child_process";
10050
+ import { existsSync as existsSync7 } from "fs";
10051
+ import path11 from "path";
10052
+ import { execFile, execFileSync as execFileSync2 } from "child_process";
9809
10053
  import { promisify } from "util";
9810
10054
  import { createRequire } from "module";
9811
10055
  var execFileAsync = promisify(execFile);
9812
10056
  var require2 = createRequire(import.meta.url);
10057
+ var cachedRgPath = null;
9813
10058
  function getRipgrepPath() {
10059
+ if (cachedRgPath)
10060
+ return cachedRgPath;
9814
10061
  try {
9815
10062
  const ripgrepPath = require2.resolve("@vscode/ripgrep");
9816
- const ripgrepDir = path10.dirname(ripgrepPath);
9817
- const rgBinary = path10.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
10063
+ const ripgrepDir = path11.dirname(ripgrepPath);
10064
+ const rgBinary = path11.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
10065
+ if (!existsSync7(rgBinary)) {
10066
+ const postinstallScript = path11.join(ripgrepDir, "..", "lib", "postinstall.js");
10067
+ if (existsSync7(postinstallScript)) {
10068
+ execFileSync2(process.execPath, [postinstallScript], {
10069
+ cwd: path11.join(ripgrepDir, ".."),
10070
+ stdio: "pipe",
10071
+ timeout: 3e4
10072
+ });
10073
+ }
10074
+ if (!existsSync7(rgBinary)) {
10075
+ throw new Error(`ripgrep binary not found at ${rgBinary}. The postinstall download may have failed. Try running: cd ${path11.join(ripgrepDir, "..")} && node lib/postinstall.js`);
10076
+ }
10077
+ }
10078
+ cachedRgPath = rgBinary;
9818
10079
  return rgBinary;
9819
10080
  } catch (error) {
10081
+ if (error instanceof Error && error.message.includes("ripgrep binary not found")) {
10082
+ throw error;
10083
+ }
9820
10084
  throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
9821
10085
  }
9822
10086
  }
9823
10087
  function isPathWithinWorkspace(targetPath, baseCwd) {
9824
- const resolvedTarget = path10.resolve(targetPath);
9825
- const resolvedBase = path10.resolve(baseCwd);
9826
- const relativePath = path10.relative(resolvedBase, resolvedTarget);
9827
- return !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
10088
+ const resolvedTarget = path11.resolve(targetPath);
10089
+ const resolvedBase = path11.resolve(baseCwd);
10090
+ const relativePath = path11.relative(resolvedBase, resolvedTarget);
10091
+ return !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
9828
10092
  }
9829
10093
  function convertGlobToRipgrepGlobs(globPattern) {
9830
10094
  if (!globPattern || globPattern === "**/*") {
@@ -9840,7 +10104,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
9840
10104
  async function searchFiles2(params) {
9841
10105
  const { pattern, dir_path, include } = params;
9842
10106
  const baseCwd = process.cwd();
9843
- const targetDir = dir_path ? path10.resolve(baseCwd, dir_path) : baseCwd;
10107
+ const targetDir = dir_path ? path11.resolve(baseCwd, dir_path) : baseCwd;
9844
10108
  if (!isPathWithinWorkspace(targetDir, baseCwd)) {
9845
10109
  throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
9846
10110
  }
@@ -9902,7 +10166,7 @@ async function searchFiles2(params) {
9902
10166
  if (item.type === "match") {
9903
10167
  const match = item;
9904
10168
  allMatches.push({
9905
- filePath: path10.relative(targetDir, match.data.path.text) || path10.basename(match.data.path.text),
10169
+ filePath: path11.relative(targetDir, match.data.path.text) || path11.basename(match.data.path.text),
9906
10170
  lineNumber: match.data.line_number,
9907
10171
  line: match.data.lines.text.trimEnd()
9908
10172
  // Remove trailing newline
@@ -9995,18 +10259,18 @@ var grepSearchTool = {
9995
10259
  };
9996
10260
 
9997
10261
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
9998
- import { promises as fs9 } from "fs";
9999
- import { existsSync as existsSync6, statSync as statSync5 } from "fs";
10000
- import path11 from "path";
10262
+ import { promises as fs10 } from "fs";
10263
+ import { existsSync as existsSync8, statSync as statSync5 } from "fs";
10264
+ import path12 from "path";
10001
10265
  async function deleteFile(params) {
10002
10266
  const { path: filePath, recursive = false } = params;
10003
- const normalizedPath = path11.normalize(filePath);
10004
- const resolvedPath = path11.resolve(process.cwd(), normalizedPath);
10005
- const cwd = path11.resolve(process.cwd());
10267
+ const normalizedPath = path12.normalize(filePath);
10268
+ const resolvedPath = path12.resolve(process.cwd(), normalizedPath);
10269
+ const cwd = path12.resolve(process.cwd());
10006
10270
  if (!resolvedPath.startsWith(cwd)) {
10007
10271
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
10008
10272
  }
10009
- if (!existsSync6(resolvedPath)) {
10273
+ if (!existsSync8(resolvedPath)) {
10010
10274
  throw new Error(`File or directory not found: ${filePath}`);
10011
10275
  }
10012
10276
  const stats = statSync5(resolvedPath);
@@ -10016,10 +10280,10 @@ async function deleteFile(params) {
10016
10280
  throw new Error(`Path is a directory: ${filePath}. Use recursive=true to delete directories and their contents.`);
10017
10281
  }
10018
10282
  if (isDirectory) {
10019
- await fs9.rm(resolvedPath, { recursive: true, force: true });
10283
+ await fs10.rm(resolvedPath, { recursive: true, force: true });
10020
10284
  return `Directory deleted successfully: ${filePath}`;
10021
10285
  } else {
10022
- await fs9.unlink(resolvedPath);
10286
+ await fs10.unlink(resolvedPath);
10023
10287
  return `File deleted successfully: ${filePath}
10024
10288
  Size: ${size} bytes`;
10025
10289
  }
@@ -10029,8 +10293,8 @@ var deleteFileTool = {
10029
10293
  implementation: (context) => ({
10030
10294
  toolFn: async (value) => {
10031
10295
  const params = value;
10032
- const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
10033
- const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
10296
+ const resolvedPath = path12.resolve(process.cwd(), path12.normalize(params.path));
10297
+ const isDirectory = existsSync8(resolvedPath) && statSync5(resolvedPath).isDirectory();
10034
10298
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
10035
10299
  path: params.path,
10036
10300
  recursive: params.recursive
@@ -10286,7 +10550,8 @@ var SchedulingProblemSchema = z139.object({
10286
10550
  name: z139.string(),
10287
10551
  description: z139.string().optional(),
10288
10552
  jobs: z139.array(JobSchema),
10289
- machines: z139.array(MachineSchema)
10553
+ machines: z139.array(MachineSchema),
10554
+ solvedJobUrl: z139.string().url().optional()
10290
10555
  });
10291
10556
  var SolverIdSchema = z139.enum([
10292
10557
  "greedy",
@@ -10951,44 +11216,389 @@ function constructSolution(allOps, pheromone, heuristic, rng) {
10951
11216
  return result;
10952
11217
  }
10953
11218
 
10954
- // ../../b4m-core/packages/quantum/dist/src/solvers/metadata.js
10955
- var solverMetadata = {
10956
- naive: {
10957
- icon: "\u{1F40C}",
10958
- // 🐌
10959
- color: "neutral",
10960
- complexity: "Exponential - O(n!)",
10961
- requires: "Browser compute",
10962
- tagline: "Try every possible solution",
10963
- fullDescription: "The simplest possible approach: systematically enumerate and evaluate every valid permutation of operations. Guaranteed to find the optimal solution, but becomes computationally infeasible for larger problems due to factorial growth.",
10964
- howItWorks: [
10965
- "Generate all valid operation orderings (permutations)",
10966
- "Evaluate each ordering to compute makespan",
10967
- "Track the best solution found",
10968
- "Return the globally optimal solution"
10969
- ],
10970
- keyParameters: [{ name: "Search Space", value: "n! permutations for n operations (with pruning for precedence)" }],
10971
- strengths: ["Guarantees optimal solution", "Simple to understand and implement", "No parameter tuning needed"],
10972
- weaknesses: [
10973
- "O(n!) time complexity",
10974
- "Infeasible for problems with >10 operations",
10975
- "No early termination heuristics"
10976
- ],
10977
- bestFor: "Tiny problems (\u226410 ops) where you need a guaranteed optimal baseline",
10978
- wikipedia: "https://en.wikipedia.org/wiki/Brute-force_search"
10979
- },
10980
- greedy: {
10981
- icon: "\u26A1",
10982
- // ⚡
10983
- color: "success",
10984
- complexity: "O(n\xB2)",
10985
- requires: "Browser compute",
10986
- tagline: "Always pick the locally best choice",
10987
- fullDescription: 'A fast heuristic that builds a solution by always selecting the "best" available operation at each step. Uses the Shortest Processing Time (SPT) rule: schedule the shortest available operation first. Fast but may miss global optimum.',
10988
- howItWorks: [
10989
- "Start with an empty schedule",
10990
- "Find all operations whose predecessors are complete",
10991
- "Pick the one with shortest duration (SPT rule)",
11219
+ // ../../b4m-core/packages/quantum/dist/src/solvers/highs-formulation.js
11220
+ function analyzeProblemComplexity(problem) {
11221
+ const numJobs = problem.jobs.length;
11222
+ const numMachines = problem.machines.length;
11223
+ const opsPerJob = problem.jobs[0]?.operations.length || 0;
11224
+ const totalOps = problem.jobs.reduce((sum2, j) => sum2 + j.operations.length, 0);
11225
+ const machineOpCounts = /* @__PURE__ */ new Map();
11226
+ for (const job of problem.jobs) {
11227
+ for (const op of job.operations) {
11228
+ machineOpCounts.set(op.machineId, (machineOpCounts.get(op.machineId) || 0) + 1);
11229
+ }
11230
+ }
11231
+ const opCounts = Array.from(machineOpCounts.values());
11232
+ const minOps = opCounts.length > 0 ? Math.min(...opCounts) : 0;
11233
+ const maxOps = opCounts.length > 0 ? Math.max(...opCounts) : 0;
11234
+ const avgOps = opCounts.length > 0 ? opCounts.reduce((a, b) => a + b, 0) / opCounts.length : 0;
11235
+ let binaryVars = 0;
11236
+ for (const n of opCounts) {
11237
+ binaryVars += n * (n - 1) / 2;
11238
+ }
11239
+ const startTimeVars = totalOps;
11240
+ const totalVars = startTimeVars + binaryVars + 1;
11241
+ const jobPrecedenceConstraints = problem.jobs.reduce((sum2, j) => sum2 + Math.max(0, j.operations.length - 1), 0);
11242
+ const machineDisjunctionConstraints = 2 * binaryVars;
11243
+ const makespanConstraints = totalOps;
11244
+ const totalConstraints = jobPrecedenceConstraints + machineDisjunctionConstraints + makespanConstraints;
11245
+ return {
11246
+ numJobs,
11247
+ numMachines,
11248
+ opsPerJob,
11249
+ totalOps,
11250
+ startTimeVars,
11251
+ binaryVars,
11252
+ totalVars,
11253
+ jobPrecedenceConstraints,
11254
+ machineDisjunctionConstraints,
11255
+ makespanConstraints,
11256
+ totalConstraints,
11257
+ opsPerMachine: { min: minOps, max: maxOps, avg: Math.round(avgOps * 10) / 10 }
11258
+ };
11259
+ }
11260
+ function estimateViability(c) {
11261
+ if (c.opsPerMachine.max > 10) {
11262
+ return {
11263
+ viable: false,
11264
+ risk: "high",
11265
+ recommendation: `Too many ops on one machine (${c.opsPerMachine.max}). Add more machines to spread the load, or use Simulated Annealing.`
11266
+ };
11267
+ }
11268
+ if (c.binaryVars <= 80 && c.totalOps <= 40 && c.opsPerMachine.max <= 6) {
11269
+ return {
11270
+ viable: true,
11271
+ risk: "low",
11272
+ recommendation: "Should solve quickly (< 1 second)"
11273
+ };
11274
+ }
11275
+ if (c.binaryVars <= 150 && c.totalOps <= 80 && c.opsPerMachine.max <= 8) {
11276
+ return {
11277
+ viable: true,
11278
+ risk: "medium",
11279
+ recommendation: "Should solve in 1-30 seconds. WASM may have occasional instability."
11280
+ };
11281
+ }
11282
+ if (c.binaryVars > 150) {
11283
+ return {
11284
+ viable: false,
11285
+ risk: "high",
11286
+ recommendation: `Too many binary vars (${c.binaryVars} > 150). Use Simulated Annealing or Tabu Search for this size.`
11287
+ };
11288
+ }
11289
+ if (c.totalOps > 80) {
11290
+ return {
11291
+ viable: false,
11292
+ risk: "high",
11293
+ recommendation: `Too many operations (${c.totalOps} > 80). Use Simulated Annealing or Tabu Search for this size.`
11294
+ };
11295
+ }
11296
+ return {
11297
+ viable: false,
11298
+ risk: "high",
11299
+ recommendation: "Exceeds tested parameters. Consider using Simulated Annealing or Tabu Search."
11300
+ };
11301
+ }
11302
+ function generateLPFormulation(problem) {
11303
+ const ops = [];
11304
+ for (const job of problem.jobs) {
11305
+ for (let opIdx = 0; opIdx < job.operations.length; opIdx++) {
11306
+ const op = job.operations[opIdx];
11307
+ const varName = `sJ${job.id}O${opIdx + 1}`;
11308
+ ops.push({
11309
+ jobId: job.id,
11310
+ machineId: op.machineId,
11311
+ opIndex: opIdx,
11312
+ duration: op.duration,
11313
+ varName
11314
+ });
11315
+ }
11316
+ }
11317
+ const bigM = ops.reduce((sum2, op) => sum2 + op.duration, 0);
11318
+ const machineOps = /* @__PURE__ */ new Map();
11319
+ for (const op of ops) {
11320
+ const machOps = machineOps.get(op.machineId) || [];
11321
+ machOps.push(op);
11322
+ machineOps.set(op.machineId, machOps);
11323
+ }
11324
+ const lines = [];
11325
+ lines.push("Minimize obj: Cmax");
11326
+ lines.push("");
11327
+ lines.push("Subject To");
11328
+ for (const job of problem.jobs) {
11329
+ for (let i = 0; i < job.operations.length - 1; i++) {
11330
+ const op1VarName = `sJ${job.id}O${i + 1}`;
11331
+ const op2VarName = `sJ${job.id}O${i + 2}`;
11332
+ const duration = job.operations[i].duration;
11333
+ const constraintName = `job${op1VarName}to${op2VarName}`;
11334
+ lines.push(` ${constraintName}: ${op1VarName} - ${op2VarName} <= ${-duration}`);
11335
+ }
11336
+ }
11337
+ const binaryVars = [];
11338
+ for (const [, machOps] of machineOps) {
11339
+ if (machOps.length < 2)
11340
+ continue;
11341
+ for (let i = 0; i < machOps.length; i++) {
11342
+ for (let j = i + 1; j < machOps.length; j++) {
11343
+ const op1 = machOps[i];
11344
+ const op2 = machOps[j];
11345
+ const yVar = `y${op1.varName}x${op2.varName}`;
11346
+ binaryVars.push(yVar);
11347
+ const c1Name = `d${op1.varName}b${op2.varName}`;
11348
+ lines.push(` ${c1Name}: ${op1.varName} - ${op2.varName} + ${bigM} ${yVar} <= ${bigM - op1.duration}`);
11349
+ const c2Name = `d${op2.varName}b${op1.varName}`;
11350
+ lines.push(` ${c2Name}: ${op2.varName} - ${op1.varName} - ${bigM} ${yVar} <= ${-op2.duration}`);
11351
+ }
11352
+ }
11353
+ }
11354
+ for (const op of ops) {
11355
+ const constraintName = `mk${op.varName}`;
11356
+ lines.push(` ${constraintName}: ${op.varName} - Cmax <= ${-op.duration}`);
11357
+ }
11358
+ lines.push("");
11359
+ lines.push("Bounds");
11360
+ for (const op of ops) {
11361
+ lines.push(` ${op.varName} >= 0`);
11362
+ }
11363
+ lines.push(" Cmax >= 0");
11364
+ lines.push("");
11365
+ if (binaryVars.length > 0) {
11366
+ lines.push("Binary");
11367
+ for (const v of binaryVars) {
11368
+ lines.push(` ${v}`);
11369
+ }
11370
+ lines.push("");
11371
+ }
11372
+ lines.push("End");
11373
+ return lines.join("\n");
11374
+ }
11375
+ function parseSolution(problem, columns) {
11376
+ const schedule = [];
11377
+ const varToOp = /* @__PURE__ */ new Map();
11378
+ for (const job of problem.jobs) {
11379
+ for (let opIdx = 0; opIdx < job.operations.length; opIdx++) {
11380
+ const op = job.operations[opIdx];
11381
+ const varName = `sJ${job.id}O${opIdx + 1}`;
11382
+ varToOp.set(varName, {
11383
+ jobId: job.id,
11384
+ machineId: op.machineId,
11385
+ opIndex: opIdx,
11386
+ duration: op.duration
11387
+ });
11388
+ }
11389
+ }
11390
+ for (const [varName, col] of Object.entries(columns)) {
11391
+ if (varName.startsWith("sJ")) {
11392
+ const opInfo = varToOp.get(varName);
11393
+ if (opInfo) {
11394
+ const startTime = Math.round(col.Primal);
11395
+ schedule.push({
11396
+ jobId: opInfo.jobId,
11397
+ machineId: opInfo.machineId,
11398
+ operationIndex: opInfo.opIndex,
11399
+ startTime,
11400
+ endTime: startTime + opInfo.duration
11401
+ });
11402
+ }
11403
+ }
11404
+ }
11405
+ schedule.sort((a, b) => a.startTime - b.startTime);
11406
+ return schedule;
11407
+ }
11408
+ function validateSchedule(problem, schedule) {
11409
+ const errors = [];
11410
+ const scheduledKeys = new Set(schedule.map((s) => `${s.jobId}-${s.operationIndex}`));
11411
+ for (const job of problem.jobs) {
11412
+ for (let opIdx = 0; opIdx < job.operations.length; opIdx++) {
11413
+ if (!scheduledKeys.has(`${job.id}-${opIdx}`)) {
11414
+ errors.push(`Operation (job=${job.id}, opIndex=${opIdx}) not scheduled`);
11415
+ }
11416
+ }
11417
+ }
11418
+ const byMachine = /* @__PURE__ */ new Map();
11419
+ for (const s of schedule) {
11420
+ const mOps = byMachine.get(s.machineId) || [];
11421
+ mOps.push(s);
11422
+ byMachine.set(s.machineId, mOps);
11423
+ }
11424
+ for (const [machineId, mOps] of byMachine) {
11425
+ mOps.sort((a, b) => a.startTime - b.startTime);
11426
+ for (let i = 0; i < mOps.length - 1; i++) {
11427
+ if (mOps[i].endTime > mOps[i + 1].startTime) {
11428
+ errors.push(`Machine ${machineId}: (job=${mOps[i].jobId},op=${mOps[i].operationIndex}) ends ${mOps[i].endTime} overlaps (job=${mOps[i + 1].jobId},op=${mOps[i + 1].operationIndex}) starts ${mOps[i + 1].startTime}`);
11429
+ }
11430
+ }
11431
+ }
11432
+ const opEndTime = /* @__PURE__ */ new Map();
11433
+ for (const s of schedule) {
11434
+ opEndTime.set(`${s.jobId}-${s.operationIndex}`, s.endTime);
11435
+ }
11436
+ for (const job of problem.jobs) {
11437
+ for (let i = 0; i < job.operations.length - 1; i++) {
11438
+ const op1End = opEndTime.get(`${job.id}-${i}`) || 0;
11439
+ const op2 = schedule.find((s) => s.jobId === job.id && s.operationIndex === i + 1);
11440
+ if (op2 && op1End > op2.startTime) {
11441
+ errors.push(`Job ${job.id}: op ${i} ends at ${op1End} but op ${i + 1} starts at ${op2.startTime}`);
11442
+ }
11443
+ }
11444
+ }
11445
+ return { valid: errors.length === 0, errors };
11446
+ }
11447
+
11448
+ // ../../b4m-core/packages/quantum/dist/src/solvers/highs-solver.js
11449
+ var MAX_BINARY_VARS = 150;
11450
+ var MAX_TOTAL_OPS = 80;
11451
+ var MAX_OPS_PER_MACHINE = 8;
11452
+ async function loadHiGHS() {
11453
+ const highsLoader = (await import("highs")).default;
11454
+ const highs = await highsLoader({
11455
+ locateFile: (file) => {
11456
+ const globalSelf = globalThis;
11457
+ if (globalSelf.location?.origin) {
11458
+ return `${globalSelf.location.origin}/${file}`;
11459
+ }
11460
+ return file;
11461
+ }
11462
+ });
11463
+ return highs;
11464
+ }
11465
+ function elapsed(startTime) {
11466
+ return typeof performance !== "undefined" ? performance.now() - startTime : Date.now() - startTime;
11467
+ }
11468
+ var highsSolver = {
11469
+ id: "highs",
11470
+ name: "HiGHS (WASM)",
11471
+ description: "Open-source MIP solver providing provably optimal solutions for small-to-medium scheduling problems via WASM.",
11472
+ async solve(problem, options) {
11473
+ const startTime = typeof performance !== "undefined" ? performance.now() : Date.now();
11474
+ const reportProgress = (percentage, bestMakespan) => {
11475
+ options?.onProgress?.({
11476
+ solverId: "highs",
11477
+ percentage,
11478
+ bestMakespan
11479
+ });
11480
+ };
11481
+ reportProgress(0);
11482
+ try {
11483
+ reportProgress(5);
11484
+ const highs = await loadHiGHS();
11485
+ reportProgress(10);
11486
+ const complexity = analyzeProblemComplexity(problem);
11487
+ const viability = estimateViability(complexity);
11488
+ if (complexity.binaryVars > MAX_BINARY_VARS) {
11489
+ throw new Error(`Problem has too many binary variables for HiGHS WASM: ${complexity.binaryVars} (max ${MAX_BINARY_VARS}). Tip: More machines spreads ops and reduces binary vars. Current: ${complexity.numMachines} machines. For this size, use Simulated Annealing or Tabu Search.`);
11490
+ }
11491
+ if (complexity.totalOps > MAX_TOTAL_OPS) {
11492
+ throw new Error(`Problem too large for HiGHS WASM: ${complexity.totalOps} operations (max ${MAX_TOTAL_OPS}). For larger problems, use Simulated Annealing or Tabu Search.`);
11493
+ }
11494
+ if (complexity.opsPerMachine.max > MAX_OPS_PER_MACHINE) {
11495
+ throw new Error(`Too many operations on one machine: ${complexity.opsPerMachine.max} ops (max ${MAX_OPS_PER_MACHINE}). Tip: Add more machines to spread the load. For this size, use Simulated Annealing or Tabu Search.`);
11496
+ }
11497
+ if (viability.risk === "high") {
11498
+ console.warn(`[HiGHS] Warning: ${viability.recommendation}`);
11499
+ }
11500
+ reportProgress(15);
11501
+ const lpString = generateLPFormulation(problem);
11502
+ if (!lpString.includes("Subject To") || !lpString.includes("Bounds")) {
11503
+ throw new Error("Invalid LP formulation - missing required sections");
11504
+ }
11505
+ reportProgress(20);
11506
+ let solution;
11507
+ try {
11508
+ solution = highs.solve(lpString, {
11509
+ time_limit: options?.timeoutMs ? options.timeoutMs / 1e3 : 60,
11510
+ mip_rel_gap: 0,
11511
+ presolve: "on",
11512
+ log_to_console: true,
11513
+ // MUST BE TRUE - see file header
11514
+ output_flag: true
11515
+ // MUST BE TRUE - see file header
11516
+ });
11517
+ } catch (solveError) {
11518
+ console.error("[HiGHS] Solve error:", solveError);
11519
+ console.error("[HiGHS] Problem config:", {
11520
+ jobs: problem.jobs.length,
11521
+ machines: problem.machines.length,
11522
+ totalOps: problem.jobs.reduce((sum2, j) => sum2 + j.operations.length, 0)
11523
+ });
11524
+ const errMsg = solveError instanceof Error ? solveError.message : String(solveError);
11525
+ if (errMsg.includes("Aborted")) {
11526
+ throw new Error(`HiGHS WASM crashed. This may be a numerical issue with the problem configuration. Jobs: ${problem.jobs.length}, Machines: ${problem.machines.length}. Check browser console for details.`);
11527
+ }
11528
+ throw solveError;
11529
+ }
11530
+ if (solution.Status === "Infeasible") {
11531
+ throw new Error("Problem is infeasible - check constraints");
11532
+ }
11533
+ if (solution.Status === "Unbounded") {
11534
+ throw new Error("Problem is unbounded - check objective");
11535
+ }
11536
+ if (solution.Status !== "Optimal" && solution.Status !== "Time limit reached" && solution.Status !== "Bound on objective reached") {
11537
+ throw new Error(`HiGHS returned unexpected status: ${solution.Status}`);
11538
+ }
11539
+ reportProgress(90);
11540
+ const schedule = parseSolution(problem, solution.Columns);
11541
+ const makespan = Math.ceil(solution.ObjectiveValue);
11542
+ const validation = validateSchedule(problem, schedule);
11543
+ if (!validation.valid) {
11544
+ console.warn("[HiGHS] Solution validation failed:", validation.errors);
11545
+ const errorDetails = validation.errors && validation.errors.length > 0 ? validation.errors.join("; ") : "Unknown validation error";
11546
+ throw new Error(`[HiGHS] Solution validation failed: ${errorDetails}`);
11547
+ }
11548
+ reportProgress(100, makespan);
11549
+ return {
11550
+ solverId: "highs",
11551
+ solverName: "HiGHS (WASM)",
11552
+ makespan,
11553
+ schedule,
11554
+ elapsedMs: elapsed(startTime),
11555
+ iterations: 1
11556
+ };
11557
+ } catch (error) {
11558
+ reportProgress(0);
11559
+ throw error;
11560
+ }
11561
+ }
11562
+ };
11563
+
11564
+ // ../../b4m-core/packages/quantum/dist/src/solvers/metadata.js
11565
+ var solverMetadata = {
11566
+ naive: {
11567
+ icon: "\u{1F40C}",
11568
+ // 🐌
11569
+ color: "neutral",
11570
+ complexity: "Exponential - O(n!)",
11571
+ requires: "Browser compute",
11572
+ tagline: "Try every possible solution",
11573
+ fullDescription: "The simplest possible approach: systematically enumerate and evaluate every valid permutation of operations. Guaranteed to find the optimal solution, but becomes computationally infeasible for larger problems due to factorial growth.",
11574
+ howItWorks: [
11575
+ "Generate all valid operation orderings (permutations)",
11576
+ "Evaluate each ordering to compute makespan",
11577
+ "Track the best solution found",
11578
+ "Return the globally optimal solution"
11579
+ ],
11580
+ keyParameters: [{ name: "Search Space", value: "n! permutations for n operations (with pruning for precedence)" }],
11581
+ strengths: ["Guarantees optimal solution", "Simple to understand and implement", "No parameter tuning needed"],
11582
+ weaknesses: [
11583
+ "O(n!) time complexity",
11584
+ "Infeasible for problems with >10 operations",
11585
+ "No early termination heuristics"
11586
+ ],
11587
+ bestFor: "Tiny problems (\u226410 ops) where you need a guaranteed optimal baseline",
11588
+ wikipedia: "https://en.wikipedia.org/wiki/Brute-force_search"
11589
+ },
11590
+ greedy: {
11591
+ icon: "\u26A1",
11592
+ // ⚡
11593
+ color: "success",
11594
+ complexity: "O(n\xB2)",
11595
+ requires: "Browser compute",
11596
+ tagline: "Always pick the locally best choice",
11597
+ fullDescription: 'A fast heuristic that builds a solution by always selecting the "best" available operation at each step. Uses the Shortest Processing Time (SPT) rule: schedule the shortest available operation first. Fast but may miss global optimum.',
11598
+ howItWorks: [
11599
+ "Start with an empty schedule",
11600
+ "Find all operations whose predecessors are complete",
11601
+ "Pick the one with shortest duration (SPT rule)",
10992
11602
  "Schedule it at the earliest possible time",
10993
11603
  "Repeat until all operations scheduled"
10994
11604
  ],
@@ -11219,7 +11829,6 @@ var solverMetadata = {
11219
11829
  color: "success",
11220
11830
  complexity: "Seconds to minutes",
11221
11831
  requires: "Browser compute (WASM)",
11222
- available: false,
11223
11832
  tagline: "Open-source solver rivaling commercial giants",
11224
11833
  fullDescription: "HiGHS (High-performance Interior-point, Gradient-descent, Simplex) is an MIT-licensed open-source solver that achieves 90%+ of commercial solver performance. Developed at the University of Edinburgh, it has rapidly become the leading open-source alternative to Gurobi and COPT for linear and mixed-integer programming.",
11225
11834
  howItWorks: [
@@ -11262,6 +11871,7 @@ var solverMetadata = {
11262
11871
  requires: "Backend compute",
11263
11872
  available: false,
11264
11873
  tagline: "Quantum-inspired classical simulation",
11874
+ externalUrl: "https://q.bike4mind.com/jobs/e499f34a-d40e-4cfb-98e5-47e0a2091f3d",
11265
11875
  fullDescription: "A classical simulator of the Quantum Approximate Optimization Algorithm (QAOA). Encodes the job-shop scheduling problem as a QUBO (Quadratic Unconstrained Binary Optimization), then simulates the quantum variational circuit classically. Provides a preview of quantum advantage without requiring real quantum hardware.",
11266
11876
  howItWorks: [
11267
11877
  "Encode scheduling problem as QUBO matrix",
@@ -11293,7 +11903,10 @@ var solverMetadata = {
11293
11903
  wikipedia: "https://en.wikipedia.org/wiki/Quantum_approximate_optimization_algorithm",
11294
11904
  otherResources: [
11295
11905
  { label: "Farhi et al. (2014) \u2014 Original QAOA Paper", url: "https://arxiv.org/abs/1411.4028" },
11296
- { label: "QUBO Formulation Guide", url: "https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization" }
11906
+ {
11907
+ label: "QUBO Formulation Guide",
11908
+ url: "https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization"
11909
+ }
11297
11910
  ]
11298
11911
  },
11299
11912
  "ionq-qaoa": {
@@ -11304,6 +11917,7 @@ var solverMetadata = {
11304
11917
  requires: "IonQ API key + credits",
11305
11918
  available: false,
11306
11919
  tagline: "Real quantum hardware optimization",
11920
+ externalUrl: "https://cloud.ionq.com/jobs/019c07ff-205a-76d0-9f83-24098ea09589",
11307
11921
  fullDescription: "Runs the Quantum Approximate Optimization Algorithm on IonQ\u2019s trapped-ion quantum computers. This is real quantum computation \u2014 the problem is encoded as a QUBO, compiled to native quantum gates, and executed on physical qubits. Results include genuine quantum effects like superposition and entanglement.",
11308
11922
  howItWorks: [
11309
11923
  "Encode scheduling problem as QUBO matrix",
@@ -11351,7 +11965,8 @@ var allSolvers = [
11351
11965
  simulatedAnnealingLargeSolver,
11352
11966
  tabuSolver,
11353
11967
  geneticAlgorithmSolver,
11354
- antColonySolver
11968
+ antColonySolver,
11969
+ highsSolver
11355
11970
  ];
11356
11971
  var solverRegistry = new Map(allSolvers.map((s) => [s.id, s]));
11357
11972
  function getSolver(id) {
@@ -11362,7 +11977,6 @@ function getAvailableSolverIds() {
11362
11977
  }
11363
11978
  var displaySolvers = [
11364
11979
  ...allSolvers.map((s) => ({ id: s.id, name: s.name, description: s.description })),
11365
- { id: "highs", name: "HiGHS (WASM)", description: solverMetadata.highs.tagline },
11366
11980
  { id: "simulated-qaoa", name: "Simulated QAOA", description: solverMetadata["simulated-qaoa"].tagline },
11367
11981
  { id: "ionq-qaoa", name: "IonQ QAOA", description: solverMetadata["ionq-qaoa"].tagline }
11368
11982
  ];
@@ -11387,11 +12001,17 @@ ${allSolvers.map((s) => `- **${s.name}** (\`${s.id}\`): ${s.description}`).join(
11387
12001
 
11388
12002
  ## How to Help Users
11389
12003
 
11390
- ### Problem Formulation
11391
- When a user describes a scheduling scenario:
11392
- 1. Use \`quantum_formulate\` to convert their description into a structured problem
11393
- 2. Present the formulated problem clearly, showing jobs, machines, and operation sequences
11394
- 3. Ask if adjustments are needed before solving
12004
+ ### Concierge Flow (Default)
12005
+ When a user describes a scheduling problem:
12006
+ 1. Use \`quantum_formulate\` to convert their description \u2014 the problem auto-loads into the Problem Editor
12007
+ 2. Call \`navigate_view\` with viewId "opti.scheduling.problem" so the user gets a button to open the Problem tab
12008
+ 3. Let the user review, tweak machines/durations, and run solvers from the dashboard
12009
+ Do NOT auto-call \`quantum_schedule\` unless the user explicitly asks to solve in chat.
12010
+
12011
+ ### Power User Fast Path
12012
+ If the user says "solve it", "find the optimal schedule", or explicitly requests an in-chat solution,
12013
+ use both \`quantum_formulate\` AND \`quantum_schedule\`. The problem still loads into the Editor
12014
+ so the user can explore results in the dashboard too.
11395
12015
 
11396
12016
  ### Running Solvers
11397
12017
  When solving a problem:
@@ -11456,6 +12076,581 @@ RESPOND WITH ONLY A JSON OBJECT matching this schema:
11456
12076
 
11457
12077
  No markdown, no explanation, no code blocks \u2014 just the raw JSON object.`;
11458
12078
 
12079
+ // ../../b4m-core/packages/quantum/dist/src/patterns.js
12080
+ var patternFamilies = [
12081
+ {
12082
+ id: "scheduling",
12083
+ name: "Scheduling",
12084
+ description: "Job shop, flow shop, resource allocation over time",
12085
+ complexity: "NP-hard",
12086
+ color: "#e53935",
12087
+ quboFit: "excellent",
12088
+ examples: ["Job Shop Scheduling", "Task Scheduling", "Production Planning"],
12089
+ status: "available",
12090
+ tagline: "When to do things, in what order, on what resources",
12091
+ asciiDiagram: [
12092
+ " 0 2 4 6 8 10 12",
12093
+ " \u251C\u2500\u2500\u253C\u2500\u2500\u253C\u2500\u2500\u253C\u2500\u2500\u253C\u2500\u2500\u253C\u2500\u2500\u2524",
12094
+ "A [\u25A0\u25A0\u25A0\u25A0\u25A0][ \u2591\u2591\u2591\u2591\u2591\u2591 ]",
12095
+ "B [\u2591\u2591\u2591\u2591\u2591][ \u25A0\u25A0\u25A0\u25A0\u25A0 ]",
12096
+ "C [\u25A0\u25A0\u25A0] [\u2591\u2591\u2591\u2591\u2591\u2591]",
12097
+ " \u2500\u2500\u2500\u2500 time \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA"
12098
+ ].join("\n"),
12099
+ subPatterns: [
12100
+ {
12101
+ name: "Job Shop Scheduling",
12102
+ subtitle: "The Classic",
12103
+ description: "Assign jobs to machines with precedence constraints. Each job has a sequence of operations that must be processed on specific machines in order. The goal is to minimize the total makespan.",
12104
+ asciiDiagram: [
12105
+ "J1: [M-A 3t]->[M-B 2t]->[M-C 4t]",
12106
+ "J2: [M-B 2t]->[M-C 3t]->[M-A 2t]",
12107
+ "J3: [M-C 4t]->[M-A 1t]->[M-B 3t]",
12108
+ "",
12109
+ "Objective: Minimize total completion time"
12110
+ ].join("\n"),
12111
+ quboFitStars: 5
12112
+ },
12113
+ {
12114
+ name: "Flow Shop Scheduling",
12115
+ subtitle: "Fixed Sequence",
12116
+ description: "All jobs follow the same machine order. Simpler than job shop but still NP-hard for 3+ machines. Common in assembly lines and manufacturing pipelines.",
12117
+ asciiDiagram: [
12118
+ "Job 1: -->[M1]-->[M2]-->[M3]--> Done",
12119
+ "Job 2: -->[M1]-->[M2]-->[M3]--> Done",
12120
+ "Job 3: -->[M1]-->[M2]-->[M3]--> Done",
12121
+ " Fixed order through all machines"
12122
+ ].join("\n"),
12123
+ quboFitStars: 5
12124
+ },
12125
+ {
12126
+ name: "Timetabling",
12127
+ subtitle: "Conflict Avoidance",
12128
+ description: "Schedule events into time slots while avoiding conflicts. No professor teaches two classes at once, no student has overlapping classes, room capacities are respected.",
12129
+ asciiDiagram: [
12130
+ " Mon Tue Wed Thu",
12131
+ " 9:00 |CS101| |CS101| |",
12132
+ "10:00 | |MATH | |MATH |",
12133
+ "11:00 |PHYS | |PHYS | |",
12134
+ " No conflicts for shared resources!"
12135
+ ].join("\n"),
12136
+ quboFitStars: 4
12137
+ },
12138
+ {
12139
+ name: "Sequencing with Changeover",
12140
+ subtitle: "Order Matters",
12141
+ description: "Transition costs between tasks depend on the sequence. Paint lines minimize cleaning by ordering light-to-dark. Semiconductor fabs batch similar wafers together.",
12142
+ asciiDiagram: [
12143
+ "White->Yellow->Orange $0 each",
12144
+ "Orange->Red->Black $0 each",
12145
+ "White->Black directly $50 clean!",
12146
+ "",
12147
+ "Objective: Minimize total changeover cost"
12148
+ ].join("\n"),
12149
+ quboFitStars: 4
12150
+ },
12151
+ {
12152
+ name: "Dynamic Scheduling",
12153
+ subtitle: "Continuous Arrival",
12154
+ description: "New jobs arrive during execution, requiring real-time rescheduling. Common in hospitals, data centers, and cloud computing where demand is unpredictable.",
12155
+ asciiDiagram: [
12156
+ "Time --------------------------->",
12157
+ "J1 ========",
12158
+ " J2 ==========",
12159
+ " J3 ======= (arrived t=3)",
12160
+ " J4 === (arrived t=5)",
12161
+ " Must reschedule as jobs arrive!"
12162
+ ].join("\n"),
12163
+ quboFitStars: 3
12164
+ }
12165
+ ],
12166
+ quboExplainer: "Each binary variable x(j,m,t) = 1 if job j starts on machine m at time t. Penalty terms enforce: (1) each operation scheduled exactly once, (2) precedence constraints respected, (3) no machine processes two jobs simultaneously. Qubit count scales as O(jobs x machines x time_horizon).",
12167
+ useCases: [
12168
+ { industry: "Manufacturing", application: "Factory floor scheduling" },
12169
+ { industry: "Healthcare", application: "Operating room scheduling" },
12170
+ { industry: "Airlines", application: "Crew and gate scheduling" },
12171
+ { industry: "Data Centers", application: "Batch job scheduling" },
12172
+ { industry: "Construction", application: "Project task sequencing" },
12173
+ { industry: "Restaurants", application: "Kitchen order optimization" }
12174
+ ]
12175
+ },
12176
+ {
12177
+ id: "routing",
12178
+ name: "Routing",
12179
+ description: "Vehicle routing, TSP, network path optimization",
12180
+ complexity: "NP-hard",
12181
+ color: "#66bb6a",
12182
+ quboFit: "excellent",
12183
+ examples: ["Traveling Salesman", "Vehicle Routing", "Delivery Optimization"],
12184
+ status: "coming-soon",
12185
+ tagline: "How to move through space efficiently",
12186
+ asciiDiagram: [
12187
+ " A",
12188
+ " /|\\",
12189
+ " 5/ | \\3",
12190
+ " / | \\",
12191
+ " B---+---C",
12192
+ " \\ | /",
12193
+ " 7\\ | /2",
12194
+ " \\|/",
12195
+ " D"
12196
+ ].join("\n"),
12197
+ subPatterns: [
12198
+ {
12199
+ name: "Traveling Salesman (TSP)",
12200
+ subtitle: "The Classic",
12201
+ description: "Visit all cities exactly once and return home via the shortest route. One of the most famous optimization problems \u2014 100+ cities makes exhaustive search impossible.",
12202
+ asciiDiagram: [
12203
+ "Start -> A -> B -> C -> D -> Start",
12204
+ "",
12205
+ "Find the shortest tour that visits",
12206
+ "every city exactly once!"
12207
+ ].join("\n"),
12208
+ quboFitStars: 5
12209
+ },
12210
+ {
12211
+ name: "Vehicle Routing (VRP)",
12212
+ subtitle: "Multiple Vehicles",
12213
+ description: "Partition customers among a fleet of vehicles, each with capacity limits and time windows. TSP generalized to multiple routes from a central depot.",
12214
+ asciiDiagram: [
12215
+ " Depot",
12216
+ " / | \\",
12217
+ " / | \\",
12218
+ " Route1 Route2 Route3",
12219
+ " A->B D->E G->H",
12220
+ " ->C ->F ->I"
12221
+ ].join("\n"),
12222
+ quboFitStars: 4
12223
+ },
12224
+ {
12225
+ name: "Shortest Path",
12226
+ subtitle: "Point-to-Point",
12227
+ description: "Find the minimum-cost path between two nodes in a weighted graph. Foundation for GPS navigation, network routing, and supply chain logistics.",
12228
+ asciiDiagram: [
12229
+ "S --3-- A --2-- T",
12230
+ "| | |",
12231
+ "5 1 4",
12232
+ "| | |",
12233
+ "B --6-- C --2-- D"
12234
+ ].join("\n"),
12235
+ quboFitStars: 3
12236
+ }
12237
+ ],
12238
+ quboExplainer: "Binary variable x(i,t) = 1 if city i is visited at step t. Constraints: each city visited exactly once, each step visits exactly one city. Objective minimizes total edge weights along the tour. Classic QUBO formulation.",
12239
+ useCases: [
12240
+ { industry: "Logistics", application: "Delivery route optimization" },
12241
+ { industry: "Ride-sharing", application: "Driver dispatch routing" },
12242
+ { industry: "Telecom", application: "Network packet routing" },
12243
+ { industry: "Retail", application: "Supply chain distribution" },
12244
+ { industry: "Field Service", application: "Technician visit scheduling" }
12245
+ ]
12246
+ },
12247
+ {
12248
+ id: "packing",
12249
+ name: "Packing",
12250
+ description: "Bin packing, knapsack, container optimization",
12251
+ complexity: "NP-complete",
12252
+ color: "#e91e63",
12253
+ quboFit: "good",
12254
+ examples: ["Bin Packing", "Knapsack Problem", "Container Loading"],
12255
+ status: "coming-soon",
12256
+ tagline: "How to fit things into space efficiently",
12257
+ asciiDiagram: [
12258
+ "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
12259
+ "\u2502 \u2588\u2588\u2588\u2588 \u2591\u2591\u2591\u2591 \u2588\u2588\u2588\u2588\u2588\u2588 \u2502 92%",
12260
+ "\u2502 \u2588\u2588\u2588\u2588 \u2591\u2591\u2591\u2591 \u2588\u2588\u2588\u2588\u2588\u2588 \u2502",
12261
+ "\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524",
12262
+ "\u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2502 68%",
12263
+ "\u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2502",
12264
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
12265
+ ].join("\n"),
12266
+ subPatterns: [
12267
+ {
12268
+ name: "Bin Packing",
12269
+ subtitle: "1D/2D/3D",
12270
+ description: "Fit items into the minimum number of bins. 1D (rod cutting, memory), 2D (sheet cutting, floor plans), 3D (container loading, truck packing). Items arrive online or in batch.",
12271
+ asciiDiagram: [
12272
+ "Items: [3] [5] [2] [4] [6]",
12273
+ "",
12274
+ "Bin 1: [3][5][2] = 10/10",
12275
+ "Bin 2: [4][6] = 10/10",
12276
+ "Minimum bins: 2"
12277
+ ].join("\n"),
12278
+ quboFitStars: 4
12279
+ },
12280
+ {
12281
+ name: "Knapsack",
12282
+ subtitle: "Value vs Weight",
12283
+ description: "Select items to maximize total value without exceeding weight capacity. The 0/1 knapsack is a direct binary problem \u2014 include or exclude each item.",
12284
+ asciiDiagram: [
12285
+ "Item Value Weight",
12286
+ " A 60 10",
12287
+ " B 100 20 <-- pick",
12288
+ " C 120 30 <-- pick",
12289
+ "Capacity: 50 Best: $220"
12290
+ ].join("\n"),
12291
+ quboFitStars: 5
12292
+ },
12293
+ {
12294
+ name: "Cutting Stock",
12295
+ subtitle: "Minimize Waste",
12296
+ description: "Cut raw material (steel rolls, fabric bolts, lumber) into required pieces with minimal waste. Dual of bin packing \u2014 subtract from fixed-size stock.",
12297
+ asciiDiagram: [
12298
+ "Stock roll: |============|",
12299
+ "Cut: |==A==|==B==|=C=|xx|",
12300
+ " waste",
12301
+ "Objective: Minimize total waste"
12302
+ ].join("\n"),
12303
+ quboFitStars: 4
12304
+ }
12305
+ ],
12306
+ quboExplainer: "Binary variable x(i,b) = 1 if item i is placed in bin b. Constraints: each item in exactly one bin, bin capacity not exceeded. For knapsack, x(i) = 1 if item i is selected, with weight constraint as penalty term.",
12307
+ useCases: [
12308
+ { industry: "Shipping", application: "Container loading optimization" },
12309
+ { industry: "Manufacturing", application: "Raw material cutting" },
12310
+ { industry: "Cloud Computing", application: "VM placement on servers" },
12311
+ { industry: "Retail", application: "Warehouse space allocation" },
12312
+ { industry: "Finance", application: "Budget allocation (knapsack)" }
12313
+ ]
12314
+ },
12315
+ {
12316
+ id: "assignment",
12317
+ name: "Assignment",
12318
+ description: "Matching agents to tasks, resource allocation",
12319
+ complexity: "NP-complete",
12320
+ color: "#42a5f5",
12321
+ quboFit: "excellent",
12322
+ examples: ["Task Assignment", "Resource Matching", "Workforce Allocation"],
12323
+ status: "coming-soon",
12324
+ tagline: "Who does what \u2014 optimal matching",
12325
+ asciiDiagram: [
12326
+ " Agents Tasks",
12327
+ " [A] \u2500\u2500\u2500\u2500\u2500\u2500 [1]",
12328
+ " [B] \u2500\u2500\u2510",
12329
+ " \u2514\u2500\u2500 [2]",
12330
+ " [C] \u2500\u2500\u2500\u2500\u2500\u2500 [3]",
12331
+ " [D] \u2500\u2500\u2500\u2500\u2500\u2500 [4]"
12332
+ ].join("\n"),
12333
+ subPatterns: [
12334
+ {
12335
+ name: "Bipartite Matching",
12336
+ subtitle: "One-to-One",
12337
+ description: "Match agents to tasks where each agent handles exactly one task and vice versa. Minimize total cost or maximize total benefit across all assignments.",
12338
+ asciiDiagram: [
12339
+ "Workers Jobs",
12340
+ " Alice ---> Design cost: 3",
12341
+ " Bob -----> Code cost: 2",
12342
+ " Carol ---> Test cost: 4",
12343
+ "Total cost: 9 (optimal)"
12344
+ ].join("\n"),
12345
+ quboFitStars: 5
12346
+ },
12347
+ {
12348
+ name: "Quadratic Assignment",
12349
+ subtitle: "Location Matters",
12350
+ description: "Assign facilities to locations minimizing the product of flow between facilities and distance between locations. Used for factory layout and chip placement.",
12351
+ asciiDiagram: [
12352
+ "Facility Flow Location Dist",
12353
+ " A <-50-> B L1 <-3-> L2",
12354
+ " A <-20-> C L1 <-5-> L3",
12355
+ "Minimize: sum(flow*distance)"
12356
+ ].join("\n"),
12357
+ quboFitStars: 5
12358
+ },
12359
+ {
12360
+ name: "Generalized Assignment",
12361
+ subtitle: "Many-to-One",
12362
+ description: "Assign tasks to agents where each agent can handle multiple tasks within their capacity. Workers have different skills and capacities across task types.",
12363
+ asciiDiagram: [
12364
+ "Agent A (cap 10): [T1:3][T2:4][T3:3]",
12365
+ "Agent B (cap 8): [T4:5][T5:3]",
12366
+ "Agent C (cap 12): [T6:6][T7:4]",
12367
+ "Maximize quality within capacity"
12368
+ ].join("\n"),
12369
+ quboFitStars: 4
12370
+ }
12371
+ ],
12372
+ quboExplainer: "Binary variable x(i,j) = 1 if agent i is assigned to task j. Constraints: each task assigned to exactly one agent (row sums = 1), each agent at most one task (column sums <= 1). Cost matrix entries become QUBO coefficients.",
12373
+ useCases: [
12374
+ { industry: "HR", application: "Employee-to-project matching" },
12375
+ { industry: "Education", application: "Student-to-course assignment" },
12376
+ { industry: "Healthcare", application: "Doctor-to-shift assignment" },
12377
+ { industry: "Military", application: "Weapon-target assignment" },
12378
+ { industry: "Sports", application: "Referee-to-game assignment" }
12379
+ ]
12380
+ },
12381
+ {
12382
+ id: "network",
12383
+ name: "Network",
12384
+ description: "Graph partitioning, max cut, community detection",
12385
+ complexity: "NP-hard",
12386
+ color: "#f4511e",
12387
+ quboFit: "excellent",
12388
+ examples: ["Graph Partitioning", "Max Cut", "Network Design"],
12389
+ status: "coming-soon",
12390
+ tagline: "How to structure and divide networks",
12391
+ asciiDiagram: [
12392
+ " a-b-c | d-e",
12393
+ " | | | | |",
12394
+ " f-g | h-i",
12395
+ " -------+-------",
12396
+ " Group A | Group B",
12397
+ " max cut \u2702"
12398
+ ].join("\n"),
12399
+ subPatterns: [
12400
+ {
12401
+ name: "Max Cut",
12402
+ subtitle: "The QUBO Natural",
12403
+ description: "Partition graph nodes into two groups to maximize edges crossing the cut. The most natural QUBO problem \u2014 maps directly without any reformulation tricks.",
12404
+ asciiDiagram: [
12405
+ "Group 0 | Group 1",
12406
+ " a--b | d--e",
12407
+ " | | |",
12408
+ " c ------+------ f",
12409
+ " cut edges = 3 (max!)"
12410
+ ].join("\n"),
12411
+ quboFitStars: 5
12412
+ },
12413
+ {
12414
+ name: "Graph Partitioning",
12415
+ subtitle: "Balanced Split",
12416
+ description: "Divide nodes into k equal-sized groups minimizing edges between groups. Used for parallel computing load balancing and VLSI chip layout.",
12417
+ asciiDiagram: [
12418
+ "Before: tangled graph",
12419
+ "After: [ A ] | [ B ] | [ C ]",
12420
+ " n/3 | n/3 | n/3",
12421
+ "Minimize cross-partition edges"
12422
+ ].join("\n"),
12423
+ quboFitStars: 5
12424
+ },
12425
+ {
12426
+ name: "Community Detection",
12427
+ subtitle: "Find Clusters",
12428
+ description: "Discover densely connected groups within a network. Social networks, protein interactions, citation graphs. Related to modularity maximization.",
12429
+ asciiDiagram: [
12430
+ "(a-b-c) (d-e-f)",
12431
+ " dense dense",
12432
+ " \\ /",
12433
+ " sparse",
12434
+ "Find the natural communities"
12435
+ ].join("\n"),
12436
+ quboFitStars: 4
12437
+ }
12438
+ ],
12439
+ quboExplainer: "For Max Cut: x(i) = 0 or 1 assigns node i to a group. Objective maximizes sum of w(i,j) * x(i) * (1-x(j)) over all edges. This is already a QUBO \u2014 no reformulation needed! Graph partitioning adds a balance constraint as a penalty term.",
12440
+ useCases: [
12441
+ { industry: "Social Media", application: "Community detection" },
12442
+ { industry: "VLSI Design", application: "Chip layout partitioning" },
12443
+ { industry: "HPC", application: "Parallel workload balancing" },
12444
+ { industry: "Biology", application: "Protein interaction clustering" },
12445
+ { industry: "Telecom", application: "Network segmentation" }
12446
+ ]
12447
+ },
12448
+ {
12449
+ id: "selection",
12450
+ name: "Selection",
12451
+ description: "Portfolio optimization, feature selection, subset problems",
12452
+ complexity: "NP-complete",
12453
+ color: "#00897b",
12454
+ quboFit: "good",
12455
+ examples: ["Portfolio Selection", "Feature Selection", "Subset Sum"],
12456
+ status: "coming-soon",
12457
+ tagline: "Which items to choose from a set",
12458
+ asciiDiagram: [
12459
+ " [\u25A0] [\xB7] [\u25A0] [\xB7]",
12460
+ " [\xB7] [\u25A0] [\xB7] [\u25A0]",
12461
+ " [\u25A0] [\xB7] [\xB7] [\xB7]",
12462
+ " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
12463
+ " \u25A0 = selected 5/12",
12464
+ " score: 847 / 1000"
12465
+ ].join("\n"),
12466
+ subPatterns: [
12467
+ {
12468
+ name: "Portfolio Optimization",
12469
+ subtitle: "Risk vs Return",
12470
+ description: "Select assets to maximize return while minimizing risk (variance). Markowitz model is naturally quadratic \u2014 covariance matrix maps directly to QUBO interactions.",
12471
+ asciiDiagram: [
12472
+ "Return ^",
12473
+ " | * efficient frontier",
12474
+ " | *",
12475
+ " | * <- optimal portfolio",
12476
+ " |* ",
12477
+ " +---------> Risk"
12478
+ ].join("\n"),
12479
+ quboFitStars: 5
12480
+ },
12481
+ {
12482
+ name: "Feature Selection",
12483
+ subtitle: "Signal vs Noise",
12484
+ description: "Choose the best subset of features for a machine learning model. Too many features cause overfitting; too few miss signal. Binary choice per feature makes this naturally QUBO.",
12485
+ asciiDiagram: [
12486
+ "Features: [x1] [x2] [x3] ... [xN]",
12487
+ "Select: Y N Y ... N",
12488
+ "",
12489
+ "Maximize: accuracy - lambda*count"
12490
+ ].join("\n"),
12491
+ quboFitStars: 4
12492
+ },
12493
+ {
12494
+ name: "Set Cover",
12495
+ subtitle: "Minimum Coverage",
12496
+ description: "Choose the fewest subsets that together cover all elements. Used for sensor placement, test suite minimization, and service location planning.",
12497
+ asciiDiagram: [
12498
+ "Universe: {1,2,3,4,5,6,7}",
12499
+ "S1={1,2,3} S2={2,4} S3={3,4,5}",
12500
+ "S4={5,6,7} S5={1,6}",
12501
+ "Solution: S1 + S3 + S4 (covers all)"
12502
+ ].join("\n"),
12503
+ quboFitStars: 4
12504
+ }
12505
+ ],
12506
+ quboExplainer: "Binary variable x(i) = 1 if item i is selected. For portfolio: minimize x^T * Sigma * x - lambda * mu^T * x where Sigma is covariance and mu is expected return. The quadratic form IS the QUBO \u2014 no conversion needed.",
12507
+ useCases: [
12508
+ { industry: "Finance", application: "Portfolio construction" },
12509
+ { industry: "Machine Learning", application: "Feature subset selection" },
12510
+ { industry: "Telecom", application: "Cell tower placement" },
12511
+ { industry: "Insurance", application: "Product bundle selection" },
12512
+ { industry: "Military", application: "Sensor network deployment" }
12513
+ ]
12514
+ },
12515
+ {
12516
+ id: "economic",
12517
+ name: "Economic",
12518
+ description: "Market equilibrium, auction design, pricing",
12519
+ complexity: "NP-hard",
12520
+ color: "#ab47bc",
12521
+ quboFit: "experimental",
12522
+ examples: ["Market Clearing", "Auction Optimization", "Price Discovery"],
12523
+ status: "coming-soon",
12524
+ tagline: "Finding optimal prices and market equilibria",
12525
+ asciiDiagram: [
12526
+ " P \u2502\\ /",
12527
+ " \u2502 \\ / Supply",
12528
+ " \u2502 \\/",
12529
+ " \u2502 /\\ \u2190 equilibrium",
12530
+ " \u2502 / \\",
12531
+ " \u2502/ \\ Demand",
12532
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Q"
12533
+ ].join("\n"),
12534
+ subPatterns: [
12535
+ {
12536
+ name: "Market Clearing",
12537
+ subtitle: "Equilibrium",
12538
+ description: "Find prices where supply meets demand across multiple goods simultaneously. Power grid markets clear hourly; financial exchanges clear in microseconds.",
12539
+ asciiDiagram: [
12540
+ "Buyers: B1=$50 B2=$40 B3=$30",
12541
+ "Sellers: S1=$20 S2=$35 S3=$45",
12542
+ "Clear price: $35-40 (2 trades)",
12543
+ "Maximize social welfare"
12544
+ ].join("\n"),
12545
+ quboFitStars: 3
12546
+ },
12547
+ {
12548
+ name: "Combinatorial Auction",
12549
+ subtitle: "Bundle Bidding",
12550
+ description: "Bidders submit bids on bundles of items. Winner determination maximizes revenue while ensuring each item sold at most once. Directly maps to weighted set packing.",
12551
+ asciiDiagram: [
12552
+ "Bid 1: {A,B} = $100",
12553
+ "Bid 2: {B,C} = $80",
12554
+ "Bid 3: {A} = $60",
12555
+ "Bid 4: {C} = $50",
12556
+ "Best: Bid 1 + Bid 4 = $150"
12557
+ ].join("\n"),
12558
+ quboFitStars: 4
12559
+ },
12560
+ {
12561
+ name: "Pricing Optimization",
12562
+ subtitle: "Revenue Maximization",
12563
+ description: "Set prices across products to maximize total revenue considering demand elasticity, competition, and cross-product effects (substitutes and complements).",
12564
+ asciiDiagram: [
12565
+ "Price Demand Revenue",
12566
+ " $10 100 $1000",
12567
+ " $15 70 $1050 <- optimal",
12568
+ " $20 40 $800",
12569
+ " $25 15 $375"
12570
+ ].join("\n"),
12571
+ quboFitStars: 2
12572
+ }
12573
+ ],
12574
+ quboExplainer: "Auction winner determination: x(i) = 1 if bid i is accepted. Maximize sum of bid values. Constraint: for each item, sum of accepted bids containing that item <= 1. Requires discretizing continuous prices into binary tiers for QUBO encoding.",
12575
+ useCases: [
12576
+ { industry: "Energy", application: "Power market clearing" },
12577
+ { industry: "Advertising", application: "Ad auction optimization" },
12578
+ { industry: "Spectrum", application: "FCC spectrum auctions" },
12579
+ { industry: "Retail", application: "Dynamic pricing strategy" },
12580
+ { industry: "Agriculture", application: "Commodity market design" }
12581
+ ]
12582
+ },
12583
+ {
12584
+ id: "continuous",
12585
+ name: "Continuous",
12586
+ description: "Continuous optimization with discretization",
12587
+ complexity: "NP-hard",
12588
+ color: "#78909c",
12589
+ quboFit: "fair",
12590
+ examples: ["Parameter Tuning", "Function Optimization", "Calibration"],
12591
+ status: "coming-soon",
12592
+ tagline: "Finding the best point in continuous space",
12593
+ asciiDiagram: [
12594
+ " f(x)",
12595
+ " \u2502 \u2571\u2572",
12596
+ " \u2502 \u2571 \u2572 \u2571\u2572",
12597
+ " \u2502\u2571 \u2572 \u2571 \u2572",
12598
+ " \u2502 \u2573 \u2572",
12599
+ " \u2502 \u2191 min \u2572",
12600
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 x"
12601
+ ].join("\n"),
12602
+ subPatterns: [
12603
+ {
12604
+ name: "Parameter Tuning",
12605
+ subtitle: "Find the Sweet Spot",
12606
+ description: "Optimize continuous parameters (temperature, pressure, learning rate) by discretizing into binary-encoded levels. Each parameter gets log2(levels) qubits.",
12607
+ asciiDiagram: [
12608
+ "Param A: [0.1, 0.2, 0.3, 0.4]",
12609
+ "Param B: [10, 20, 30, 40, 50]",
12610
+ "",
12611
+ "Encode: 2 bits per param (4 levels)",
12612
+ "Search 4x5 = 20 combinations"
12613
+ ].join("\n"),
12614
+ quboFitStars: 3
12615
+ },
12616
+ {
12617
+ name: "Function Minimization",
12618
+ subtitle: "Global Optimum",
12619
+ description: "Find the global minimum of a multivariate function with many local minima. Discretize the domain and encode as QUBO. Quantum tunneling helps escape local optima.",
12620
+ asciiDiagram: [
12621
+ "f(x) ^",
12622
+ " | /\\ /\\",
12623
+ " | / \\ / \\",
12624
+ " |/ \\/ \\",
12625
+ " | global \\",
12626
+ " +-----min-----> x"
12627
+ ].join("\n"),
12628
+ quboFitStars: 3
12629
+ },
12630
+ {
12631
+ name: "Calibration",
12632
+ subtitle: "Model Fitting",
12633
+ description: "Adjust model parameters to best fit observed data. When the error landscape is non-convex, classical gradient descent gets stuck. QUBO explores the full landscape.",
12634
+ asciiDiagram: [
12635
+ "Data: * * * * * *",
12636
+ "Model: ----\\--/--------",
12637
+ " \\/ <- fit here",
12638
+ "Minimize: sum(error^2)"
12639
+ ].join("\n"),
12640
+ quboFitStars: 2
12641
+ }
12642
+ ],
12643
+ quboExplainer: "Continuous variables are discretized into binary representation. For N levels, use log2(N) qubits per variable. The continuous objective function is approximated as a polynomial in binary variables, yielding a QUBO. Precision trades off against qubit count.",
12644
+ useCases: [
12645
+ { industry: "Engineering", application: "Design parameter optimization" },
12646
+ { industry: "ML/AI", application: "Hyperparameter tuning" },
12647
+ { industry: "Chemistry", application: "Reaction condition optimization" },
12648
+ { industry: "Finance", application: "Model calibration" },
12649
+ { industry: "Manufacturing", application: "Process control tuning" }
12650
+ ]
12651
+ }
12652
+ ];
12653
+
11459
12654
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/quantumSchedule/index.js
11460
12655
  function formatResult(result) {
11461
12656
  const lines = [
@@ -11680,14 +12875,15 @@ ${problem.description}` : "",
11680
12875
  "### Machines:",
11681
12876
  ...problem.machines.map((m) => `- ${m.name} (id: ${m.id})`),
11682
12877
  "",
11683
- "### Structured Problem (JSON):",
11684
- "```json",
11685
- JSON.stringify(problem, null, 2),
11686
- "```",
11687
- "",
11688
- "You can now use the `quantum_schedule` tool to solve this problem with various optimization algorithms."
12878
+ 'A problem is ready to load. Navigate to the Problem tab and click "Load Problem" to review, tweak, and run solvers.'
11689
12879
  ];
11690
- return lines.filter((l) => l !== void 0).join("\n");
12880
+ const humanReadableSummary = lines.filter((l) => l !== void 0).join("\n");
12881
+ return JSON.stringify({
12882
+ __uiSideEffect: true,
12883
+ type: "populateProblem",
12884
+ payload: problem,
12885
+ displayMessage: humanReadableSummary
12886
+ });
11691
12887
  } catch (err) {
11692
12888
  return `Error formulating problem: ${err instanceof Error ? err.message : String(err)}`;
11693
12889
  }
@@ -11779,7 +12975,7 @@ var navigateViewTool = {
11779
12975
 
11780
12976
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
11781
12977
  import { spawn } from "child_process";
11782
- import path12 from "path";
12978
+ import path13 from "path";
11783
12979
  var DEFAULT_TIMEOUT_MS = 6e4;
11784
12980
  var MAX_OUTPUT_SIZE = 100 * 1024;
11785
12981
  var DANGEROUS_PATTERNS = [
@@ -11963,7 +13159,7 @@ async function executeBashCommand(params) {
11963
13159
  };
11964
13160
  }
11965
13161
  const baseCwd = process.cwd();
11966
- const targetCwd = relativeCwd ? path12.resolve(baseCwd, relativeCwd) : baseCwd;
13162
+ const targetCwd = relativeCwd ? path13.resolve(baseCwd, relativeCwd) : baseCwd;
11967
13163
  const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
11968
13164
  return new Promise((resolve3) => {
11969
13165
  let stdout = "";
@@ -12966,9 +14162,9 @@ function parseQuery(query) {
12966
14162
  }
12967
14163
 
12968
14164
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
12969
- import { promises as fs10 } from "fs";
12970
- import { existsSync as existsSync7 } from "fs";
12971
- import path13 from "path";
14165
+ import { promises as fs11 } from "fs";
14166
+ import { existsSync as existsSync9 } from "fs";
14167
+ import path14 from "path";
12972
14168
  import { diffLines as diffLines3 } from "diff";
12973
14169
  function generateDiff(original, modified) {
12974
14170
  const differences = diffLines3(original, modified);
@@ -12992,16 +14188,16 @@ function generateDiff(original, modified) {
12992
14188
  }
12993
14189
  async function editLocalFile(params) {
12994
14190
  const { path: filePath, old_string, new_string } = params;
12995
- const normalizedPath = path13.normalize(filePath);
12996
- const resolvedPath = path13.resolve(process.cwd(), normalizedPath);
12997
- const cwd = path13.resolve(process.cwd());
14191
+ const normalizedPath = path14.normalize(filePath);
14192
+ const resolvedPath = path14.resolve(process.cwd(), normalizedPath);
14193
+ const cwd = path14.resolve(process.cwd());
12998
14194
  if (!resolvedPath.startsWith(cwd)) {
12999
14195
  throw new Error(`Access denied: Cannot edit files outside of current working directory`);
13000
14196
  }
13001
- if (!existsSync7(resolvedPath)) {
14197
+ if (!existsSync9(resolvedPath)) {
13002
14198
  throw new Error(`File not found: ${filePath}`);
13003
14199
  }
13004
- const currentContent = await fs10.readFile(resolvedPath, "utf-8");
14200
+ const currentContent = await fs11.readFile(resolvedPath, "utf-8");
13005
14201
  if (!currentContent.includes(old_string)) {
13006
14202
  const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
13007
14203
  throw new Error(`String to replace not found in file. Make sure the old_string matches exactly (including whitespace and line endings). Searched for: "${preview}"`);
@@ -13011,7 +14207,7 @@ async function editLocalFile(params) {
13011
14207
  throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
13012
14208
  }
13013
14209
  const newContent = currentContent.replace(old_string, new_string);
13014
- await fs10.writeFile(resolvedPath, newContent, "utf-8");
14210
+ await fs11.writeFile(resolvedPath, newContent, "utf-8");
13015
14211
  const diffResult = generateDiff(old_string, new_string);
13016
14212
  return `File edited successfully: ${filePath}
13017
14213
  Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
@@ -13406,7 +14602,6 @@ var b4mTools = {
13406
14602
  edit_image: imageEditTool,
13407
14603
  web_search: webSearchTool,
13408
14604
  web_fetch: webFetchTool,
13409
- wolfram_alpha: wolframAlphaTool,
13410
14605
  math_evaluate: mathTool,
13411
14606
  mermaid_chart: mermaidChartTool,
13412
14607
  current_datetime: currentDateTimeTool,
@@ -13661,12 +14856,27 @@ var GithubManagerAgent = (config) => ({
13661
14856
  exclusiveMcpServers: ["github"],
13662
14857
  systemPrompt: `You are a GitHub specialist with access to GitHub's API. Your job is to help manage repositories, issues, pull requests, code search, branches, and CI/CD workflows.
13663
14858
 
13664
- ## First Step: Resolve Repository Context
13665
- ALWAYS start by calling the \`github__current_user\` tool. The response includes:
14859
+ ## First Step: ALWAYS Resolve Repository Context Automatically
14860
+ Before doing ANYTHING else, call the \`github__current_user\` tool. The response includes:
13666
14861
  - **selected_repositories**: The list of repositories the user has enabled for AI access (in \`owner/repo\` format)
13667
14862
  - **user**: The authenticated user's profile (login, name, etc.)
13668
14863
 
13669
- Use this to resolve the owner and repo name when the user does not specify them. If only one repository is selected, default to that repository. If multiple are selected and the user's request is ambiguous, mention the available repositories and ask for clarification via your response.
14864
+ ### Repository Resolution Rules (CRITICAL)
14865
+ You MUST use the selected_repositories list to automatically resolve the owner and repo. NEVER ask the user for the org, owner, or full repository path. Instead:
14866
+
14867
+ 1. **Exact match**: If the user says a repo name that exactly matches a repo name in selected_repositories, use it immediately.
14868
+ 2. **Partial/fuzzy match**: If the user provides a partial name, match it against any repo in selected_repositories whose name contains that term (e.g., if the user says "lumina" and selected_repositories contains \`SomeOrg/lumina5\`, use that).
14869
+ 3. **Single repo shortcut**: If only one repository is selected, ALWAYS default to it \u2014 no questions asked.
14870
+ 4. **Multiple matches**: Only if multiple selected repos match the user's term AND you truly cannot disambiguate, list the matching repos and ask which one. Do NOT list repos that don't match.
14871
+ 5. **No match**: If nothing in selected_repositories matches, tell the user which repos are available and ask them to clarify.
14872
+
14873
+ **NEVER ask the user to "paste the GitHub URL" or provide the org/owner name.** You already have this information from \`github__current_user\`. The owner and repo are embedded in the \`owner/repo\` format of each selected repository entry.
14874
+
14875
+ ### Defaults for Ambiguous Requests
14876
+ - **Timezone**: Default to UTC unless the user specifies otherwise.
14877
+ - **PR/Issue state**: Default to \`open\` unless the user specifies otherwise.
14878
+ - **Scope**: All matching repos unless the user narrows it down.
14879
+ - **When in doubt, act**: Prefer making reasonable assumptions and proceeding over asking clarifying questions. You can always note your assumptions in the response.
13670
14880
 
13671
14881
  ## Capabilities
13672
14882
 
@@ -13707,7 +14917,7 @@ Use this to resolve the owner and repo name when the user does not specify them.
13707
14917
  ## Output Format
13708
14918
  Provide a clear summary of actions taken:
13709
14919
  1. What was done (created, searched, merged, etc.)
13710
- 2. Links to relevant items (e.g., owner/repo#123, PR URLs)
14920
+ 2. Always provide links to relevant items (e.g., owner/repo#123, PR URLs)
13711
14921
  3. Any issues or warnings encountered
13712
14922
 
13713
14923
  Be precise with repository names, issue numbers, and PR numbers. Your results will be used by the main agent.`
@@ -14402,10 +15612,10 @@ var ToolErrorType;
14402
15612
  // src/utils/diffPreview.ts
14403
15613
  import * as Diff from "diff";
14404
15614
  import { readFile } from "fs/promises";
14405
- import { existsSync as existsSync8 } from "fs";
15615
+ import { existsSync as existsSync10 } from "fs";
14406
15616
  async function generateFileDiffPreview(args) {
14407
15617
  try {
14408
- if (!existsSync8(args.path)) {
15618
+ if (!existsSync10(args.path)) {
14409
15619
  const lines2 = args.content.split("\n");
14410
15620
  const preview = lines2.slice(0, 20).join("\n");
14411
15621
  const hasMore = lines2.length > 20;
@@ -14443,10 +15653,10 @@ ${diffLines4.join("\n")}`;
14443
15653
  }
14444
15654
  async function generateFileDeletePreview(args) {
14445
15655
  try {
14446
- if (!existsSync8(args.path)) {
15656
+ if (!existsSync10(args.path)) {
14447
15657
  return `[File does not exist: ${args.path}]`;
14448
15658
  }
14449
- const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
15659
+ const stats = await import("fs/promises").then((fs15) => fs15.stat(args.path));
14450
15660
  return `[File will be deleted]
14451
15661
 
14452
15662
  Path: ${args.path}
@@ -14745,25 +15955,26 @@ var DEFAULT_AGENT_MODEL = "claude-3-5-haiku-20241022";
14745
15955
  var DEFAULT_THOROUGHNESS = "medium";
14746
15956
 
14747
15957
  // src/utils/toolsAdapter.ts
15958
+ import path15 from "path";
14748
15959
  var NoOpStorage = class extends BaseStorage {
14749
15960
  async upload(input, destination, options) {
14750
15961
  return `/tmp/${destination}`;
14751
15962
  }
14752
- async download(path19) {
15963
+ async download(path21) {
14753
15964
  throw new Error("Download not supported in CLI");
14754
15965
  }
14755
- async delete(path19) {
15966
+ async delete(path21) {
14756
15967
  }
14757
- async getSignedUrl(path19) {
14758
- return `/tmp/${path19}`;
15968
+ async getSignedUrl(path21) {
15969
+ return `/tmp/${path21}`;
14759
15970
  }
14760
- getPublicUrl(path19) {
14761
- return `/tmp/${path19}`;
15971
+ getPublicUrl(path21) {
15972
+ return `/tmp/${path21}`;
14762
15973
  }
14763
- async getPreview(path19) {
14764
- return `/tmp/${path19}`;
15974
+ async getPreview(path21) {
15975
+ return `/tmp/${path21}`;
14765
15976
  }
14766
- async getMetadata(path19) {
15977
+ async getMetadata(path21) {
14767
15978
  return { size: 0, contentType: "application/octet-stream" };
14768
15979
  }
14769
15980
  };
@@ -14936,6 +16147,27 @@ function wrapToolWithHooks(tool, hooks, hookContext) {
14936
16147
  }
14937
16148
  };
14938
16149
  }
16150
+ var CHECKPOINT_TOOLS = /* @__PURE__ */ new Set(["create_file", "edit_local_file", "delete_file"]);
16151
+ function wrapToolWithCheckpointing(tool, checkpointStore) {
16152
+ if (!checkpointStore || !CHECKPOINT_TOOLS.has(tool.toolSchema.name)) {
16153
+ return tool;
16154
+ }
16155
+ const originalFn = tool.toolFn;
16156
+ const toolName = tool.toolSchema.name;
16157
+ return {
16158
+ ...tool,
16159
+ toolFn: async (args) => {
16160
+ const filePath = args?.path;
16161
+ if (filePath) {
16162
+ try {
16163
+ await checkpointStore.createCheckpoint(toolName, [filePath], `before-${toolName}-${path15.basename(filePath)}`);
16164
+ } catch {
16165
+ }
16166
+ }
16167
+ return originalFn(args);
16168
+ }
16169
+ };
16170
+ }
14939
16171
  var TOOL_NAME_MAPPING = {
14940
16172
  // Claude Code -> B4M
14941
16173
  read: "file_read",
@@ -14960,7 +16192,7 @@ function normalizeToolName(toolName) {
14960
16192
  }
14961
16193
  return TOOL_NAME_MAPPING[toolName] || toolName;
14962
16194
  }
14963
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
16195
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, checkpointStore) {
14964
16196
  const logger2 = new CliLogger();
14965
16197
  const storage = new NoOpStorage();
14966
16198
  const user = {
@@ -15019,9 +16251,17 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
15019
16251
  // imageProcessorLambdaName (not needed for CLI)
15020
16252
  tools_to_generate
15021
16253
  );
15022
- let tools = Object.entries(toolsMap).map(
15023
- ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
15024
- );
16254
+ let tools = Object.entries(toolsMap).map(([_, tool]) => {
16255
+ const permissionWrapped = wrapToolWithPermission(
16256
+ tool,
16257
+ permissionManager,
16258
+ showPermissionPrompt,
16259
+ agentContext,
16260
+ configStore,
16261
+ apiClient
16262
+ );
16263
+ return wrapToolWithCheckpointing(permissionWrapped, checkpointStore ?? null);
16264
+ });
15025
16265
  if (toolFilter) {
15026
16266
  const { allowedTools, deniedTools } = toolFilter;
15027
16267
  const normalizedAllowed = allowedTools?.map(normalizeToolName);
@@ -15199,8 +16439,8 @@ function getEnvironmentName(configApiConfig) {
15199
16439
  }
15200
16440
 
15201
16441
  // src/utils/contextLoader.ts
15202
- import * as fs11 from "fs";
15203
- import * as path14 from "path";
16442
+ import * as fs12 from "fs";
16443
+ import * as path16 from "path";
15204
16444
  import { homedir as homedir3 } from "os";
15205
16445
  var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
15206
16446
  var PROJECT_CONTEXT_FILES = [
@@ -15221,9 +16461,9 @@ function formatFileSize2(bytes) {
15221
16461
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
15222
16462
  }
15223
16463
  function tryReadContextFile(dir, filename, source) {
15224
- const filePath = path14.join(dir, filename);
16464
+ const filePath = path16.join(dir, filename);
15225
16465
  try {
15226
- const stats = fs11.lstatSync(filePath);
16466
+ const stats = fs12.lstatSync(filePath);
15227
16467
  if (stats.isDirectory()) {
15228
16468
  return null;
15229
16469
  }
@@ -15237,7 +16477,7 @@ function tryReadContextFile(dir, filename, source) {
15237
16477
  error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
15238
16478
  };
15239
16479
  }
15240
- const content = fs11.readFileSync(filePath, "utf-8");
16480
+ const content = fs12.readFileSync(filePath, "utf-8");
15241
16481
  return {
15242
16482
  filename,
15243
16483
  content,
@@ -15289,7 +16529,7 @@ ${project.content}`;
15289
16529
  }
15290
16530
  async function loadContextFiles(projectDir) {
15291
16531
  const errors = [];
15292
- const globalDir = path14.join(homedir3(), ".bike4mind");
16532
+ const globalDir = path16.join(homedir3(), ".bike4mind");
15293
16533
  const projectDirectory = projectDir || process.cwd();
15294
16534
  const [globalResult, projectResult] = await Promise.all([
15295
16535
  Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
@@ -15560,8 +16800,8 @@ function substituteArguments(template, args) {
15560
16800
  // ../../b4m-core/packages/mcp/dist/src/client.js
15561
16801
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
15562
16802
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
15563
- import path15 from "path";
15564
- import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
16803
+ import path17 from "path";
16804
+ import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
15565
16805
  var MCPClient = class {
15566
16806
  // Note: This class handles MCP server communication with repository filtering
15567
16807
  mcp;
@@ -15602,18 +16842,18 @@ var MCPClient = class {
15602
16842
  const root = process.env.INIT_CWD || process.cwd();
15603
16843
  const candidatePaths = [
15604
16844
  // When running from SST Lambda with node_modules structure (copyFiles)
15605
- path15.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
16845
+ path17.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
15606
16846
  // When running from SST Lambda deployed environment (/var/task)
15607
- path15.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16847
+ path17.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
15608
16848
  // When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
15609
- path15.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16849
+ path17.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
15610
16850
  // When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
15611
- path15.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16851
+ path17.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
15612
16852
  // Original paths (backward compatibility)
15613
- path15.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
15614
- path15.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
16853
+ path17.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16854
+ path17.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
15615
16855
  ];
15616
- const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
16856
+ const serverScriptPath = candidatePaths.find((p) => existsSync11(p));
15617
16857
  if (!serverScriptPath) {
15618
16858
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
15619
16859
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -16444,6 +17684,10 @@ var ServerLlmBackend = class {
16444
17684
  */
16445
17685
  getFallbackModels() {
16446
17686
  return [
17687
+ {
17688
+ id: "claude-sonnet-4-6",
17689
+ name: "Claude 4.6 Sonnet"
17690
+ },
16447
17691
  {
16448
17692
  id: "claude-sonnet-4-5-20250929",
16449
17693
  name: "Claude 4.5 Sonnet"
@@ -16539,6 +17783,7 @@ var WebSocketLlmBackend = class {
16539
17783
  settled = true;
16540
17784
  this.wsManager.offRequest(requestId);
16541
17785
  this.wsManager.offDisconnect(onDisconnect);
17786
+ options.abortSignal?.removeEventListener("abort", abortHandler);
16542
17787
  action();
16543
17788
  };
16544
17789
  const settleResolve = () => settle(() => resolve3());
@@ -16548,19 +17793,16 @@ var WebSocketLlmBackend = class {
16548
17793
  settleReject(new Error("WebSocket connection lost during completion"));
16549
17794
  };
16550
17795
  this.wsManager.onDisconnect(onDisconnect);
17796
+ const abortHandler = () => {
17797
+ logger.debug("[WebSocketLlmBackend] Abort signal received");
17798
+ settleResolve();
17799
+ };
16551
17800
  if (options.abortSignal) {
16552
17801
  if (options.abortSignal.aborted) {
16553
17802
  settleResolve();
16554
17803
  return;
16555
17804
  }
16556
- options.abortSignal.addEventListener(
16557
- "abort",
16558
- () => {
16559
- logger.debug("[WebSocketLlmBackend] Abort signal received");
16560
- settleResolve();
16561
- },
16562
- { once: true }
16563
- );
17805
+ options.abortSignal.addEventListener("abort", abortHandler, { once: true });
16564
17806
  }
16565
17807
  const updateUsage = (usage) => {
16566
17808
  if (usage) {
@@ -16654,6 +17896,7 @@ var WebSocketLlmBackend = class {
16654
17896
  }
16655
17897
  getFallbackModels() {
16656
17898
  return [
17899
+ { id: "claude-sonnet-4-6", name: "Claude 4.6 Sonnet" },
16657
17900
  { id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
16658
17901
  { id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
16659
17902
  { id: "gpt-4o", name: "GPT-4o" },
@@ -16897,27 +18140,35 @@ var WebSocketToolExecutor = class {
16897
18140
  const requestId = uuidv412();
16898
18141
  return new Promise((resolve3, reject) => {
16899
18142
  let settled = false;
18143
+ let timeoutTimer;
16900
18144
  const settle = (action) => {
16901
18145
  if (settled) return;
16902
18146
  settled = true;
18147
+ clearTimeout(timeoutTimer);
16903
18148
  this.wsManager.offRequest(requestId);
16904
18149
  this.wsManager.offDisconnect(onDisconnect);
18150
+ abortSignal?.removeEventListener("abort", abortHandler);
16905
18151
  action();
16906
18152
  };
16907
18153
  const settleResolve = (result) => settle(() => resolve3(result));
16908
18154
  const settleReject = (err) => settle(() => reject(err));
18155
+ const TOOL_TIMEOUT_MS = 5 * 60 * 1e3;
18156
+ timeoutTimer = setTimeout(() => {
18157
+ settleReject(new Error(`Tool execution timed out after ${TOOL_TIMEOUT_MS / 1e3}s`));
18158
+ }, TOOL_TIMEOUT_MS);
16909
18159
  const onDisconnect = () => {
16910
18160
  settleReject(new Error("WebSocket connection lost during tool execution"));
16911
18161
  };
16912
18162
  this.wsManager.onDisconnect(onDisconnect);
18163
+ const abortHandler = () => {
18164
+ settleReject(new Error("Tool execution aborted"));
18165
+ };
16913
18166
  if (abortSignal) {
16914
18167
  if (abortSignal.aborted) {
16915
18168
  settleReject(new Error("Tool execution aborted"));
16916
18169
  return;
16917
18170
  }
16918
- abortSignal.addEventListener("abort", () => settleReject(new Error("Tool execution aborted")), {
16919
- once: true
16920
- });
18171
+ abortSignal.addEventListener("abort", abortHandler, { once: true });
16921
18172
  }
16922
18173
  this.wsManager.onRequest(requestId, (message) => {
16923
18174
  if (message.action === "cli_tool_response") {
@@ -17365,7 +18616,10 @@ var SubagentOrchestrator = class {
17365
18616
  this.deps.showPermissionPrompt,
17366
18617
  agentContext,
17367
18618
  this.deps.configStore,
17368
- this.deps.apiClient
18619
+ this.deps.apiClient,
18620
+ void 0,
18621
+ // toolFilter (applied separately below)
18622
+ this.deps.checkpointStore
17369
18623
  );
17370
18624
  const filteredTools = filterToolsByPatterns2(allTools, toolFilter.allowedTools, toolFilter.deniedTools);
17371
18625
  if (this.deps.customCommandStore) {
@@ -17518,8 +18772,8 @@ var SubagentOrchestrator = class {
17518
18772
  };
17519
18773
 
17520
18774
  // src/agents/AgentStore.ts
17521
- import fs12 from "fs/promises";
17522
- import path16 from "path";
18775
+ import fs13 from "fs/promises";
18776
+ import path18 from "path";
17523
18777
  import os2 from "os";
17524
18778
  import matter2 from "gray-matter";
17525
18779
  var FULL_MODEL_ID_PREFIXES = [
@@ -17552,6 +18806,7 @@ var MODEL_ALIASES = {
17552
18806
  "claude-haiku": "claude-3-5-haiku-20241022",
17553
18807
  // Version-specific Claude aliases
17554
18808
  "claude-4.6-opus": "claude-opus-4-6",
18809
+ "claude-4.6-sonnet": "claude-sonnet-4-6",
17555
18810
  "claude-4.5-opus": "claude-opus-4-5-20251101",
17556
18811
  "claude-4.5-sonnet": "claude-sonnet-4-5-20250929",
17557
18812
  "claude-4.5-haiku": "claude-haiku-4-5-20251001",
@@ -17668,10 +18923,10 @@ var AgentStore = class {
17668
18923
  const root = projectRoot || process.cwd();
17669
18924
  const home = os2.homedir();
17670
18925
  this.builtinAgentsDir = builtinDir;
17671
- this.globalB4MAgentsDir = path16.join(home, ".bike4mind", "agents");
17672
- this.globalClaudeAgentsDir = path16.join(home, ".claude", "agents");
17673
- this.projectB4MAgentsDir = path16.join(root, ".bike4mind", "agents");
17674
- this.projectClaudeAgentsDir = path16.join(root, ".claude", "agents");
18926
+ this.globalB4MAgentsDir = path18.join(home, ".bike4mind", "agents");
18927
+ this.globalClaudeAgentsDir = path18.join(home, ".claude", "agents");
18928
+ this.projectB4MAgentsDir = path18.join(root, ".bike4mind", "agents");
18929
+ this.projectClaudeAgentsDir = path18.join(root, ".claude", "agents");
17675
18930
  }
17676
18931
  /**
17677
18932
  * Load all agents from all directories
@@ -17695,7 +18950,7 @@ var AgentStore = class {
17695
18950
  */
17696
18951
  async loadAgentsFromDirectory(directory, source) {
17697
18952
  try {
17698
- const stats = await fs12.stat(directory);
18953
+ const stats = await fs13.stat(directory);
17699
18954
  if (!stats.isDirectory()) {
17700
18955
  return;
17701
18956
  }
@@ -17723,9 +18978,9 @@ var AgentStore = class {
17723
18978
  async findAgentFiles(directory) {
17724
18979
  const files = [];
17725
18980
  try {
17726
- const entries = await fs12.readdir(directory, { withFileTypes: true });
18981
+ const entries = await fs13.readdir(directory, { withFileTypes: true });
17727
18982
  for (const entry of entries) {
17728
- const fullPath = path16.join(directory, entry.name);
18983
+ const fullPath = path18.join(directory, entry.name);
17729
18984
  if (entry.isDirectory()) {
17730
18985
  const subFiles = await this.findAgentFiles(fullPath);
17731
18986
  files.push(...subFiles);
@@ -17742,10 +18997,10 @@ var AgentStore = class {
17742
18997
  * Parse a single agent markdown file
17743
18998
  */
17744
18999
  async parseAgentFile(filePath, source) {
17745
- const content = await fs12.readFile(filePath, "utf-8");
19000
+ const content = await fs13.readFile(filePath, "utf-8");
17746
19001
  const { data: frontmatter, content: body } = matter2(content);
17747
19002
  const parsed = AgentFrontmatterSchema.parse(frontmatter);
17748
- const name = path16.basename(filePath, ".md");
19003
+ const name = path18.basename(filePath, ".md");
17749
19004
  const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
17750
19005
  const resolution = resolveModelAlias(modelInput, name, filePath);
17751
19006
  if (!resolution.resolved && resolution.warning) {
@@ -17825,16 +19080,16 @@ var AgentStore = class {
17825
19080
  */
17826
19081
  async createAgentFile(name, isGlobal = false, useClaude = true) {
17827
19082
  const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
17828
- const filePath = path16.join(targetDir, `${name}.md`);
19083
+ const filePath = path18.join(targetDir, `${name}.md`);
17829
19084
  try {
17830
- await fs12.access(filePath);
19085
+ await fs13.access(filePath);
17831
19086
  throw new Error(`Agent file already exists: ${filePath}`);
17832
19087
  } catch (error) {
17833
19088
  if (error.code !== "ENOENT") {
17834
19089
  throw error;
17835
19090
  }
17836
19091
  }
17837
- await fs12.mkdir(targetDir, { recursive: true });
19092
+ await fs13.mkdir(targetDir, { recursive: true });
17838
19093
  const template = `---
17839
19094
  description: ${name} agent description
17840
19095
  model: claude-3-5-haiku-20241022
@@ -17869,7 +19124,7 @@ You are a ${name} specialist. Your job is to [describe primary task].
17869
19124
  ## Output Format
17870
19125
  Describe the expected output format here.
17871
19126
  `;
17872
- await fs12.writeFile(filePath, template, "utf-8");
19127
+ await fs13.writeFile(filePath, template, "utf-8");
17873
19128
  return filePath;
17874
19129
  }
17875
19130
  /**
@@ -18313,8 +19568,8 @@ function createBackgroundAgentTools(manager) {
18313
19568
  case "queued":
18314
19569
  return `Agent "${job.agentName}" is queued (waiting for a concurrency slot). Task: ${job.task}`;
18315
19570
  case "running": {
18316
- const elapsed = Math.round((Date.now() - job.startTime) / 1e3);
18317
- return `Agent "${job.agentName}" is still running (${elapsed}s elapsed). Task: ${job.task}`;
19571
+ const elapsed2 = Math.round((Date.now() - job.startTime) / 1e3);
19572
+ return `Agent "${job.agentName}" is still running (${elapsed2}s elapsed). Task: ${job.task}`;
18318
19573
  }
18319
19574
  case "completed": {
18320
19575
  const result = manager.getResult(job_id);
@@ -18346,7 +19601,7 @@ function createBackgroundAgentTools(manager) {
18346
19601
  const jobs = manager.listJobs();
18347
19602
  if (jobs.length === 0) return "No background agents.";
18348
19603
  return jobs.map((job) => {
18349
- const elapsed = Math.round(((job.endTime || Date.now()) - job.startTime) / 1e3);
19604
+ const elapsed2 = Math.round(((job.endTime || Date.now()) - job.startTime) / 1e3);
18350
19605
  const statusIcons = {
18351
19606
  queued: "\u{1F550}",
18352
19607
  running: "\u23F3",
@@ -18355,7 +19610,7 @@ function createBackgroundAgentTools(manager) {
18355
19610
  cancelled: "\u{1F6AB}"
18356
19611
  };
18357
19612
  const statusIcon = statusIcons[job.status] || "\u2753";
18358
- return `${statusIcon} [${job.id}] ${job.agentName} (${job.status}, ${elapsed}s) - ${job.task.slice(0, 80)}`;
19613
+ return `${statusIcon} [${job.id}] ${job.agentName} (${job.status}, ${elapsed2}s) - ${job.task.slice(0, 80)}`;
18359
19614
  }).join("\n");
18360
19615
  },
18361
19616
  toolSchema: {
@@ -18522,7 +19777,7 @@ function createTodoStore(onUpdate) {
18522
19777
 
18523
19778
  // src/tools/findDefinitionTool.ts
18524
19779
  import { stat as stat3 } from "fs/promises";
18525
- import path17 from "path";
19780
+ import path19 from "path";
18526
19781
  import { execFile as execFile2 } from "child_process";
18527
19782
  import { promisify as promisify2 } from "util";
18528
19783
  import { createRequire as createRequire2 } from "module";
@@ -18543,8 +19798,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
18543
19798
  function getRipgrepPath2() {
18544
19799
  try {
18545
19800
  const ripgrepPath = require3.resolve("@vscode/ripgrep");
18546
- const ripgrepDir = path17.dirname(ripgrepPath);
18547
- return path17.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
19801
+ const ripgrepDir = path19.dirname(ripgrepPath);
19802
+ return path19.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
18548
19803
  } catch {
18549
19804
  throw new Error(
18550
19805
  "ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
@@ -18552,10 +19807,10 @@ function getRipgrepPath2() {
18552
19807
  }
18553
19808
  }
18554
19809
  function isPathWithinWorkspace2(targetPath, baseCwd) {
18555
- const resolvedTarget = path17.resolve(targetPath);
18556
- const resolvedBase = path17.resolve(baseCwd);
18557
- const relativePath = path17.relative(resolvedBase, resolvedTarget);
18558
- return !relativePath.startsWith("..") && !path17.isAbsolute(relativePath);
19810
+ const resolvedTarget = path19.resolve(targetPath);
19811
+ const resolvedBase = path19.resolve(baseCwd);
19812
+ const relativePath = path19.relative(resolvedBase, resolvedTarget);
19813
+ return !relativePath.startsWith("..") && !path19.isAbsolute(relativePath);
18559
19814
  }
18560
19815
  function escapeRegex(str) {
18561
19816
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -18588,7 +19843,7 @@ async function findDefinitions(params) {
18588
19843
  throw new Error("symbol_name is required");
18589
19844
  }
18590
19845
  const baseCwd = process.cwd();
18591
- const targetDir = search_path ? path17.resolve(baseCwd, search_path) : baseCwd;
19846
+ const targetDir = search_path ? path19.resolve(baseCwd, search_path) : baseCwd;
18592
19847
  if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
18593
19848
  throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
18594
19849
  }
@@ -18643,7 +19898,7 @@ async function findDefinitions(params) {
18643
19898
  const lineText = match.data.lines.text.trimEnd();
18644
19899
  if (isLikelyDefinition(lineText)) {
18645
19900
  allMatches.push({
18646
- filePath: path17.relative(targetDir, match.data.path.text) || path17.basename(match.data.path.text),
19901
+ filePath: path19.relative(targetDir, match.data.path.text) || path19.basename(match.data.path.text),
18647
19902
  lineNumber: match.data.line_number,
18648
19903
  line: lineText
18649
19904
  });
@@ -18721,8 +19976,8 @@ function createFindDefinitionTool() {
18721
19976
  }
18722
19977
 
18723
19978
  // src/tools/getFileStructure/index.ts
18724
- import { existsSync as existsSync10, promises as fs13, statSync as statSync6 } from "fs";
18725
- import path18 from "path";
19979
+ import { existsSync as existsSync12, promises as fs14, statSync as statSync6 } from "fs";
19980
+ import path20 from "path";
18726
19981
 
18727
19982
  // src/tools/getFileStructure/formatter.ts
18728
19983
  var SECTIONS = [
@@ -18759,35 +20014,35 @@ function formatSection(lines, title, items, format) {
18759
20014
  }
18760
20015
 
18761
20016
  // src/tools/getFileStructure/index.ts
18762
- var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
20017
+ var MAX_FILE_SIZE4 = 10 * 1024 * 1024;
18763
20018
  function createGetFileStructureTool() {
18764
20019
  return {
18765
20020
  toolFn: async (value) => {
18766
20021
  const params = value;
18767
20022
  try {
18768
20023
  const cwd = process.cwd();
18769
- const resolvedPath = path18.resolve(cwd, params.path);
20024
+ const resolvedPath = path20.resolve(cwd, params.path);
18770
20025
  if (!resolvedPath.startsWith(cwd)) {
18771
20026
  return "Error: Access denied - cannot read files outside of current working directory";
18772
20027
  }
18773
- if (!existsSync10(resolvedPath)) {
20028
+ if (!existsSync12(resolvedPath)) {
18774
20029
  return `Error: File not found: ${params.path}`;
18775
20030
  }
18776
20031
  const stats = statSync6(resolvedPath);
18777
20032
  if (stats.isDirectory()) {
18778
20033
  return `Error: Path is a directory, not a file: ${params.path}`;
18779
20034
  }
18780
- if (stats.size > MAX_FILE_SIZE3) {
18781
- return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE3 / 1024 / 1024}MB`;
20035
+ if (stats.size > MAX_FILE_SIZE4) {
20036
+ return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE4 / 1024 / 1024}MB`;
18782
20037
  }
18783
- const ext = path18.extname(resolvedPath).toLowerCase();
20038
+ const ext = path20.extname(resolvedPath).toLowerCase();
18784
20039
  const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
18785
20040
  const languageId = getLanguageForExtension(ext);
18786
20041
  if (!languageId) {
18787
20042
  const supported = getSupportedLanguages();
18788
20043
  return `Error: Unsupported file type "${ext}". Supported languages: ${supported.join(", ")}`;
18789
20044
  }
18790
- const sourceCode = await fs13.readFile(resolvedPath, "utf-8");
20045
+ const sourceCode = await fs14.readFile(resolvedPath, "utf-8");
18791
20046
  const lineCount = sourceCode.split("\n").length;
18792
20047
  const items = await parseFileStructure(sourceCode, languageId);
18793
20048
  return formatStructureOutput(params.path, items, stats.size, lineCount);
@@ -18849,7 +20104,8 @@ function CliApp() {
18849
20104
  abortController: null,
18850
20105
  contextContent: "",
18851
20106
  backgroundManager: null,
18852
- wsManager: null
20107
+ wsManager: null,
20108
+ checkpointStore: null
18853
20109
  });
18854
20110
  const [isInitialized, setIsInitialized] = useState10(false);
18855
20111
  const [initError, setInitError] = useState10(null);
@@ -19103,6 +20359,12 @@ function CliApp() {
19103
20359
  enqueuePermissionPrompt(prompt);
19104
20360
  });
19105
20361
  };
20362
+ const checkpointProjectDir = state.configStore.getProjectConfigDir() || process.cwd();
20363
+ const checkpointStore = new CheckpointStore(checkpointProjectDir);
20364
+ try {
20365
+ await checkpointStore.init(newSession.id);
20366
+ } catch {
20367
+ }
19106
20368
  const agentContext = {
19107
20369
  currentAgent: null,
19108
20370
  observationQueue: []
@@ -19115,7 +20377,10 @@ function CliApp() {
19115
20377
  promptFn,
19116
20378
  agentContext,
19117
20379
  state.configStore,
19118
- apiClient
20380
+ apiClient,
20381
+ void 0,
20382
+ // toolFilter
20383
+ checkpointStore
19119
20384
  );
19120
20385
  startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
19121
20386
  const mcpManager = new McpManager(config);
@@ -19146,7 +20411,8 @@ function CliApp() {
19146
20411
  apiClient,
19147
20412
  agentStore,
19148
20413
  customCommandStore: state.customCommandStore,
19149
- enableParallelToolExecution: config.preferences.enableParallelToolExecution === true
20414
+ enableParallelToolExecution: config.preferences.enableParallelToolExecution === true,
20415
+ checkpointStore
19150
20416
  });
19151
20417
  const backgroundManager = new BackgroundAgentManager(orchestrator);
19152
20418
  backgroundManager.setOnStatusChange((job) => {
@@ -19269,8 +20535,10 @@ function CliApp() {
19269
20535
  // Store raw context for compact instructions
19270
20536
  backgroundManager,
19271
20537
  // Store for grouped notification turn tracking
19272
- wsManager
20538
+ wsManager,
19273
20539
  // WebSocket connection manager (null if using SSE fallback)
20540
+ checkpointStore
20541
+ // File change checkpointing for undo/restore
19274
20542
  }));
19275
20543
  setStoreSession(newSession);
19276
20544
  const bannerLines = [
@@ -19963,6 +21231,10 @@ Available commands:
19963
21231
  /exit - Exit the CLI
19964
21232
  /clear - Start a new session
19965
21233
  /rewind - Rewind conversation to a previous point
21234
+ /undo - Undo the last file change
21235
+ /checkpoints - List available file restore points
21236
+ /restore <n> - Restore files to a specific checkpoint
21237
+ /diff [n] - Show diff between current state and a checkpoint
19966
21238
  /login - Authenticate with your B4M account
19967
21239
  /logout - Clear authentication and sign out
19968
21240
  /whoami - Show current authenticated user
@@ -20055,6 +21327,9 @@ Keyboard Shortcuts:
20055
21327
  }
20056
21328
  await logger.initialize(loadedSession.id);
20057
21329
  logger.debug("=== Session Resumed ===");
21330
+ if (state.checkpointStore) {
21331
+ state.checkpointStore.setSessionId(loadedSession.id);
21332
+ }
20058
21333
  setState((prev) => ({ ...prev, session: loadedSession }));
20059
21334
  setStoreSession(loadedSession);
20060
21335
  useCliStore.getState().clearPendingMessages();
@@ -20293,6 +21568,9 @@ Keyboard Shortcuts:
20293
21568
  };
20294
21569
  await logger.initialize(newSession.id);
20295
21570
  logger.debug("=== New Session Started via /clear ===");
21571
+ if (state.checkpointStore) {
21572
+ state.checkpointStore.setSessionId(newSession.id);
21573
+ }
20296
21574
  setState((prev) => ({ ...prev, session: newSession }));
20297
21575
  setStoreSession(newSession);
20298
21576
  useCliStore.getState().clearPendingMessages();
@@ -20303,8 +21581,7 @@ Keyboard Shortcuts:
20303
21581
  `);
20304
21582
  break;
20305
21583
  }
20306
- case "rewind":
20307
- case "undo": {
21584
+ case "rewind": {
20308
21585
  if (!state.session) {
20309
21586
  console.log("No active session to rewind");
20310
21587
  return;
@@ -20363,6 +21640,103 @@ Keyboard Shortcuts:
20363
21640
  }));
20364
21641
  break;
20365
21642
  }
21643
+ case "undo": {
21644
+ if (!state.checkpointStore) {
21645
+ console.log("Checkpointing not available.");
21646
+ return;
21647
+ }
21648
+ const undoCheckpoints = state.checkpointStore.listCheckpoints();
21649
+ if (undoCheckpoints.length === 0) {
21650
+ console.log("No checkpoints available. No file changes have been made yet.");
21651
+ return;
21652
+ }
21653
+ try {
21654
+ const restored = await state.checkpointStore.undoLast();
21655
+ console.log(`
21656
+ \u2705 Restored ${restored.filePaths.length} file(s) to state before: ${restored.name}`);
21657
+ for (const f of restored.filePaths) {
21658
+ console.log(` - ${f}`);
21659
+ }
21660
+ console.log("");
21661
+ } catch (err) {
21662
+ console.log(`Failed to undo: ${err instanceof Error ? err.message : String(err)}`);
21663
+ }
21664
+ break;
21665
+ }
21666
+ case "checkpoints": {
21667
+ if (!state.checkpointStore) {
21668
+ console.log("Checkpointing not available.");
21669
+ return;
21670
+ }
21671
+ const cpList = state.checkpointStore.listCheckpoints();
21672
+ if (cpList.length === 0) {
21673
+ console.log("No checkpoints yet. Checkpoints are created automatically before file changes.");
21674
+ return;
21675
+ }
21676
+ console.log("\nCheckpoints (most recent first):\n");
21677
+ cpList.forEach((cp, idx) => {
21678
+ const ageMs = Date.now() - new Date(cp.timestamp).getTime();
21679
+ const ageSec = Math.floor(ageMs / 1e3);
21680
+ let age;
21681
+ if (ageSec < 60) age = `${ageSec}s ago`;
21682
+ else if (ageSec < 3600) age = `${Math.floor(ageSec / 60)}m ago`;
21683
+ else age = `${Math.floor(ageSec / 3600)}h ago`;
21684
+ console.log(` ${idx + 1}. ${cp.name} (${age}) - ${cp.filePaths.length} file(s)`);
21685
+ });
21686
+ console.log("\nUse /restore <number> to restore, /diff <number> to see changes.\n");
21687
+ break;
21688
+ }
21689
+ case "restore": {
21690
+ if (!state.checkpointStore) {
21691
+ console.log("Checkpointing not available.");
21692
+ return;
21693
+ }
21694
+ const restoreTarget = args[0];
21695
+ if (!restoreTarget) {
21696
+ console.log("Usage: /restore <checkpoint-number>");
21697
+ console.log("Use /checkpoints to list available restore points.");
21698
+ return;
21699
+ }
21700
+ const restoreNum = parseInt(restoreTarget, 10);
21701
+ if (isNaN(restoreNum) || restoreNum < 1) {
21702
+ console.log("Please provide a valid checkpoint number. Use /checkpoints to list.");
21703
+ return;
21704
+ }
21705
+ try {
21706
+ const restoredCp = await state.checkpointStore.restoreCheckpoint(restoreNum);
21707
+ console.log(`
21708
+ \u2705 Restored to checkpoint: ${restoredCp.name}`);
21709
+ for (const f of restoredCp.filePaths) {
21710
+ console.log(` - ${f}`);
21711
+ }
21712
+ console.log("");
21713
+ } catch (err) {
21714
+ console.log(`Failed to restore: ${err instanceof Error ? err.message : String(err)}`);
21715
+ }
21716
+ break;
21717
+ }
21718
+ case "diff": {
21719
+ if (!state.checkpointStore) {
21720
+ console.log("Checkpointing not available.");
21721
+ return;
21722
+ }
21723
+ const diffTarget = args[0] ? parseInt(args[0], 10) : 1;
21724
+ if (isNaN(diffTarget) || diffTarget < 1) {
21725
+ console.log("Usage: /diff [checkpoint-number] (defaults to 1, most recent)");
21726
+ return;
21727
+ }
21728
+ try {
21729
+ const diffOutput = state.checkpointStore.getCheckpointDiff(diffTarget);
21730
+ if (!diffOutput.trim()) {
21731
+ console.log("No differences found between checkpoint and current state.");
21732
+ } else {
21733
+ console.log(diffOutput);
21734
+ }
21735
+ } catch (err) {
21736
+ console.log(`Failed to generate diff: ${err instanceof Error ? err.message : String(err)}`);
21737
+ }
21738
+ break;
21739
+ }
20366
21740
  case "usage": {
20367
21741
  const usageAuthTokens = await state.configStore.getAuthTokens();
20368
21742
  if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {