@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/{artifactExtractor-C2IQKNSV.js → artifactExtractor-HSTGLMPL.js} +1 -1
- package/dist/{chunk-WZURXWHI.js → chunk-2PXPXXDN.js} +116 -7
- package/dist/{chunk-KTHRGR3M.js → chunk-EEGKRFVP.js} +43 -20
- package/dist/{chunk-RCVCDNU6.js → chunk-LAZAO77H.js} +2 -8
- package/dist/{chunk-WO3ITPAK.js → chunk-RTD3GUAP.js} +2 -2
- package/dist/{chunk-DRPTZ5JQ.js → chunk-S3S2FV2N.js} +6 -6
- package/dist/{chunk-MHXRROUI.js → chunk-ZHXVJFCI.js} +2 -2
- package/dist/commands/doctorCommand.js +1 -1
- package/dist/commands/updateCommand.js +1 -1
- package/dist/{create-3DD2VF24.js → create-ICV35GRO.js} +3 -3
- package/dist/index.js +1726 -352
- package/dist/{llmMarkdownGenerator-6F3NRN2U.js → llmMarkdownGenerator-6FOXI2T4.js} +1 -1
- package/dist/{markdownGenerator-I6SXLUQY.js → markdownGenerator-6RY7C5B7.js} +1 -1
- package/dist/{mementoService-IVERNAPK.js → mementoService-OHL67X43.js} +3 -3
- package/dist/{src-4LH7F47S.js → src-O2DE2WAD.js} +7 -1
- package/dist/{src-SSG6BPOK.js → src-ZR2BFWLN.js} +2 -2
- package/dist/{subtractCredits-OOGV44PO.js → subtractCredits-OPURYRNV.js} +3 -3
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunk-GQGOWACU.js";
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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)" : `(${
|
|
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/
|
|
2843
|
-
import fs5 from "fs
|
|
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
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
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
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
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
|
|
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
|
|
3426
|
+
const entries = await fs6.readdir(directory, { withFileTypes: true });
|
|
3074
3427
|
for (const entry of entries) {
|
|
3075
|
-
const fullPath =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
3177
|
-
const fileExists = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6123
|
-
return
|
|
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
|
|
7560
|
-
return
|
|
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
|
|
9502
|
-
import { existsSync as
|
|
9503
|
-
import
|
|
9504
|
-
var
|
|
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 =
|
|
9508
|
-
const resolvedPath =
|
|
9509
|
-
const 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 (!
|
|
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 >
|
|
9521
|
-
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
9627
|
-
import { existsSync as
|
|
9628
|
-
import
|
|
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 =
|
|
9632
|
-
const resolvedPath =
|
|
9633
|
-
const 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 =
|
|
9880
|
+
const fileExists = existsSync6(resolvedPath);
|
|
9638
9881
|
const action = fileExists ? "overwritten" : "created";
|
|
9639
9882
|
if (createDirectories) {
|
|
9640
|
-
const dir =
|
|
9641
|
-
await
|
|
9883
|
+
const dir = path9.dirname(resolvedPath);
|
|
9884
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
9642
9885
|
}
|
|
9643
|
-
await
|
|
9644
|
-
const stats = await
|
|
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 =
|
|
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
|
|
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 ?
|
|
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) =>
|
|
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
|
|
9808
|
-
import
|
|
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 =
|
|
9817
|
-
const rgBinary =
|
|
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 =
|
|
9825
|
-
const resolvedBase =
|
|
9826
|
-
const relativePath =
|
|
9827
|
-
return !relativePath.startsWith("..") && !
|
|
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 ?
|
|
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:
|
|
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
|
|
9999
|
-
import { existsSync as
|
|
10000
|
-
import
|
|
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 =
|
|
10004
|
-
const resolvedPath =
|
|
10005
|
-
const 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 (!
|
|
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
|
|
10283
|
+
await fs10.rm(resolvedPath, { recursive: true, force: true });
|
|
10020
10284
|
return `Directory deleted successfully: ${filePath}`;
|
|
10021
10285
|
} else {
|
|
10022
|
-
await
|
|
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 =
|
|
10033
|
-
const 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/
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
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
|
-
{
|
|
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
|
-
###
|
|
11391
|
-
When a user describes a scheduling
|
|
11392
|
-
1. Use \`quantum_formulate\` to convert their description into
|
|
11393
|
-
2.
|
|
11394
|
-
3.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
12970
|
-
import { existsSync as
|
|
12971
|
-
import
|
|
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 =
|
|
12996
|
-
const resolvedPath =
|
|
12997
|
-
const 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 (!
|
|
14197
|
+
if (!existsSync9(resolvedPath)) {
|
|
13002
14198
|
throw new Error(`File not found: ${filePath}`);
|
|
13003
14199
|
}
|
|
13004
|
-
const currentContent = await
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
15615
|
+
import { existsSync as existsSync10 } from "fs";
|
|
14406
15616
|
async function generateFileDiffPreview(args) {
|
|
14407
15617
|
try {
|
|
14408
|
-
if (!
|
|
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 (!
|
|
15656
|
+
if (!existsSync10(args.path)) {
|
|
14447
15657
|
return `[File does not exist: ${args.path}]`;
|
|
14448
15658
|
}
|
|
14449
|
-
const stats = await import("fs/promises").then((
|
|
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(
|
|
15963
|
+
async download(path21) {
|
|
14753
15964
|
throw new Error("Download not supported in CLI");
|
|
14754
15965
|
}
|
|
14755
|
-
async delete(
|
|
15966
|
+
async delete(path21) {
|
|
14756
15967
|
}
|
|
14757
|
-
async getSignedUrl(
|
|
14758
|
-
return `/tmp/${
|
|
15968
|
+
async getSignedUrl(path21) {
|
|
15969
|
+
return `/tmp/${path21}`;
|
|
14759
15970
|
}
|
|
14760
|
-
getPublicUrl(
|
|
14761
|
-
return `/tmp/${
|
|
15971
|
+
getPublicUrl(path21) {
|
|
15972
|
+
return `/tmp/${path21}`;
|
|
14762
15973
|
}
|
|
14763
|
-
async getPreview(
|
|
14764
|
-
return `/tmp/${
|
|
15974
|
+
async getPreview(path21) {
|
|
15975
|
+
return `/tmp/${path21}`;
|
|
14765
15976
|
}
|
|
14766
|
-
async getMetadata(
|
|
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
|
-
|
|
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
|
|
15203
|
-
import * as
|
|
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 =
|
|
16464
|
+
const filePath = path16.join(dir, filename);
|
|
15225
16465
|
try {
|
|
15226
|
-
const stats =
|
|
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 =
|
|
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 =
|
|
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
|
|
15564
|
-
import { existsSync as
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16851
|
+
path17.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
15612
16852
|
// Original paths (backward compatibility)
|
|
15613
|
-
|
|
15614
|
-
|
|
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) =>
|
|
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",
|
|
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
|
|
17522
|
-
import
|
|
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 =
|
|
17672
|
-
this.globalClaudeAgentsDir =
|
|
17673
|
-
this.projectB4MAgentsDir =
|
|
17674
|
-
this.projectClaudeAgentsDir =
|
|
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
|
|
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
|
|
18981
|
+
const entries = await fs13.readdir(directory, { withFileTypes: true });
|
|
17727
18982
|
for (const entry of entries) {
|
|
17728
|
-
const fullPath =
|
|
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
|
|
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 =
|
|
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 =
|
|
19083
|
+
const filePath = path18.join(targetDir, `${name}.md`);
|
|
17829
19084
|
try {
|
|
17830
|
-
await
|
|
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
|
|
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
|
|
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
|
|
18317
|
-
return `Agent "${job.agentName}" is still running (${
|
|
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
|
|
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}, ${
|
|
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
|
|
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 =
|
|
18547
|
-
return
|
|
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 =
|
|
18556
|
-
const resolvedBase =
|
|
18557
|
-
const relativePath =
|
|
18558
|
-
return !relativePath.startsWith("..") && !
|
|
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 ?
|
|
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:
|
|
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
|
|
18725
|
-
import
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 >
|
|
18781
|
-
return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${
|
|
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 =
|
|
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
|
|
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()) {
|